Skip to content

Commit e24cecd

Browse files
refactor(api): move route from lib to src
1 parent f2440dc commit e24cecd

File tree

8 files changed

+213
-146
lines changed

8 files changed

+213
-146
lines changed

api/lib/application/passwords/index.js

-36
This file was deleted.

api/lib/routes.js

-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import * as certificationCenters from './application/certification-centers/index
66
import * as frameworks from './application/frameworks/index.js';
77
import * as memberships from './application/memberships/index.js';
88
import * as organizations from './application/organizations/index.js';
9-
import * as passwords from './application/passwords/index.js';
109
import * as scoOrganizationLearners from './application/sco-organization-learners/index.js';
1110
import * as targetProfiles from './application/target-profiles/index.js';
1211
import * as users from './application/users/index.js';
@@ -19,7 +18,6 @@ const routes = [
1918
healthcheck,
2019
memberships,
2120
organizations,
22-
passwords,
2321
scoOrganizationLearners,
2422
targetProfiles,
2523
frameworks,

api/src/identity-access-management/application/password/password.route.js

+25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import Joi from 'joi';
2+
import XRegExp from 'xregexp';
23

4+
import { config } from '../../../shared/config.js';
35
import { passwordController } from './password.controller.js';
46

7+
const { passwordValidationPattern } = config.account;
8+
59
export const passwordRoutes = [
610
{
711
method: 'POST',
@@ -40,4 +44,25 @@ export const passwordRoutes = [
4044
tags: ['api', 'passwords'],
4145
},
4246
},
47+
{
48+
method: 'POST',
49+
path: '/api/expired-password-updates',
50+
config: {
51+
auth: false,
52+
handler: (request, h) => passwordController.updateExpiredPassword(request, h),
53+
validate: {
54+
payload: Joi.object({
55+
data: {
56+
attributes: {
57+
'password-reset-token': Joi.string().required(),
58+
'new-password': Joi.string().pattern(XRegExp(passwordValidationPattern)).required(),
59+
},
60+
type: Joi.string(),
61+
},
62+
}),
63+
},
64+
notes: ['Route publique', 'Cette route permet de mettre à jour un mot de passe expiré'],
65+
tags: ['api', 'passwords'],
66+
},
67+
},
4368
];

api/tests/identity-access-management/acceptance/application/password/password.route.test.js

+115-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { resetPasswordService } from '../../../../../src/identity-access-management/domain/services/reset-password.service.js';
22
import { config } from '../../../../../src/shared/config.js';
3+
import { tokenService } from '../../../../../src/shared/domain/services/token-service.js';
34
import { createServer, databaseBuilder, expect } from '../../../../test-helper.js';
45

56
describe('Acceptance | Identity Access Management | Application | Route | password', function () {
@@ -97,23 +98,120 @@ describe('Acceptance | Identity Access Management | Application | Route | passwo
9798
});
9899

99100
describe('GET /api/password-reset-demands/{temporaryKey}', function () {
100-
it('returns 200 http status code', async function () {
101-
// given
102-
const temporaryKey = await resetPasswordService.generateTemporaryKey();
103-
const options = {
104-
method: 'GET',
105-
url: `/api/password-reset-demands/${temporaryKey}`,
106-
};
107-
const userId = databaseBuilder.factory.buildUser({ email }).id;
108-
databaseBuilder.factory.buildAuthenticationMethod.withPixAsIdentityProviderAndHashedPassword({ userId });
109-
databaseBuilder.factory.buildResetPasswordDemand({ temporaryKey, email });
110-
await databaseBuilder.commit();
111-
112-
// when
113-
const response = await server.inject(options);
114-
115-
// then
116-
expect(response.statusCode).to.equal(200);
101+
const options = {
102+
method: 'GET',
103+
url: null,
104+
};
105+
context('when temporaryKey is not valid', function () {
106+
it('replies with 401 status code', async function () {
107+
// given
108+
options.url = '/api/password-reset-demands/invalid-temporary-key';
109+
110+
// when
111+
const response = await server.inject(options);
112+
113+
// then
114+
expect(response.statusCode).to.equal(401);
115+
});
116+
});
117+
context('when temporaryKey is valid', function () {
118+
let temporaryKey;
119+
120+
beforeEach(async function () {
121+
temporaryKey = await resetPasswordService.generateTemporaryKey();
122+
options.url = `/api/password-reset-demands/${temporaryKey}`;
123+
});
124+
125+
context('when temporaryKey is not linked to a reset password demand', function () {
126+
it('replies with 404 status code', async function () {
127+
// when
128+
const response = await server.inject(options);
129+
130+
// then
131+
expect(response.statusCode).to.equal(404);
132+
});
133+
});
134+
135+
context('when temporaryKey is linked to a password reset demand', function () {
136+
beforeEach(async function () {
137+
databaseBuilder.factory.buildUser({ email });
138+
databaseBuilder.factory.buildResetPasswordDemand({ email, temporaryKey });
139+
140+
await databaseBuilder.commit();
141+
});
142+
143+
it('replies with 200 status code', async function () {
144+
// when
145+
const response = await server.inject(options);
146+
147+
// then
148+
expect(response.statusCode).to.equal(200);
149+
});
150+
});
151+
});
152+
});
153+
154+
describe('POST /api/expired-password-updates', function () {
155+
context('Success cases', function () {
156+
it('returns 201 HTTP status code', async function () {
157+
// given
158+
const user = databaseBuilder.factory.buildUser.withRawPassword({
159+
shouldChangePassword: true,
160+
});
161+
await databaseBuilder.commit();
162+
const passwordResetToken = tokenService.createPasswordResetToken(user.id);
163+
164+
const options = {
165+
method: 'POST',
166+
url: '/api/expired-password-updates',
167+
payload: {
168+
data: {
169+
attributes: {
170+
'password-reset-token': passwordResetToken,
171+
'new-password': 'Password02',
172+
},
173+
},
174+
},
175+
};
176+
177+
// when
178+
const response = await server.inject(options);
179+
180+
// then
181+
expect(response.statusCode).to.equal(201);
182+
});
183+
});
184+
185+
context('Error cases', function () {
186+
context('when shouldChangePassword is false', function () {
187+
it('responds 403 HTTP status code', async function () {
188+
// given
189+
const user = databaseBuilder.factory.buildUser.withRawPassword({
190+
shouldChangePassword: false,
191+
});
192+
await databaseBuilder.commit();
193+
const passwordResetToken = tokenService.createPasswordResetToken(user.id);
194+
195+
const options = {
196+
method: 'POST',
197+
url: '/api/expired-password-updates',
198+
payload: {
199+
data: {
200+
attributes: {
201+
'password-reset-token': passwordResetToken,
202+
'new-password': 'Password02',
203+
},
204+
},
205+
},
206+
};
207+
208+
// when
209+
const response = await server.inject(options);
210+
211+
// then
212+
expect(response.statusCode).to.equal(403);
213+
});
214+
});
117215
});
118216
});
119217
});

api/tests/identity-access-management/integration/application/password/password.route.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,27 @@ describe('Integration | Identity Access Management | Application | Route | passw
5252
expect(response.statusCode).to.equal(200);
5353
});
5454
});
55+
56+
describe('POST /api/expired-password-updates', function () {
57+
it('returns 201 http status code', async function () {
58+
// given
59+
const method = 'POST';
60+
const url = '/api/expired-password-updates';
61+
const payload = {
62+
data: {
63+
attributes: {
64+
'password-reset-token': 'PASSWORD_RESET_TOKEN',
65+
'new-password': 'Password123',
66+
},
67+
},
68+
};
69+
sinon.stub(passwordController, 'updateExpiredPassword').callsFake((request, h) => h.response().created());
70+
71+
// when
72+
const response = await httpTestServer.request(method, url, payload);
73+
74+
// then
75+
expect(response.statusCode).to.equal(201);
76+
});
77+
});
5578
});

api/tests/identity-access-management/unit/application/password/password.route.test.js

+50
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,54 @@ describe('Unit | Identity Access Management | Application | Route | password', f
5151
});
5252
});
5353
});
54+
55+
describe('POST /api/expired-password-updates', function () {
56+
const method = 'POST';
57+
const url = '/api/expired-password-updates';
58+
59+
it('returns 201 http status code', async function () {
60+
// given
61+
sinon.stub(passwordController, 'updateExpiredPassword').callsFake((request, h) => h.response().created());
62+
const httpTestServer = new HttpTestServer();
63+
await httpTestServer.register(routesUnderTest);
64+
65+
const payload = {
66+
data: {
67+
attributes: {
68+
'password-reset-token': 'PASSWORD_RESET_TOKEN',
69+
'new-password': 'Password123',
70+
},
71+
},
72+
};
73+
74+
// when
75+
const response = await httpTestServer.request(method, url, payload);
76+
77+
// then
78+
expect(response.statusCode).to.equal(201);
79+
});
80+
81+
context('When the payload has the wrong format or no passwordResetToken or newPassword is provided.', function () {
82+
it('returns 400 http status code', async function () {
83+
// given
84+
const httpTestServer = new HttpTestServer();
85+
await httpTestServer.register(routesUnderTest);
86+
87+
const payload = {
88+
data: {
89+
attributes: {
90+
'password-reset-token': 'PASSWORD_RESET_TOKEN',
91+
newPassword: null,
92+
},
93+
},
94+
};
95+
96+
// when
97+
const response = await httpTestServer.request(method, url, payload);
98+
99+
// then
100+
expect(response.statusCode).to.equal(400);
101+
});
102+
});
103+
});
54104
});

api/tests/integration/application/passwords/index_test.js

-36
This file was deleted.

0 commit comments

Comments
 (0)