Skip to content

Commit b5206dc

Browse files
committed
add endpoint for toggling verification and extra auth activities
1 parent 03406cd commit b5206dc

File tree

9 files changed

+377
-9
lines changed

9 files changed

+377
-9
lines changed

src/docs/player-auth-api.docs.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,38 @@ const PlayerAuthAPIDocs: APIDocs<PlayerAuthAPIService> = {
237237
}
238238
}
239239
]
240+
},
241+
toggleVerification: {
242+
description: 'Toggle if verification is required for a player account',
243+
params: {
244+
headers: {
245+
'x-talo-player': 'The ID of the player',
246+
'x-talo-alias': 'The ID of the player\'s alias',
247+
'x-talo-session': 'The session token'
248+
},
249+
body: {
250+
currentPassword: 'The current password of the player',
251+
verificationEnabled: 'The new verification status for the player account',
252+
email: 'Required when enabling verification. This is also used for password resets: players without an email cannot reset their password'
253+
}
254+
},
255+
samples: [
256+
{
257+
title: 'Sample request (disabling verification)',
258+
sample: {
259+
currentPassword: 'password',
260+
verificationEnabled: false
261+
}
262+
},
263+
{
264+
title: 'Sample request (enabling verification)',
265+
sample: {
266+
currentPassword: 'password',
267+
email: 'boz@mail.com',
268+
verificationEnabled: true
269+
}
270+
}
271+
]
240272
}
241273
}
242274

src/entities/player-auth-activity.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ export enum PlayerAuthActivityType {
1111
CHANGED_PASSWORD,
1212
CHANGED_EMAIL,
1313
PASSWORD_RESET_REQUESTED,
14-
PASSWORD_RESET_COMPLETED
14+
PASSWORD_RESET_COMPLETED,
15+
VERFICIATION_TOGGLED,
16+
CHANGE_PASSWORD_FAILED,
17+
CHANGE_EMAIL_FAILED,
18+
TOGGLE_VERIFICATION_FAILED
1519
}
1620

1721
@Entity()
@@ -64,6 +68,14 @@ export default class PlayerAuthActivity {
6468
return `A password reset request was made for ${authAlias.identifier}'s account`
6569
case PlayerAuthActivityType.PASSWORD_RESET_COMPLETED:
6670
return `A password reset was completed for ${authAlias.identifier}'s account`
71+
case PlayerAuthActivityType.VERFICIATION_TOGGLED:
72+
return `${authAlias.identifier} toggled verification`
73+
case PlayerAuthActivityType.CHANGE_PASSWORD_FAILED:
74+
return `${authAlias.identifier} failed to change their password`
75+
case PlayerAuthActivityType.CHANGE_EMAIL_FAILED:
76+
return `${authAlias.identifier} failed to change their email`
77+
case PlayerAuthActivityType.TOGGLE_VERIFICATION_FAILED:
78+
return `${authAlias.identifier} failed to toggle verification`
6779
default:
6880
return ''
6981
}

src/entities/player-auth.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export enum PlayerAuthErrorCode {
1414
INVALID_SESSION = 'INVALID_SESSION',
1515
NEW_PASSWORD_MATCHES_CURRENT_PASSWORD = 'NEW_PASSWORD_MATCHES_CURRENT_PASSWORD',
1616
NEW_EMAIL_MATCHES_CURRENT_EMAIL = 'NEW_EMAIL_MATCHES_CURRENT_EMAIL',
17-
PASSWORD_RESET_CODE_INVALID = 'PASSWORD_RESET_CODE_INVALID'
17+
PASSWORD_RESET_CODE_INVALID = 'PASSWORD_RESET_CODE_INVALID',
18+
VERIFICATION_EMAIL_REQUIRED = 'VERIFICATION_EMAIL_REQUIRED'
1819
}
1920

2021
@Entity()

src/policies/api/player-auth-api.policy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,8 @@ export default class PlayerAuthAPIPolicy extends Policy {
3434
async resetPassword(): Promise<PolicyResponse> {
3535
return await this.hasScopes([APIKeyScope.READ_PLAYERS, APIKeyScope.WRITE_PLAYERS])
3636
}
37+
38+
async toggleVerification(): Promise<PolicyResponse> {
39+
return await this.hasScopes([APIKeyScope.READ_PLAYERS, APIKeyScope.WRITE_PLAYERS])
40+
}
3741
}

src/services/api/player-auth-api.service.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ import { PlayerAuthActivityType } from '../../entities/player-auth-activity'
6464
path: '/reset_password',
6565
handler: 'resetPassword',
6666
docs: PlayerAuthAPIDocs.resetPassword
67+
},
68+
{
69+
method: 'PATCH',
70+
path: '/toggle_verification',
71+
handler: 'toggleVerification',
72+
docs: PlayerAuthAPIDocs.toggleVerification
6773
}
6874
])
6975
export default class PlayerAuthAPIService extends APIService {
@@ -214,7 +220,6 @@ export default class PlayerAuthAPIService extends APIService {
214220
createPlayerAuthActivity(req, alias.player, {
215221
type: PlayerAuthActivityType.VERIFICATION_FAILED
216222
})
217-
218223
await em.flush()
219224

220225
req.ctx.throw(403, {
@@ -281,6 +286,14 @@ export default class PlayerAuthAPIService extends APIService {
281286

282287
const passwordMatches = await bcrypt.compare(currentPassword, alias.player.auth.password)
283288
if (!passwordMatches) {
289+
createPlayerAuthActivity(req, alias.player, {
290+
type: PlayerAuthActivityType.CHANGE_PASSWORD_FAILED,
291+
extra: {
292+
errrorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
293+
}
294+
})
295+
await em.flush()
296+
284297
req.ctx.throw(403, {
285298
message: 'Current password is incorrect',
286299
errorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
@@ -289,6 +302,14 @@ export default class PlayerAuthAPIService extends APIService {
289302

290303
const isSamePassword = await bcrypt.compare(newPassword, alias.player.auth.password)
291304
if (isSamePassword) {
305+
createPlayerAuthActivity(req, alias.player, {
306+
type: PlayerAuthActivityType.CHANGE_PASSWORD_FAILED,
307+
extra: {
308+
errrorCode: PlayerAuthErrorCode.NEW_PASSWORD_MATCHES_CURRENT_PASSWORD
309+
}
310+
})
311+
await em.flush()
312+
292313
req.ctx.throw(400, {
293314
message: 'Please choose a different password',
294315
errorCode: PlayerAuthErrorCode.NEW_PASSWORD_MATCHES_CURRENT_PASSWORD
@@ -323,6 +344,14 @@ export default class PlayerAuthAPIService extends APIService {
323344

324345
const passwordMatches = await bcrypt.compare(currentPassword, alias.player.auth.password)
325346
if (!passwordMatches) {
347+
createPlayerAuthActivity(req, alias.player, {
348+
type: PlayerAuthActivityType.CHANGE_EMAIL_FAILED,
349+
extra: {
350+
errrorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
351+
}
352+
})
353+
await em.flush()
354+
326355
req.ctx.throw(403, {
327356
message: 'Current password is incorrect',
328357
errorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
@@ -331,6 +360,14 @@ export default class PlayerAuthAPIService extends APIService {
331360

332361
const isSameEmail = newEmail === alias.player.auth.email
333362
if (isSameEmail) {
363+
createPlayerAuthActivity(req, alias.player, {
364+
type: PlayerAuthActivityType.CHANGE_EMAIL_FAILED,
365+
extra: {
366+
errrorCode: PlayerAuthErrorCode.NEW_EMAIL_MATCHES_CURRENT_EMAIL
367+
}
368+
})
369+
await em.flush()
370+
334371
req.ctx.throw(400, {
335372
message: 'Please choose a different email address',
336373
errorCode: PlayerAuthErrorCode.NEW_EMAIL_MATCHES_CURRENT_EMAIL
@@ -438,4 +475,63 @@ export default class PlayerAuthAPIService extends APIService {
438475
status: 204
439476
}
440477
}
478+
479+
@Validate({
480+
body: ['currentPassword', 'verificationEnabled']
481+
})
482+
@HasPermission(PlayerAuthAPIPolicy, 'toggleVerification')
483+
async toggleVerification(req: Request): Promise<Response> {
484+
const { currentPassword, verificationEnabled, email } = req.body
485+
const em: EntityManager = req.ctx.em
486+
487+
const alias = await em.getRepository(PlayerAlias).findOne(req.ctx.state.currentAliasId, {
488+
populate: ['player.auth']
489+
})
490+
491+
if (verificationEnabled && !alias.player.auth.email && !email) {
492+
createPlayerAuthActivity(req, alias.player, {
493+
type: PlayerAuthActivityType.TOGGLE_VERIFICATION_FAILED,
494+
extra: {
495+
errrorCode: PlayerAuthErrorCode.VERIFICATION_EMAIL_REQUIRED
496+
}
497+
})
498+
await em.flush()
499+
500+
req.ctx.throw(400, {
501+
message: 'An email address is required to enable verification',
502+
errorCode: PlayerAuthErrorCode.VERIFICATION_EMAIL_REQUIRED
503+
})
504+
}
505+
506+
const passwordMatches = await bcrypt.compare(currentPassword, alias.player.auth.password)
507+
if (!passwordMatches) {
508+
createPlayerAuthActivity(req, alias.player, {
509+
type: PlayerAuthActivityType.TOGGLE_VERIFICATION_FAILED,
510+
extra: {
511+
errrorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
512+
}
513+
})
514+
await em.flush()
515+
516+
req.ctx.throw(403, {
517+
message: 'Current password is incorrect',
518+
errorCode: PlayerAuthErrorCode.INVALID_CREDENTIALS
519+
})
520+
}
521+
522+
alias.player.auth.verificationEnabled = Boolean(verificationEnabled)
523+
524+
createPlayerAuthActivity(req, alias.player, {
525+
type: PlayerAuthActivityType.VERFICIATION_TOGGLED,
526+
extra: {
527+
verificationEnabled: alias.player.auth.verificationEnabled
528+
}
529+
})
530+
531+
await em.flush()
532+
533+
return {
534+
status: 204
535+
}
536+
}
441537
}

tests/fixtures/PlayerAuthActivityFactory.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,8 @@ export default class PlayerAuthActivityFactory extends Factory<PlayerAuthActivit
1919
type: casual.random_element([
2020
PlayerAuthActivityType.REGISTERED,
2121
PlayerAuthActivityType.VERIFICATION_STARTED,
22-
PlayerAuthActivityType.VERIFICATION_FAILED,
2322
PlayerAuthActivityType.LOGGED_IN,
24-
PlayerAuthActivityType.LOGGED_OUT,
25-
PlayerAuthActivityType.CHANGED_PASSWORD,
26-
PlayerAuthActivityType.CHANGED_EMAIL,
27-
PlayerAuthActivityType.PASSWORD_RESET_REQUESTED,
28-
PlayerAuthActivityType.PASSWORD_RESET_COMPLETED
23+
PlayerAuthActivityType.LOGGED_OUT
2924
]),
3025
player: await new PlayerFactory([this.game]).state('with talo alias').one()
3126
}

tests/services/_api/player-auth-api/changeEmail.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ describe('Player auth API service - change email', () => {
101101
message: 'Current password is incorrect',
102102
errorCode: 'INVALID_CREDENTIALS'
103103
})
104+
105+
const activity = await (<EntityManager>global.em).getRepository(PlayerAuthActivity).findOne({
106+
type: PlayerAuthActivityType.CHANGE_EMAIL_FAILED,
107+
player: player.id,
108+
extra: {
109+
errrorCode: 'INVALID_CREDENTIALS'
110+
}
111+
})
112+
expect(activity).not.toBeNull()
104113
})
105114

106115
it('should not change a player\'s email if the current email is the same as the new email', async () => {
@@ -132,5 +141,14 @@ describe('Player auth API service - change email', () => {
132141
message: 'Please choose a different email address',
133142
errorCode: 'NEW_EMAIL_MATCHES_CURRENT_EMAIL'
134143
})
144+
145+
const activity = await (<EntityManager>global.em).getRepository(PlayerAuthActivity).findOne({
146+
type: PlayerAuthActivityType.CHANGE_EMAIL_FAILED,
147+
player: player.id,
148+
extra: {
149+
errrorCode: 'NEW_EMAIL_MATCHES_CURRENT_EMAIL'
150+
}
151+
})
152+
expect(activity).not.toBeNull()
135153
})
136154
})

tests/services/_api/player-auth-api/changePassword.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ describe('Player auth API service - change password', () => {
9898
message: 'Current password is incorrect',
9999
errorCode: 'INVALID_CREDENTIALS'
100100
})
101+
102+
const activity = await (<EntityManager>global.em).getRepository(PlayerAuthActivity).findOne({
103+
type: PlayerAuthActivityType.CHANGE_PASSWORD_FAILED,
104+
player: player.id,
105+
extra: {
106+
errrorCode: 'INVALID_CREDENTIALS'
107+
}
108+
})
109+
expect(activity).not.toBeNull()
101110
})
102111

103112
it('should not change a player\'s password if the current password is the same as the new password', async () => {
@@ -129,5 +138,14 @@ describe('Player auth API service - change password', () => {
129138
message: 'Please choose a different password',
130139
errorCode: 'NEW_PASSWORD_MATCHES_CURRENT_PASSWORD'
131140
})
141+
142+
const activity = await (<EntityManager>global.em).getRepository(PlayerAuthActivity).findOne({
143+
type: PlayerAuthActivityType.CHANGE_PASSWORD_FAILED,
144+
player: player.id,
145+
extra: {
146+
errrorCode: 'NEW_PASSWORD_MATCHES_CURRENT_PASSWORD'
147+
}
148+
})
149+
expect(activity).not.toBeNull()
132150
})
133151
})

0 commit comments

Comments
 (0)