diff --git a/src/lib/services/reset-token-service.ts b/src/lib/services/reset-token-service.ts index 42f4c7ab76..efc5a32df8 100644 --- a/src/lib/services/reset-token-service.ts +++ b/src/lib/services/reset-token-service.ts @@ -61,6 +61,10 @@ export default class ResetTokenService { } } + expireExistingTokensForUser = async (userId: number): Promise => { + return this.store.expireExistingTokensForUser(userId); + }; + async isValid(token: string): Promise { let t; try { @@ -109,7 +113,7 @@ export default class ResetTokenService { ): Promise { const token = await this.generateToken(); const expiry = new Date(Date.now() + expiryDelta); - await this.store.expireExistingTokensForUser(tokenUser); + await this.expireExistingTokensForUser(tokenUser); return this.store.insert({ reset_token: token, user_id: tokenUser, diff --git a/src/lib/services/user-service.ts b/src/lib/services/user-service.ts index 8619e38db6..5928e41c76 100644 --- a/src/lib/services/user-service.ts +++ b/src/lib/services/user-service.ts @@ -351,6 +351,7 @@ class UserService { const passwordHash = await bcrypt.hash(password, saltRounds); await this.store.setPasswordHash(userId, passwordHash); await this.sessionService.deleteSessionsForUser(userId); + await this.resetTokenService.expireExistingTokensForUser(userId); } async getUserForToken(token: string): Promise { @@ -388,7 +389,6 @@ class UserService { }); if (allowed) { await this.changePassword(user.id, password); - await this.sessionService.deleteSessionsForUser(user.id); } else { throw new InvalidTokenError(); } diff --git a/src/lib/types/stores/reset-token-store.ts b/src/lib/types/stores/reset-token-store.ts index 19cf4d6c63..f932edf14f 100644 --- a/src/lib/types/stores/reset-token-store.ts +++ b/src/lib/types/stores/reset-token-store.ts @@ -33,5 +33,5 @@ export interface IResetTokenStore extends Store { useToken(token: IResetQuery): Promise; deleteFromQuery(query: IResetTokenQuery): Promise; deleteExpired(): Promise; - expireExistingTokensForUser(user_id: number): Promise; + expireExistingTokensForUser(userId: number): Promise; } diff --git a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts index 8a70ef9413..b42669faa5 100644 --- a/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts +++ b/src/test/e2e/api/auth/reset-password-controller.e2e.test.ts @@ -168,7 +168,7 @@ test('Trying to reset password with same token twice does not work', async () => token, password, }) - .expect(403) + .expect(401) .expect((res) => { expect(res.body.details[0].message).toBeTruthy(); }); @@ -191,7 +191,7 @@ test('Calling validate endpoint with already existing session should destroy ses await request.get('/api/admin/features').expect(200); const url = await resetTokenService.createResetPasswordUrl( user.id, - adminUser.username, + adminUser.username!, ); const relative = getBackendResetUrl(url); @@ -267,3 +267,31 @@ test('Trying to change password to undefined should yield 400 without crashing t }) .expect(400); }); + +test('changing password should expire all active tokens', async () => { + const url = await resetTokenService.createResetPasswordUrl( + user.id, + adminUser.username, + ); + const relative = getBackendResetUrl(url); + + const { + body: { token }, + } = await app.request + .get(relative) + .expect(200) + .expect('Content-Type', /json/); + + await app.request + .post(`/api/admin/user-admin/${user.id}/change-password`) + .send({ password: 'simple123-_ASsad' }) + .expect(200); + + await app.request + .post('/auth/reset/password') + .send({ + token, + password, + }) + .expect(401); +});