Skip to content

Commit 13897ab

Browse files
committed
add endpoint for retrieving player auth activities
1 parent 515e593 commit 13897ab

31 files changed

+507
-29
lines changed

src/entities/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import PlayerAuthActivity from './player-auth-activity'
12
import PlayerAuth from './player-auth'
23
import GameFeedback from './game-feedback'
34
import Invite from './invite'
@@ -33,6 +34,7 @@ import PlayerGroup from './player-group'
3334
import GameSecret from './game-secret'
3435

3536
export default [
37+
PlayerAuthActivity,
3638
PlayerAuth,
3739
GameFeedback,
3840
GameSecret,

src/entities/player-auth-activity.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/mysql'
2+
import Player from './player'
3+
import PlayerAlias, { PlayerAliasService } from './player-alias'
4+
5+
export enum PlayerAuthActivityType {
6+
REGISTERED,
7+
VERIFICATION_STARTED,
8+
VERIFICATION_FAILED,
9+
LOGGED_IN,
10+
LOGGED_OUT,
11+
CHANGED_PASSWORD,
12+
CHANGED_EMAIL,
13+
PASSWORD_RESET_REQUESTED,
14+
PASSWORD_RESET_COMPLETED
15+
}
16+
17+
@Entity()
18+
export default class PlayerAuthActivity {
19+
@PrimaryKey()
20+
id: number
21+
22+
@ManyToOne(() => Player, { eager: true })
23+
player: Player
24+
25+
@Enum(() => PlayerAuthActivityType)
26+
type: PlayerAuthActivityType
27+
28+
@Property({ type: 'json' })
29+
extra: {
30+
[key: string]: unknown
31+
} = {}
32+
33+
@Property()
34+
createdAt: Date = new Date()
35+
36+
constructor(player: Player) {
37+
this.player = player
38+
}
39+
40+
private getAuthAlias(): PlayerAlias {
41+
return this.player.aliases.find((alias) => alias.service === PlayerAliasService.TALO)
42+
}
43+
44+
/* v8 ignore start */
45+
private getActivity(): string {
46+
const authAlias = this.getAuthAlias()
47+
48+
switch (this.type) {
49+
case PlayerAuthActivityType.REGISTERED:
50+
return `${authAlias.identifier} created their account`
51+
case PlayerAuthActivityType.VERIFICATION_STARTED:
52+
return `${authAlias.identifier} started verification`
53+
case PlayerAuthActivityType.VERIFICATION_FAILED:
54+
return `${authAlias.identifier} failed verification`
55+
case PlayerAuthActivityType.LOGGED_IN:
56+
return `${authAlias.identifier} logged in`
57+
case PlayerAuthActivityType.LOGGED_OUT:
58+
return `${authAlias.identifier} logged out`
59+
case PlayerAuthActivityType.CHANGED_PASSWORD:
60+
return `${authAlias.identifier} changed their password`
61+
case PlayerAuthActivityType.CHANGED_EMAIL:
62+
return `${authAlias.identifier} changed their email`
63+
case PlayerAuthActivityType.PASSWORD_RESET_REQUESTED:
64+
return `A password reset request was made for ${authAlias.identifier}'s account`
65+
case PlayerAuthActivityType.PASSWORD_RESET_COMPLETED:
66+
return `A password reset was completed for ${authAlias.identifier}'s account`
67+
default:
68+
return ''
69+
}
70+
}
71+
/* v8 ignore stop */
72+
73+
toJSON() {
74+
return {
75+
id: this.id,
76+
type: this.type,
77+
description: this.getActivity(),
78+
extra: this.extra,
79+
createdAt: this.createdAt
80+
}
81+
}
82+
}

src/lib/logging/createGameActivity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { EntityManager } from '@mikro-orm/mysql'
22
import GameActivity from '../../entities/game-activity'
33

4-
export default async function createGameActivity(em: EntityManager, data: Partial<GameActivity>): Promise<GameActivity> {
4+
export default function createGameActivity(em: EntityManager, data: Partial<GameActivity>): GameActivity {
55
const activity = new GameActivity(data.game, data.user)
66
activity.type = data.type
77
activity.extra = data.extra ?? {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import PlayerAuthActivity from '../../entities/player-auth-activity'
2+
import Player from '../../entities/player'
3+
import { Request } from 'koa-clay'
4+
import { EntityManager } from '@mikro-orm/mysql'
5+
6+
export default function createPlayerAuthActivity(
7+
req: Request,
8+
player: Player,
9+
data: Pick<Partial<PlayerAuthActivity>, 'type' | 'extra'>
10+
): PlayerAuthActivity {
11+
const em: EntityManager = req.ctx.em
12+
13+
const activity = new PlayerAuthActivity(player)
14+
activity.type = data.type
15+
activity.extra = {
16+
...(data.extra ?? {}),
17+
userAgent: req.headers['user-agent'],
18+
ip: req.ctx.request.ip
19+
}
20+
21+
em.persist(activity)
22+
23+
return activity
24+
}

src/migrations/.snapshot-gs_dev.json

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,95 @@
10491049
},
10501050
"nativeEnums": {}
10511051
},
1052+
{
1053+
"columns": {
1054+
"id": {
1055+
"name": "id",
1056+
"type": "int",
1057+
"unsigned": true,
1058+
"autoincrement": true,
1059+
"primary": true,
1060+
"nullable": false,
1061+
"mappedType": "integer"
1062+
},
1063+
"player_id": {
1064+
"name": "player_id",
1065+
"type": "varchar(255)",
1066+
"unsigned": false,
1067+
"autoincrement": false,
1068+
"primary": false,
1069+
"nullable": false,
1070+
"mappedType": "string"
1071+
},
1072+
"type": {
1073+
"name": "type",
1074+
"type": "tinyint",
1075+
"unsigned": false,
1076+
"autoincrement": false,
1077+
"primary": false,
1078+
"nullable": false,
1079+
"mappedType": "enum"
1080+
},
1081+
"extra": {
1082+
"name": "extra",
1083+
"type": "json",
1084+
"unsigned": false,
1085+
"autoincrement": false,
1086+
"primary": false,
1087+
"nullable": false,
1088+
"mappedType": "json"
1089+
},
1090+
"created_at": {
1091+
"name": "created_at",
1092+
"type": "datetime",
1093+
"unsigned": false,
1094+
"autoincrement": false,
1095+
"primary": false,
1096+
"nullable": false,
1097+
"length": 0,
1098+
"mappedType": "datetime"
1099+
}
1100+
},
1101+
"name": "player_auth_activity",
1102+
"indexes": [
1103+
{
1104+
"columnNames": [
1105+
"player_id"
1106+
],
1107+
"composite": false,
1108+
"keyName": "player_auth_activity_player_id_index",
1109+
"constraint": false,
1110+
"primary": false,
1111+
"unique": false
1112+
},
1113+
{
1114+
"keyName": "PRIMARY",
1115+
"columnNames": [
1116+
"id"
1117+
],
1118+
"composite": false,
1119+
"constraint": true,
1120+
"primary": true,
1121+
"unique": true
1122+
}
1123+
],
1124+
"checks": [],
1125+
"foreignKeys": {
1126+
"player_auth_activity_player_id_foreign": {
1127+
"constraintName": "player_auth_activity_player_id_foreign",
1128+
"columnNames": [
1129+
"player_id"
1130+
],
1131+
"localTableName": "player_auth_activity",
1132+
"referencedColumnNames": [
1133+
"id"
1134+
],
1135+
"referencedTableName": "player",
1136+
"updateRule": "cascade"
1137+
}
1138+
},
1139+
"nativeEnums": {}
1140+
},
10521141
{
10531142
"columns": {
10541143
"id": {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Migration } from '@mikro-orm/migrations'
2+
3+
export class CreatePlayerAuthActivityTable extends Migration {
4+
5+
async up(): Promise<void> {
6+
this.addSql('create table `player_auth_activity` (`id` int unsigned not null auto_increment primary key, `player_id` varchar(255) not null, `type` tinyint not null, `extra` json not null, `created_at` datetime not null) default character set utf8mb4 engine = InnoDB;')
7+
this.addSql('alter table `player_auth_activity` add index `player_auth_activity_player_id_index`(`player_id`);')
8+
9+
this.addSql('alter table `player_auth_activity` add constraint `player_auth_activity_player_id_foreign` foreign key (`player_id`) references `player` (`id`) on update cascade;')
10+
}
11+
12+
async down(): Promise<void> {
13+
this.addSql('drop table if exists `player_auth_activity`;')
14+
}
15+
16+
}

src/migrations/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { AddAPIKeyLastUsedAtColumn } from './20230205220925AddAPIKeyLastUsedAtCo
2626
import { CreateGameFeedbackAndCategoryTables } from './20240606165637CreateGameFeedbackAndCategoryTables'
2727
import { AddAPIKeyUpdatedAtColumn } from './20240614122547AddAPIKeyUpdatedAtColumn'
2828
import { CreatePlayerAuthTable } from './20240628155142CreatePlayerAuthTable'
29+
import { CreatePlayerAuthActivityTable } from './20240725183402CreatePlayerAuthActivityTable'
2930

3031
export default [
3132
{
@@ -139,5 +140,9 @@ export default [
139140
{
140141
name: 'CreatePlayerAuthTable',
141142
class: CreatePlayerAuthTable
143+
},
144+
{
145+
name: 'CreatePlayerAuthActivityTable',
146+
class: CreatePlayerAuthActivityTable
142147
}
143148
]

src/policies/player.policy.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,14 @@ export default class PlayerPolicy extends Policy {
6262

6363
return await this.canAccessGame(player.game.id)
6464
}
65+
66+
@UserTypeGate([UserType.ADMIN], 'view player auth activities')
67+
async getAuthActivities(req: Request): Promise<PolicyResponse> {
68+
const { id } = req.params
69+
70+
const player = await this.getPlayer(id)
71+
if (!player) return new PolicyDenial({ message: 'Player not found' }, 404)
72+
73+
return await this.canAccessGame(player.game.id)
74+
}
6575
}

src/services/api-key.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default class APIKeyService extends Service {
5050
const apiKey = new APIKey(req.ctx.state.game, req.ctx.state.user)
5151
apiKey.scopes = scopes
5252

53-
await createGameActivity(em, {
53+
createGameActivity(em, {
5454
user: req.ctx.state.user,
5555
game: req.ctx.state.game,
5656
type: GameActivityType.API_KEY_CREATED,
@@ -97,7 +97,7 @@ export default class APIKeyService extends Service {
9797

9898
const token = await createToken(em, apiKey)
9999

100-
await createGameActivity(em, {
100+
createGameActivity(em, {
101101
user: req.ctx.state.user,
102102
game: req.ctx.state.game,
103103
type: GameActivityType.API_KEY_REVOKED,
@@ -139,7 +139,7 @@ export default class APIKeyService extends Service {
139139

140140
const token = await createToken(em, apiKey)
141141

142-
await createGameActivity(em, {
142+
createGameActivity(em, {
143143
user: req.ctx.state.user,
144144
game: req.ctx.state.game,
145145
type: GameActivityType.API_KEY_UPDATED,

0 commit comments

Comments
 (0)