Skip to content

Commit aa08c99

Browse files
nyablopradel
authored andcommitted
[@accounts/password] Add invalidateAllSessionsAfterPasswordReset and invalidateAllSessionsAfterPasswordChanged options (#773)
* invalidateAllSessionsAfterPasswordReset and invalidateAllSessionsAfterPasswordChanged options * upd test: changePassword -> changePassword and invalidate all sessions * added a test: call invalidateAllSessions * accounts-password: upd tests and snaps
1 parent d4db308 commit aa08c99

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

packages/password/__tests__/__snapshots__/accounts-password.ts.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ exports[`AccountsPassword authenticate throws when hash not found 1`] = `"User h
2020

2121
exports[`AccountsPassword authenticate throws when user not found 1`] = `"User not found"`;
2222

23-
exports[`AccountsPassword changePassword throws when new password is invalid 1`] = `"Invalid password"`;
23+
exports[`AccountsPassword changePassword and invalidate all sessions call invalidateAllSessions 1`] = `
24+
Array [
25+
undefined,
26+
]
27+
`;
28+
29+
exports[`AccountsPassword changePassword and invalidate all sessions throws when new password is invalid 1`] = `"Invalid password"`;
2430

2531
exports[`AccountsPassword createUser throws on required fields 1`] = `"Username or Email is required"`;
2632

packages/password/__tests__/accounts-password.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,20 +402,53 @@ describe('AccountsPassword', () => {
402402
});
403403
});
404404

405-
describe('changePassword', () => {
405+
describe('changePassword and invalidate all sessions', () => {
406+
const tmpAccountsPassword = new AccountsPassword({
407+
invalidateAllSessionsAfterPasswordChanged: true,
408+
});
406409
const validUser = {
407410
emails: [{ address: '[email protected]', verified: true }],
408411
};
409412

410413
it('throws when new password is invalid', async () => {
411414
try {
412-
await password.changePassword('userId', 'old-password', null as any);
415+
await tmpAccountsPassword.changePassword('userId', 'old-password', null as any);
413416
throw new Error();
414417
} catch (err) {
415418
expect(err.message).toMatchSnapshot();
416419
}
417420
});
418421

422+
it('call invalidateAllSessions', async () => {
423+
const userId = 'id';
424+
const setPassword = jest.fn(() => Promise.resolve('user'));
425+
const findUserById = jest.fn(() => Promise.resolve(validUser));
426+
const invalidateAllSessions = jest.fn(() => Promise.resolve());
427+
const findPasswordHash = jest.fn(() => Promise.resolve());
428+
tmpAccountsPassword.setStore({
429+
setPassword,
430+
findUserById,
431+
findPasswordHash,
432+
invalidateAllSessions,
433+
} as any);
434+
const prepareMail = jest.fn(() => Promise.resolve());
435+
const sanitizeUser = jest.fn(() => Promise.resolve());
436+
const sendMail = jest.fn(() => Promise.resolve());
437+
tmpAccountsPassword.server = {
438+
...server,
439+
prepareMail,
440+
options: { sendMail },
441+
sanitizeUser,
442+
} as any;
443+
set(tmpAccountsPassword.server, 'options.emailTemplates', {});
444+
jest
445+
.spyOn(tmpAccountsPassword, 'passwordAuthenticator' as any)
446+
.mockImplementation(() => Promise.resolve(validUser));
447+
await tmpAccountsPassword.changePassword(userId, 'old-password', 'new-password');
448+
expect(invalidateAllSessions.mock.calls[0]).toMatchSnapshot();
449+
(tmpAccountsPassword as any).passwordAuthenticator.mockRestore();
450+
});
451+
419452
it('call passwordAuthenticator and this.db.setPassword', async () => {
420453
const userId = 'id';
421454
const setPassword = jest.fn(() => Promise.resolve('user'));

packages/password/src/accounts-password.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ export interface AccountsPasswordOptions {
5858
* Default to false.
5959
*/
6060
returnTokensAfterResetPassword?: boolean;
61+
/**
62+
* Invalidate existing sessions after password has been reset
63+
* Default to true.
64+
*/
65+
invalidateAllSessionsAfterPasswordReset?: boolean;
66+
/**
67+
* Invalidate existing sessions after password has been changed
68+
* Default to false.
69+
*/
70+
invalidateAllSessionsAfterPasswordChanged?: boolean;
6171
/**
6272
* Function that will validate the user object during `createUser`.
6373
* The user returned from this function will be directly inserted in the database so be careful when you whitelist the fields,
@@ -92,6 +102,8 @@ const defaultOptions = {
92102
passwordEnrollTokenExpiration: 2592000000,
93103
notifyUserAfterPasswordChanged: true,
94104
returnTokensAfterResetPassword: false,
105+
invalidateAllSessionsAfterPasswordReset: true,
106+
invalidateAllSessionsAfterPasswordChanged: false,
95107
validateEmail(email?: string): boolean {
96108
return !isEmpty(trim(email)) && isEmail(email);
97109
},
@@ -271,7 +283,9 @@ export default class AccountsPassword implements AuthenticationService {
271283
}
272284

273285
// Changing the password should invalidate existing sessions
274-
await this.db.invalidateAllSessions(user.id);
286+
if (this.options.invalidateAllSessionsAfterPasswordReset) {
287+
await this.db.invalidateAllSessions(user.id);
288+
}
275289

276290
if (this.options.notifyUserAfterPasswordChanged) {
277291
const address = user.emails && user.emails[0].address;
@@ -331,6 +345,10 @@ export default class AccountsPassword implements AuthenticationService {
331345

332346
this.server.getHooks().emit(ServerHooks.ChangePasswordSuccess, user);
333347

348+
if (this.options.invalidateAllSessionsAfterPasswordChanged) {
349+
await this.db.invalidateAllSessions(user.id);
350+
}
351+
334352
if (this.options.notifyUserAfterPasswordChanged) {
335353
const address = user.emails && user.emails[0].address;
336354
if (!address) {

0 commit comments

Comments
 (0)