Skip to content

Commit

Permalink
[TECH] Migrer la route PATCH /api/account-recovery vers src/identity-…
Browse files Browse the repository at this point in the history
…access-management (PIX-12761)

 #9309
  • Loading branch information
pix-service-auto-merge authored Jun 20, 2024
2 parents c43fd8f + cb4debe commit 8cee08a
Show file tree
Hide file tree
Showing 23 changed files with 353 additions and 309 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { usecases } from '../../domain/usecases/index.js';
import { DomainTransaction } from '../../infrastructure/DomainTransaction.js';
import * as studentInformationForAccountRecoverySerializer from '../../infrastructure/serializers/jsonapi/student-information-for-account-recovery-serializer.js';

const sendEmailForAccountRecovery = async function (
Expand All @@ -26,25 +25,7 @@ const checkAccountRecoveryDemand = async function (
return dependencies.studentInformationForAccountRecoverySerializer.serializeAccountRecovery(studentInformation);
};

const updateUserAccountFromRecoveryDemand = async function (request, h) {
const temporaryKey = request.payload.data.attributes['temporary-key'];
const password = request.payload.data.attributes.password;

await DomainTransaction.execute(async (domainTransaction) => {
await usecases.updateUserForAccountRecovery({
password,
temporaryKey,
domainTransaction,
});
});

return h.response().code(204);
};

const accountRecoveryController = {
export const accountRecoveryController = {
sendEmailForAccountRecovery,
checkAccountRecoveryDemand,
updateUserAccountFromRecoveryDemand,
};

export { accountRecoveryController };
29 changes: 0 additions & 29 deletions api/lib/application/account-recovery/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import JoiDate from '@joi/date';
import BaseJoi from 'joi';
const Joi = BaseJoi.extend(JoiDate);
import XRegExp from 'xregexp';
const inePattern = new RegExp('^[0-9]{9}[a-zA-Z]{2}$');
const inaPattern = new RegExp('^[0-9]{10}[a-zA-Z]{1}$');

import { config } from '../../config.js';
import { accountRecoveryController } from './account-recovery-controller.js';

const { passwordValidationPattern } = config.account;

const register = async function (server) {
server.route([
{
Expand Down Expand Up @@ -57,31 +53,6 @@ const register = async function (server) {
tags: ['api', 'account-recovery'],
},
},
{
method: 'PATCH',
path: '/api/account-recovery',
config: {
auth: false,
handler: accountRecoveryController.updateUserAccountFromRecoveryDemand,
validate: {
payload: Joi.object({
data: {
attributes: {
'temporary-key': Joi.string().min(32).required(),
password: Joi.string().pattern(XRegExp(passwordValidationPattern)).required(),
},
},
}),
options: {
allowUnknown: true,
},
},
notes: [
'- Permet de mettre à jour les informations d’un utilisateur via à une demande de récupération de compte.',
],
tags: ['api', 'account-recovery'],
},
},
]);
};

Expand Down
2 changes: 1 addition & 1 deletion api/lib/domain/models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { UserSavedTutorial } from '../../../src/devcomp/domain/models/UserSavedT
import { Answer } from '../../../src/evaluation/domain/models/Answer.js';
import { CompetenceEvaluation } from '../../../src/evaluation/domain/models/CompetenceEvaluation.js';
import { Progression } from '../../../src/evaluation/domain/models/Progression.js';
import { AccountRecoveryDemand } from '../../../src/identity-access-management/domain/models/AccountRecoveryDemand.js';
import { AuthenticationMethod } from '../../../src/identity-access-management/domain/models/AuthenticationMethod.js';
import { User } from '../../../src/identity-access-management/domain/models/User.js';
import { UserLogin } from '../../../src/identity-access-management/domain/models/UserLogin.js';
Expand Down Expand Up @@ -45,7 +46,6 @@ import { Examiner } from '../../../src/shared/domain/models/Examiner.js';
import { OrganizationInvitation } from '../../../src/team/domain/models/OrganizationInvitation.js';
import { CampaignParticipant } from './../../../src/prescription/campaign-participation/domain/models/CampaignParticipant.js';
import { CampaignParticipation } from './../../../src/prescription/campaign-participation/domain/models/CampaignParticipation.js';
import { AccountRecoveryDemand } from './AccountRecoveryDemand.js';
import { AnswerCollectionForScoring } from './AnswerCollectionForScoring.js';
import { Area } from './Area.js';
import { Authentication } from './Authentication.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import crypto from 'node:crypto';

import { AccountRecoveryDemand } from '../../models/AccountRecoveryDemand.js';
import { AccountRecoveryDemand } from '../../../../src/identity-access-management/domain/models/AccountRecoveryDemand.js';

const sendEmailForAccountRecovery = async function ({
studentInformation,
Expand Down
4 changes: 2 additions & 2 deletions api/lib/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import { OidcAuthenticationServiceRegistry } from '../../../src/identity-access-
import { pixAuthenticationService } from '../../../src/identity-access-management/domain/services/pix-authentication-service.js';
import { refreshTokenService } from '../../../src/identity-access-management/domain/services/refresh-token-service.js';
import * as resetPasswordService from '../../../src/identity-access-management/domain/services/reset-password.service.js';
import { scoAccountRecoveryService } from '../../../src/identity-access-management/domain/services/sco-account-recovery.service.js';
import { accountRecoveryDemandRepository } from '../../../src/identity-access-management/infrastructure/repositories/account-recovery-demand.repository.js';
import * as authenticationMethodRepository from '../../../src/identity-access-management/infrastructure/repositories/authentication-method.repository.js';
import * as oidcProviderRepository from '../../../src/identity-access-management/infrastructure/repositories/oidc-provider-repository.js';
import * as resetPasswordDemandRepository from '../../../src/identity-access-management/infrastructure/repositories/reset-password-demand.repository.js';
Expand Down Expand Up @@ -112,7 +114,6 @@ import * as sessionPublicationService from '../../domain/services/session-public
import * as verifyCertificateCodeService from '../../domain/services/verify-certificate-code-service.js';
import * as disabledPoleEmploiNotifier from '../../infrastructure/externals/pole-emploi/disabled-pole-emploi-notifier.js';
import * as poleEmploiNotifier from '../../infrastructure/externals/pole-emploi/pole-emploi-notifier.js';
import * as accountRecoveryDemandRepository from '../../infrastructure/repositories/account-recovery-demand-repository.js';
import * as attachableTargetProfileRepository from '../../infrastructure/repositories/attachable-target-profiles-repository.js';
import * as badgeAcquisitionRepository from '../../infrastructure/repositories/badge-acquisition-repository.js';
import * as badgeForCalculationRepository from '../../infrastructure/repositories/badge-for-calculation-repository.js';
Expand Down Expand Up @@ -167,7 +168,6 @@ import * as userEmailRepository from '../../infrastructure/repositories/user-ema
import * as userOrganizationsForAdminRepository from '../../infrastructure/repositories/user-organizations-for-admin-repository.js';
import * as writeCsvUtils from '../../infrastructure/utils/csv/write-csv-utils.js';
import * as learningContentConversionService from '../services/learning-content/learning-content-conversion-service.js';
import * as scoAccountRecoveryService from '../services/sco-account-recovery-service.js';
import * as userReconciliationService from '../services/user-reconciliation-service.js';
import * as organizationCreationValidator from '../validators/organization-creation-validator.js';
import * as organizationValidator from '../validators/organization-with-tags-and-target-profiles-script.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { usecases } from '../../domain/usecases/index.js';

const updateUserAccountFromRecoveryDemand = async function (request, h) {
const temporaryKey = request.payload.data.attributes['temporary-key'];
const password = request.payload.data.attributes.password;

await DomainTransaction.execute(async (domainTransaction) => {
await usecases.updateUserForAccountRecovery({
password,
temporaryKey,
domainTransaction,
});
});

return h.response().code(204);
};

export const accountRecoveryController = { updateUserAccountFromRecoveryDemand };
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Joi from 'joi';
import XRegExp from 'xregexp';

import { config } from '../../../shared/config.js';
import { accountRecoveryController } from './account-recovery.controller.js';

const { passwordValidationPattern } = config.account;

export const accountRecoveryRoutes = [
{
method: 'PATCH',
path: '/api/account-recovery',
config: {
auth: false,
handler: (request, h) => accountRecoveryController.updateUserAccountFromRecoveryDemand(request, h),
validate: {
payload: Joi.object({
data: {
attributes: {
'temporary-key': Joi.string().min(32).required(),
password: Joi.string().pattern(XRegExp(passwordValidationPattern)).required(),
},
},
}),
options: {
allowUnknown: true,
},
},
notes: [
'- Permet de mettre à jour les informations d’un utilisateur via à une demande de récupération de compte.',
],
tags: ['identity-access-management', 'api', 'account-recovery'],
},
},
];
2 changes: 2 additions & 0 deletions api/src/identity-access-management/application/routes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { accountRecoveryRoutes } from './account-recovery/account-recovery.route.js';
import { anonymizationAdminRoutes } from './anonymization/anonymization.admin.route.js';
import { oidcProviderAdminRoutes } from './oidc-provider/oidc-provider.admin.route.js';
import { oidcProviderRoutes } from './oidc-provider/oidc-provider.route.js';
Expand All @@ -9,6 +10,7 @@ import { userRoutes } from './user/user.route.js';

const register = async function (server) {
server.route([
...accountRecoveryRoutes,
...anonymizationAdminRoutes,
...oidcProviderAdminRoutes,
...oidcProviderRoutes,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
class AccountRecoveryDemand {
export class AccountRecoveryDemand {
/**
* @param {{
* id: string,
* userId: string,
* organizationLearnerId: string,
* oldEmail: string,
* newEmail: string,
* temporaryKey: string,
* used: boolean,
* createdAt: string|Date,
* }} data
*/
constructor({ id, userId, organizationLearnerId, oldEmail, newEmail, temporaryKey, used, createdAt } = {}) {
this.id = id;
this.organizationLearnerId = organizationLearnerId;
Expand All @@ -10,5 +22,3 @@ class AccountRecoveryDemand {
this.createdAt = createdAt;
}
}

export { AccountRecoveryDemand };
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import lodash from 'lodash';

import { config } from '../../config.js';
import {
AccountRecoveryDemandExpired,
MultipleOrganizationLearnersWithDifferentNationalStudentIdError,
UserHasAlreadyLeftSCO,
UserNotFoundError,
} from '../errors.js';
} from '../../../../lib/domain/errors.js';
import { config } from '../../../shared/config.js';

const { uniqBy } = lodash;

const { features } = config;

async function retrieveOrganizationLearner({
Expand Down Expand Up @@ -67,6 +66,8 @@ async function retrieveAndValidateAccountRecoveryDemand({
return { id, userId, newEmail, organizationLearnerId };
}

export const scoAccountRecoveryService = { retrieveAndValidateAccountRecoveryDemand, retrieveOrganizationLearner };

function _demandHasExpired(demandCreationDate) {
const minutesInADay = 60 * 24;
const lifetimeInMinutes = parseInt(features.scoAccountRecoveryKeyLifetimeMinutes) || minutesInADay;
Expand Down Expand Up @@ -104,5 +105,3 @@ async function _checkIfThereAreMultipleUserForTheSameAccount({ userId, organizat
throw new MultipleOrganizationLearnersWithDifferentNationalStudentIdError();
}
}

export { retrieveAndValidateAccountRecoveryDemand, retrieveOrganizationLearner };
4 changes: 4 additions & 0 deletions api/src/identity-access-management/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as adminMemberRepository from '../../../shared/infrastructure/repositor
import * as userLoginRepository from '../../../shared/infrastructure/repositories/user-login-repository.js';
import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
import { accountRecoveryDemandRepository } from '../../infrastructure/repositories/account-recovery-demand.repository.js';
import * as authenticationMethodRepository from '../../infrastructure/repositories/authentication-method.repository.js';
import { oidcProviderRepository } from '../../infrastructure/repositories/oidc-provider-repository.js';
import * as resetPasswordDemandRepository from '../../infrastructure/repositories/reset-password-demand.repository.js';
Expand All @@ -24,11 +25,13 @@ import { authenticationSessionService } from '../services/authentication-session
import { pixAuthenticationService } from '../services/pix-authentication-service.js';
import { refreshTokenService } from '../services/refresh-token-service.js';
import * as resetPasswordService from '../services/reset-password.service.js';
import { scoAccountRecoveryService } from '../services/sco-account-recovery.service.js';
import { addOidcProviderValidator } from '../validators/add-oidc-provider.validator.js';

const path = dirname(fileURLToPath(import.meta.url));

const repositories = {
accountRecoveryDemandRepository,
adminMemberRepository,
authenticationMethodRepository,
campaignRepository,
Expand All @@ -47,6 +50,7 @@ const services = {
pixAuthenticationService,
refreshTokenService,
resetPasswordService,
scoAccountRecoveryService,
tokenService,
userService,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { NON_OIDC_IDENTITY_PROVIDERS } from '../../../../src/identity-access-management/domain/constants/identity-providers.js';
import { AuthenticationMethod } from '../../../../src/identity-access-management/domain/models/AuthenticationMethod.js';
import { NON_OIDC_IDENTITY_PROVIDERS } from '../constants/identity-providers.js';
import { AuthenticationMethod } from '../models/AuthenticationMethod.js';

const updateUserForAccountRecovery = async function ({
/**
* @param {{
* password: string,
* temporaryKey: string,
* accountRecoveryDemandRepository: AccountRecoveryDemandRepository,
* authenticationMethodRepository: AuthenticationMethodRepository,
* userRepository: UserRepository,
* cryptoService: CryptoService,
* scoAccountRecoveryService: ScoAccountRecoveryService,
* domainTransaction: DomainTransaction,
* }} params
* @return {Promise<void>}
*/
export const updateUserForAccountRecovery = async function ({
password,
temporaryKey,
userRepository,
domainTransaction,
authenticationMethodRepository,
accountRecoveryDemandRepository,
scoAccountRecoveryService,
cryptoService,
domainTransaction,
}) {
const { userId, newEmail } = await scoAccountRecoveryService.retrieveAndValidateAccountRecoveryDemand({
temporaryKey,
Expand Down Expand Up @@ -60,5 +73,3 @@ const updateUserForAccountRecovery = async function ({
});
await accountRecoveryDemandRepository.markAsBeingUsed(temporaryKey, domainTransaction);
};

export { updateUserForAccountRecovery };
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import _ from 'lodash';

import { knex } from '../../../db/knex-database-connection.js';
import { NotFoundError } from '../../domain/errors.js';
import { knex } from '../../../../db/knex-database-connection.js';
import { NotFoundError } from '../../../../lib/domain/errors.js';
import { DomainTransaction } from '../../../../lib/infrastructure/DomainTransaction.js';
import { AccountRecoveryDemand } from '../../domain/models/AccountRecoveryDemand.js';
import { DomainTransaction } from '../DomainTransaction.js';

const _toDomain = (accountRecoveryDemandDTO) => {
return new AccountRecoveryDemand(accountRecoveryDemandDTO);
Expand Down Expand Up @@ -50,4 +50,4 @@ const markAsBeingUsed = async function (temporaryKey, { knexTransaction } = Doma
return query;
};

export { findByTemporaryKey, findByUserId, markAsBeingUsed, save };
export const accountRecoveryDemandRepository = { findByTemporaryKey, findByUserId, markAsBeingUsed, save };
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createServer, databaseBuilder, expect, knex } from '../../../test-helper.js';
import { createServer, databaseBuilder, expect, knex } from '../../../../test-helper.js';

describe('Acceptance | Route | Account-recovery', function () {
describe('Acceptance | Identity Access Management | Application | Route | account-recovery', function () {
describe('PATCH /api/account-recovery', function () {
context('when user has pix authentication method', function () {
it("should proceed to the account recover by changing user's password and email", async function () {
it("proceeds to the account recover by changing user's password and email", async function () {
// given
const server = await createServer();
const userId = databaseBuilder.factory.buildUser.withRawPassword({
Expand Down Expand Up @@ -50,7 +50,7 @@ describe('Acceptance | Route | Account-recovery', function () {
});

context('when user has no pix authentication method', function () {
it('should proceed to the account recover by create pix authentication method', async function () {
it('proceeds to the account recover by create pix authentication method', async function () {
// given
const server = await createServer();
const userWithGarAuthenticationMethod = databaseBuilder.factory.buildUser.withoutPixAuthenticationMethod({
Expand Down
Loading

0 comments on commit 8cee08a

Please sign in to comment.