Skip to content

Commit 0c440e2

Browse files
authored
Merge pull request #111 from TaloDev/develop
Release 0.15.0
2 parents 06d017b + a0a4f38 commit 0c440e2

File tree

11 files changed

+110
-25
lines changed

11 files changed

+110
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "game-services",
3-
"version": "0.14.1",
3+
"version": "0.15.0",
44
"description": "",
55
"main": "src/index.ts",
66
"scripts": {

src/entities/event.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Entity, Embedded, ManyToOne, PrimaryKey, Property, Cascade } from '@mikro-orm/core'
2+
import sanitiseProps from '../lib/props/sanitiseProps'
23
import Game from './game'
34
import PlayerAlias from './player-alias'
45
import Prop from './prop'
56

7+
const eventMetaProps = ['META_OS', 'META_GAME_VERSION', 'META_WINDOW_MODE', 'META_SCREEN_WIDTH', 'META_SCREEN_HEIGHT']
8+
69
@Entity()
710
export default class Event {
811
@PrimaryKey()
@@ -31,6 +34,18 @@ export default class Event {
3134
this.game = game
3235
}
3336

37+
setProps(props: Prop[]) {
38+
this.props = sanitiseProps(props, true, (prop) => {
39+
return !prop.key.startsWith('META_') || eventMetaProps.includes(prop.key)
40+
})
41+
42+
this.props.forEach((prop) => {
43+
if (eventMetaProps.includes(prop.key)) {
44+
this.playerAlias.player.props.push(prop)
45+
}
46+
})
47+
}
48+
3449
toJSON() {
3550
return {
3651
id: this.id,

src/lib/props/sanitiseProps.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import Prop from '../../entities/prop'
22

3-
const sanitiseProps = (props: Prop[], deleteNull = false): Prop[] => {
3+
const sanitiseProps = (props: Prop[], deleteNull = false, extraFilter?: (prop: Prop) => boolean): Prop[] => {
44
if (deleteNull) props = props.filter((prop) => prop.value !== null)
55

6-
return props.filter((prop) => Boolean(prop.key)).map((prop) => new Prop(String(prop.key), prop.value ? String(prop.value) : null))
6+
return props
7+
.filter((prop) => Boolean(prop.key))
8+
.filter((prop) => extraFilter?.(prop) ?? true)
9+
.map((prop) => new Prop(String(prop.key), prop.value ? String(prop.value) : null))
710
}
811

912
export default sanitiseProps

src/policies/api-key.policy.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import { PolicyDenial, Request, PolicyResponse } from 'koa-clay'
33
import APIKey from '../entities/api-key'
44
import { UserType } from '../entities/user'
55
import UserTypeGate from './user-type-gate'
6+
import EmailConfirmedGate from './email-confirmed-gate'
67

78
export default class APIKeyPolicy extends Policy {
89
@UserTypeGate([UserType.ADMIN], 'create API keys')
10+
@EmailConfirmedGate('create API keys')
911
async post(req: Request): Promise<PolicyResponse> {
1012
const { gameId } = req.body
1113

12-
const user = await this.getUser()
13-
if (!user.emailConfirmed) return new PolicyDenial({ message: 'You need to confirm your email address to do this' })
14-
1514
const canAccessGame = await this.canAccessGame(gameId)
1615
return canAccessGame
1716
}

src/policies/data-export.policy.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Policy from './policy'
2-
import { PolicyDenial, Request, PolicyResponse } from 'koa-clay'
2+
import { Request, PolicyResponse } from 'koa-clay'
33
import { UserType } from '../entities/user'
44
import UserTypeGate from './user-type-gate'
5+
import EmailConfirmedGate from './email-confirmed-gate'
56

67
export default class DataExportPolicy extends Policy {
78
@UserTypeGate([UserType.ADMIN], 'view data exports')
@@ -11,12 +12,9 @@ export default class DataExportPolicy extends Policy {
1112
}
1213

1314
@UserTypeGate([UserType.ADMIN], 'create data exports')
15+
@EmailConfirmedGate('create data exports')
1416
async post(req: Request): Promise<PolicyResponse> {
1517
const { gameId } = req.body
16-
17-
const user = await this.getUser()
18-
if (!user.emailConfirmed) return new PolicyDenial({ message: 'You need to confirm your email address to create data exports' })
19-
2018
return await this.canAccessGame(gameId)
2119
}
2220
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Request } from 'koa-clay'
2+
import Policy from './policy'
3+
4+
const EmailConfirmedGate = (action: string) => (tar: Policy, _: string, descriptor: PropertyDescriptor) => {
5+
const base = descriptor.value
6+
7+
descriptor.value = async function (...args) {
8+
const req: Request = args[0]
9+
10+
if (!req.ctx.state.user.api) {
11+
const user = await tar.getUser(req)
12+
if (!user.emailConfirmed) req.ctx.throw(403, `You need to confirm your email address to ${action}`)
13+
}
14+
15+
const result = await base.apply(this, args)
16+
return result
17+
}
18+
19+
return descriptor
20+
}
21+
22+
export default EmailConfirmedGate

src/policies/invite.policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Policy from './policy'
22
import { PolicyResponse } from 'koa-clay'
33
import { UserType } from '../entities/user'
44
import UserTypeGate from './user-type-gate'
5+
import EmailConfirmedGate from './email-confirmed-gate'
56

67
export default class InvitePolicy extends Policy {
78
@UserTypeGate([UserType.ADMIN], 'view invites')
@@ -10,6 +11,7 @@ export default class InvitePolicy extends Policy {
1011
}
1112

1213
@UserTypeGate([UserType.ADMIN], 'create invites')
14+
@EmailConfirmedGate('create invites')
1315
async post(): Promise<PolicyResponse> {
1416
return true
1517
}

src/services/api/event-api.service.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import APIService from './api-service'
77
import APIKey from '../../entities/api-key'
88
import PlayerAlias from '../../entities/player-alias'
99
import groupBy from 'lodash.groupby'
10-
import sanitiseProps from '../../lib/props/sanitiseProps'
1110

1211
export default class EventAPIService extends APIService<EventService> {
1312
constructor() {
@@ -66,11 +65,8 @@ export default class EventAPIService extends APIService<EventService> {
6665

6766
if (item.props) {
6867
try {
69-
if (!Array.isArray(item.props)) {
70-
throw new Error('Props must be an array')
71-
}
72-
73-
event.props = sanitiseProps(item.props, true)
68+
if (!Array.isArray(item.props)) throw new Error('Props must be an array')
69+
event.setProps(item.props)
7470
} catch (err) {
7571
errors[i].push(err.message)
7672
}

tests/services/_api/event-api/post.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,42 @@ describe('Event API service - post', () => {
228228

229229
expect(res.body.errors[0]).toStrictEqual(['Props must be an array'])
230230
})
231+
232+
it('should add valid meta props to the player\'s props', async () => {
233+
apiKey.scopes = [APIKeyScope.WRITE_EVENTS]
234+
await (<EntityManager>app.context.em).flush()
235+
token = await createToken(apiKey)
236+
237+
await request(app.callback())
238+
.post(`${baseUrl}`)
239+
.send({
240+
events: [
241+
{ name: 'Equip bow', aliasId: validPlayer.aliases[0].id, timestamp: Date.now(), props: [{ key: 'META_OS', value: 'macOS' }] }
242+
]
243+
})
244+
.auth(token, { type: 'bearer' })
245+
.expect(200)
246+
247+
const player = await (<EntityManager>app.context.em).getRepository(Player).findOne(validPlayer.id, { refresh: true })
248+
expect(player.props).toContainEqual({ key: 'META_OS', value: 'macOS' })
249+
})
250+
251+
it('should strip out event props that start with META_ but aren\'t in the meta props list', async () => {
252+
apiKey.scopes = [APIKeyScope.WRITE_EVENTS]
253+
await (<EntityManager>app.context.em).flush()
254+
token = await createToken(apiKey)
255+
256+
const res = await request(app.callback())
257+
.post(`${baseUrl}`)
258+
.send({
259+
events: [
260+
{ name: 'Equip bow', aliasId: validPlayer.aliases[0].id, timestamp: Date.now(), props: [{ key: 'META_NO_WAY', value: 'true' }, { key: 'META_OS', value: 'macOS' }] }
261+
]
262+
})
263+
.auth(token, { type: 'bearer' })
264+
.expect(200)
265+
266+
expect(res.body.events[0].props).toContainEqual({ key: 'META_OS', value: 'macOS' })
267+
expect(res.body.events[0].props).not.toContainEqual({ key: 'META_NO_WAY', value: 'true' })
268+
})
231269
})

tests/services/api-key/post.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ describe('API key service - post', () => {
6969
.auth(token, { type: 'bearer' })
7070
.expect(403)
7171

72-
expect(res.body).toStrictEqual({ message: 'You need to confirm your email address to do this' })
72+
expect(res.body).toStrictEqual({ message: 'You need to confirm your email address to create API keys' })
7373
})
7474

7575
it('should not create an api key for a non-existent game', async () => {

0 commit comments

Comments
 (0)