Skip to content

Commit 1472b2a

Browse files
authored
Merge pull request #166 from TaloDev/develop
Release 0.28.0
2 parents de1b455 + 0ab3f58 commit 1472b2a

29 files changed

+427
-127
lines changed

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
- name: Build and push
2626
id: docker_build
27-
uses: docker/build-push-action@v3
27+
uses: docker/build-push-action@v4
2828
with:
2929
push: true
3030
tags: |

envs/.env.dev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ FROM_EMAIL=hello@trytalo.com
2121

2222
RECOVERY_CODES_SECRET=tc0d8e0h0lqv5isajfjw0iivj5pc3d95
2323
STEAM_INTEGRATION_SECRET=PjBw8vy8ZbFqXvZwAABWfbhfXvJ32idf
24+
API_SECRET=YgGBEely4gOIzXGOf44N4IWtFwMCzw8O
2425

2526
STRIPE_KEY=

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "game-services",
3-
"version": "0.27.0",
3+
"version": "0.28.0",
44
"description": "",
55
"main": "src/index.ts",
66
"scripts": {
@@ -70,7 +70,7 @@
7070
"koa-bodyparser": "^4.3.0",
7171
"koa-clay": "^6.5.0",
7272
"koa-helmet": "^6.1.0",
73-
"koa-jwt": "^4.0.3",
73+
"koa-jwt": "^4.0.4",
7474
"koa-logger": "^3.2.1",
7575
"lodash.get": "^4.4.2",
7676
"lodash.groupby": "^4.6.0",

src/config/api-routes.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@ import EventAPIService from '../services/api/event-api.service'
88
import PlayerAPIService from '../services/api/player-api.service'
99
import limiterMiddleware from '../middlewares/limiter-middleware'
1010
import currentPlayerMiddleware from '../middlewares/current-player-middleware'
11+
import { apiRouteAuthMiddleware, getRouteInfo } from '../middlewares/route-middleware'
12+
import apiKeyMiddleware from '../middlewares/api-key-middleware'
1113

1214
export default (app: Koa) => {
15+
app.use(apiKeyMiddleware)
16+
app.use(apiRouteAuthMiddleware)
17+
app.use(limiterMiddleware)
18+
1319
app.use(async (ctx: Context, next: Next): Promise<void> => {
14-
if (ctx.path.match(/^\/(v1)\//)) {
15-
if (ctx.state.user?.api !== true) ctx.throw(401)
16-
}
20+
const route = getRouteInfo(ctx)
21+
if (route.isAPIRoute && !route.isAPICall) ctx.throw(401)
1722
await next()
1823
})
1924

20-
if (process.env.NODE_ENV !== 'test') {
21-
app.use(limiterMiddleware)
22-
}
23-
2425
app.use(currentPlayerMiddleware)
2526

2627
app.use(service('/v1/game-config', new GameConfigAPIService()))

src/config/protected-routes.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ import PlayerService from '../services/player.service'
1515
import UserService from '../services/user.service'
1616
import BillingService from '../services/billing.service'
1717
import IntegrationService from '../services/integration.service'
18+
import { getRouteInfo, protectedRouteAuthMiddleware } from '../middlewares/route-middleware'
1819

1920
export default (app: Koa) => {
21+
app.use(protectedRouteAuthMiddleware)
22+
2023
app.use(async (ctx: Context, next: Next): Promise<void> => {
21-
// trying to access protected route via api key
22-
if (!ctx.path.match(/^\/(public|v1)\//)) {
23-
if (ctx.state.user?.api) ctx.throw(401)
24-
}
24+
const route = getRouteInfo(ctx)
25+
if (route.isProtectedRoute && route.isAPICall) ctx.throw(401)
2526
await next()
2627
})
2728

src/entities/api-key.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
22
import Game from './game'
33
import User from './user'
4-
import jwt from 'jsonwebtoken'
54

65
export enum APIKeyScope {
76
READ_GAME_CONFIG = 'read:gameConfig',
@@ -39,27 +38,22 @@ export default class APIKey {
3938
@Property({ nullable: true })
4039
revokedAt?: Date
4140

41+
@Property({ nullable: true })
42+
lastUsedAt?: Date
43+
4244
constructor(game: Game, createdByUser: User) {
4345
this.game = game
4446
this.createdByUser = createdByUser
4547
}
4648

47-
createTokenSync(apiKey: APIKey, iat: number) {
48-
const payload = { sub: apiKey.id, api: true, iat }
49-
return jwt.sign(payload, process.env.JWT_SECRET)
50-
}
51-
5249
toJSON() {
53-
const iat = new Date(this.createdAt).getTime()
54-
const token = this.createTokenSync(this, Math.floor(iat / 1000))
55-
5650
return {
5751
id: this.id,
58-
token: token.substring(token.length - 5, token.length),
5952
scopes: this.scopes,
6053
gameId: this.game.id,
6154
createdBy: this.createdByUser.username,
62-
createdAt: this.createdAt
55+
createdAt: this.createdAt,
56+
lastUsedAt: this.lastUsedAt
6357
}
6458
}
6559
}

src/entities/game-secret.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Entity, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
2+
import { decrypt, encrypt } from '../lib/crypto/string-encryption'
3+
import Game from './game'
4+
import crypto from 'crypto'
5+
6+
@Entity()
7+
export default class GameSecret {
8+
@PrimaryKey()
9+
id: number
10+
11+
@OneToOne(() => Game, (game) => game.apiSecret)
12+
game: Game
13+
14+
@Property({ hidden: true })
15+
secret: string
16+
17+
constructor() {
18+
this.secret = this.generateSecret()
19+
}
20+
21+
generateSecret(): string {
22+
const secret = Buffer.from(crypto.randomBytes(48)).toString('hex')
23+
return encrypt(secret, process.env.API_SECRET)
24+
}
25+
26+
getPlainSecret(): string {
27+
return decrypt(this.secret, process.env.API_SECRET)
28+
}
29+
30+
/* c8 ignore start */
31+
toJSON() {
32+
return {
33+
id: this.id
34+
}
35+
}
36+
/* c8 ignore stop */
37+
}

src/entities/game.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Collection, Embedded, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
1+
import { Collection, Embedded, Entity, ManyToOne, OneToMany, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
2+
import GameSecret from './game-secret'
23
import Organisation from './organisation'
34
import Player from './player'
45
import Prop from './prop'
@@ -20,6 +21,9 @@ export default class Game {
2021
@OneToMany(() => Player, (player) => player.game)
2122
players: Collection<Player> = new Collection<Player>(this)
2223

24+
@OneToOne({ orphanRemoval: true })
25+
apiSecret: GameSecret
26+
2327
@Property()
2428
createdAt: Date = new Date()
2529

src/entities/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import SteamworksIntegrationEvent from './steamworks-integration-event'
2828
import SteamworksLeaderboardMapping from './steamworks-leaderboard-mapping'
2929
import PlayerProp from './player-prop'
3030
import PlayerGroup from './player-group'
31+
import GameSecret from './game-secret'
3132

3233
export default [
34+
GameSecret,
3335
PlayerGroup,
3436
PlayerProp,
3537
SteamworksLeaderboardMapping,

src/index.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'dotenv/config'
22
import Koa, { Context, Next } from 'koa'
33
import logger from 'koa-logger'
44
import bodyParser from 'koa-bodyparser'
5-
import jwt from 'koa-jwt'
65
import helmet from 'koa-helmet'
76
import { RequestContext } from '@mikro-orm/core'
87
import configureProtectedRoutes from './config/protected-routes'
@@ -27,24 +26,18 @@ export const init = async (): Promise<Koa> => {
2726
app.use(errorMiddleware)
2827
app.use(bodyParser())
2928
app.use(helmet())
30-
3129
app.use(corsMiddleware)
32-
app.use(jwt({ secret: process.env.JWT_SECRET }).unless({ path: [/^\/public/] }))
33-
3430
app.use((ctx: Context, next: Next) => RequestContext.createAsync(ctx.em, next))
35-
3631
app.use(devDataMiddleware)
32+
app.context.emailQueue = createEmailQueue()
3733

3834
configureProtectedRoutes(app)
3935
configurePublicRoutes(app)
4036
configureAPIRoutes(app)
4137

42-
app.context.emailQueue = createEmailQueue()
43-
4438
app.use(cleanupMiddleware)
4539

4640
if (!isTest) app.listen(80, () => console.log('Listening on port 80'))
47-
4841
return app
4942
}
5043

0 commit comments

Comments
 (0)