Skip to content

Commit 1e55b2e

Browse files
[BUGFIX] Rendre plus résistant le changement de mot de passe d'un utilisateur côté API, pour ne plus renvoyer d’erreur 500 (PIX-14971)
#10413
2 parents 53c913e + 55cc473 commit 1e55b2e

File tree

9 files changed

+256
-149
lines changed

9 files changed

+256
-149
lines changed

api/db/database-builder/factory/build-reset-password-demand.js

+4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ const buildResetPasswordDemand = function ({
55
email = '[email protected]',
66
temporaryKey = 'ABCD12345',
77
used = false,
8+
createdAt,
9+
updatedAt,
810
} = {}) {
911
const values = {
1012
id,
1113
email,
1214
temporaryKey,
1315
used,
16+
createdAt,
17+
updatedAt,
1418
};
1519
return databaseBuffer.pushInsertable({
1620
tableName: 'reset-password-demands',

api/src/identity-access-management/domain/services/reset-password.service.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,36 @@ const generateTemporaryKey = async function () {
1515
);
1616
};
1717

18-
const invalidateOldResetPasswordDemand = function (userEmail, resetPasswordDemandRepository) {
19-
return resetPasswordDemandRepository.markAsBeingUsed(userEmail);
18+
const invalidateOldResetPasswordDemandsByEmail = function (userEmail, resetPasswordDemandRepository) {
19+
return resetPasswordDemandRepository.markAllAsUsedByEmail(userEmail);
2020
};
2121

2222
const verifyDemand = function (temporaryKey, resetPasswordDemandRepository) {
2323
return resetPasswordDemandRepository.findByTemporaryKey(temporaryKey);
2424
};
2525

2626
/**
27-
* @callback hasUserAPasswordResetDemandInProgress
2827
* @param {string} email
2928
* @param {string} temporaryKey
3029
* @param {ResetPasswordDemandRepository} resetPasswordDemandRepository
3130
* @return {Promise<*>}
31+
* @throws PasswordResetDemandNotFoundError
3232
*/
33-
const hasUserAPasswordResetDemandInProgress = function (email, temporaryKey, resetPasswordDemandRepository) {
34-
return resetPasswordDemandRepository.findByUserEmail(email, temporaryKey);
33+
const invalidateResetPasswordDemand = function (email, temporaryKey, resetPasswordDemandRepository) {
34+
return resetPasswordDemandRepository.markAsUsed(email, temporaryKey);
3535
};
3636

3737
/**
3838
* @typedef {Object} ResetPasswordService
3939
* @property generateTemporaryKey
40-
* @property hasUserAPasswordResetDemandInProgress
41-
* @property invalidateOldResetPasswordDemand
40+
* @property invalidateResetPasswordDemand
41+
* @property invalidateOldResetPasswordDemandsByEmail
4242
* @property verifyDemand
4343
*/
4444
const resetPasswordService = {
4545
generateTemporaryKey,
46-
hasUserAPasswordResetDemandInProgress,
47-
invalidateOldResetPasswordDemand,
46+
invalidateResetPasswordDemand,
47+
invalidateOldResetPasswordDemandsByEmail,
4848
verifyDemand,
4949
};
5050
export { resetPasswordService };
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withTransaction } from '../../../shared/domain/DomainTransaction.js';
12
import { UserNotAuthorizedToUpdatePasswordError } from '../../../shared/domain/errors.js';
23

34
/**
@@ -14,7 +15,7 @@ import { UserNotAuthorizedToUpdatePasswordError } from '../../../shared/domain/e
1415
* @return {Promise<void>}
1516
* @throws {UserNotAuthorizedToUpdatePasswordError}
1617
*/
17-
export const updateUserPassword = async function ({
18+
export const updateUserPassword = withTransaction(async function ({
1819
userId,
1920
password,
2021
temporaryKey,
@@ -24,25 +25,20 @@ export const updateUserPassword = async function ({
2425
userRepository,
2526
resetPasswordDemandRepository,
2627
}) {
27-
const hashedPassword = await cryptoService.hashPassword(password);
2828
const user = await userRepository.get(userId);
29-
3029
if (!user.email) {
3130
throw new UserNotAuthorizedToUpdatePasswordError();
3231
}
3332

34-
await resetPasswordService.hasUserAPasswordResetDemandInProgress(
35-
user.email,
36-
temporaryKey,
37-
resetPasswordDemandRepository,
38-
);
33+
await resetPasswordService.invalidateResetPasswordDemand(user.email, temporaryKey, resetPasswordDemandRepository);
3934

35+
const hashedPassword = await cryptoService.hashPassword(password);
4036
await authenticationMethodRepository.updateChangedPassword({
4137
userId: user.id,
4238
hashedPassword,
4339
});
44-
await resetPasswordService.invalidateOldResetPasswordDemand(user.email, resetPasswordDemandRepository);
4540

46-
user.markEmailAsValid();
47-
await userRepository.update(user.mapToDatabaseDto());
48-
};
41+
await resetPasswordService.invalidateOldResetPasswordDemandsByEmail(user.email, resetPasswordDemandRepository);
42+
43+
await userRepository.updateEmailConfirmed(userId);
44+
});

api/src/identity-access-management/infrastructure/repositories/reset-password-demand.repository.js

+22-22
Original file line numberDiff line numberDiff line change
@@ -21,47 +21,46 @@ const create = async function ({ email, temporaryKey }) {
2121

2222
/**
2323
* @param {string} email
24-
*/
25-
const markAsBeingUsed = async function (email) {
26-
const knexConn = DomainTransaction.getConnection();
27-
28-
await knexConn(RESET_PASSWORD_DEMANDS_TABLE_NAME)
29-
.whereRaw('LOWER("email") = LOWER(?)', email)
30-
.update({ used: true, updatedAt: new Date() });
31-
};
32-
33-
/**
3424
* @param {string} temporaryKey
3525
*
36-
* @returns {ResetPasswordDemand} retrieved reset password demand
26+
* @returns {Promise<void>}
27+
* @throws PasswordResetDemandNotFoundError when resetPasswordDemand has been already used or does not exist
3728
*/
38-
const findByTemporaryKey = async function (temporaryKey) {
29+
const markAsUsed = async function (email, temporaryKey) {
3930
const knexConn = DomainTransaction.getConnection();
4031

4132
const resetPasswordDemand = await knexConn(RESET_PASSWORD_DEMANDS_TABLE_NAME)
42-
.select('*')
33+
.whereRaw('LOWER("email") = LOWER(?)', email)
4334
.where({ temporaryKey, used: false })
44-
.first();
35+
.update({ used: true, updatedAt: new Date() });
4536

4637
if (!resetPasswordDemand) {
4738
throw new PasswordResetDemandNotFoundError();
4839
}
49-
50-
return _toDomain(resetPasswordDemand);
5140
};
5241

5342
/**
5443
* @param {string} email
44+
*/
45+
const markAllAsUsedByEmail = async function (email) {
46+
const knexConn = DomainTransaction.getConnection();
47+
48+
await knexConn(RESET_PASSWORD_DEMANDS_TABLE_NAME)
49+
.whereRaw('LOWER("email") = LOWER(?)', email)
50+
.where({ used: false })
51+
.update({ used: true, updatedAt: new Date() });
52+
};
53+
54+
/**
5555
* @param {string} temporaryKey
5656
*
5757
* @returns {ResetPasswordDemand} retrieved reset password demand
5858
*/
59-
const findByUserEmail = async function (email, temporaryKey) {
59+
const findByTemporaryKey = async function (temporaryKey) {
6060
const knexConn = DomainTransaction.getConnection();
6161

6262
const resetPasswordDemand = await knexConn(RESET_PASSWORD_DEMANDS_TABLE_NAME)
6363
.select('*')
64-
.whereRaw('LOWER("email") = LOWER(?)', email)
6564
.where({ temporaryKey, used: false })
6665
.first();
6766

@@ -83,18 +82,19 @@ const removeAllByEmail = async function (email) {
8382

8483
/**
8584
* @typedef {Object} ResetPasswordDemandRepository
85+
* @property {function} markAsUsed
8686
* @property {function} create
8787
* @property {function} deleteByUserEmail
8888
* @property {function} findByTemporaryKey
89-
* @property {function} findByUserEmail
90-
* @property {function} markAsBeingUsed
89+
* @property {function} getByUserEmail
90+
* @property {function} markAllAsUsedByEmail
9191
*/
9292
const resetPasswordDemandRepository = {
93+
markAsUsed,
9394
create,
9495
removeAllByEmail,
9596
findByTemporaryKey,
96-
findByUserEmail,
97-
markAsBeingUsed,
97+
markAllAsUsedByEmail,
9898
};
9999

100100
export { resetPasswordDemandRepository };

api/src/identity-access-management/infrastructure/repositories/user.repository.js

+8
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,12 @@ const updateEmail = async function ({ id, email }) {
266266
return new User(updatedUserEmail);
267267
};
268268

269+
const updateEmailConfirmed = async function (userId) {
270+
const knexConn = DomainTransaction.getConnection();
271+
const updatedAt = new Date();
272+
await knexConn('users').where({ id: userId }).update({ emailConfirmedAt: updatedAt, updatedAt });
273+
};
274+
269275
const updateUserDetailsForAdministration = async function ({ id, userAttributes }, { preventUpdatedAt } = {}) {
270276
const knexConn = DomainTransaction.getConnection();
271277

@@ -435,6 +441,7 @@ const updateLastDataProtectionPolicySeenAt = async function ({ userId }) {
435441
* @property {function} isUsernameAvailable
436442
* @property {function} update
437443
* @property {function} updateEmail
444+
* @property {function} updateEmailConfirmed
438445
* @property {function} updateHasSeenAssessmentInstructionsToTrue
439446
* @property {function} updateHasSeenChallengeTooltip
440447
* @property {function} updateHasSeenNewDashboardInfoToTrue
@@ -467,6 +474,7 @@ export {
467474
isUsernameAvailable,
468475
update,
469476
updateEmail,
477+
updateEmailConfirmed,
470478
updateHasSeenAssessmentInstructionsToTrue,
471479
updateHasSeenChallengeTooltip,
472480
updateHasSeenNewDashboardInfoToTrue,

0 commit comments

Comments
 (0)