Skip to content

Commit 0b4ec3a

Browse files
[FEATURE] Enregistrer la date de dernière connexion dans Authentication method pour les connexions OIDC (PIX-16742)
#11538
2 parents 81a28fd + 950c4b4 commit 0b4ec3a

File tree

8 files changed

+200
-42
lines changed

8 files changed

+200
-42
lines changed

api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ async function authenticateOidcUser({
7777
authenticationMethodRepository,
7878
});
7979

80+
await _updateUserLastConnection({
81+
user,
82+
requestedApplication,
83+
oidcAuthenticationService,
84+
authenticationMethodRepository,
85+
lastUserApplicationConnectionsRepository,
86+
userLoginRepository,
87+
});
88+
8089
const pixAccessToken = oidcAuthenticationService.createAccessToken({ userId: user.id, audience });
8190

8291
let logoutUrlUUID;
@@ -87,18 +96,23 @@ async function authenticateOidcUser({
8796
});
8897
}
8998

90-
await userLoginRepository.updateLastLoggedAt({ userId: user.id });
91-
await lastUserApplicationConnectionsRepository.upsert({
92-
userId: user.id,
93-
application: requestedApplication.applicationName,
94-
lastLoggedAt: new Date(),
95-
});
96-
9799
return { pixAccessToken, logoutUrlUUID, isAuthenticationComplete: true };
98100
}
99101

100102
export { authenticateOidcUser };
101103

104+
async function _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }) {
105+
if (requestedApplication.isPixAdmin) {
106+
const adminMember = await adminMemberRepository.get({ userId: user.id });
107+
if (!adminMember?.hasAccessToAdminScope) {
108+
throw new ForbiddenAccess(
109+
'User does not have the rights to access the application',
110+
'PIX_ADMIN_ACCESS_NOT_ALLOWED',
111+
);
112+
}
113+
}
114+
}
115+
102116
async function _updateAuthenticationMethodWithComplement({
103117
userInfo,
104118
userId,
@@ -111,21 +125,29 @@ async function _updateAuthenticationMethodWithComplement({
111125
sessionContent,
112126
});
113127

114-
return await authenticationMethodRepository.updateAuthenticationComplementByUserIdAndIdentityProvider({
128+
await authenticationMethodRepository.updateAuthenticationComplementByUserIdAndIdentityProvider({
115129
authenticationComplement,
116130
userId,
117131
identityProvider: oidcAuthenticationService.identityProvider,
118132
});
119133
}
120134

121-
async function _assertUserHasAccessToApplication({ requestedApplication, user, adminMemberRepository }) {
122-
if (requestedApplication.isPixAdmin) {
123-
const adminMember = await adminMemberRepository.get({ userId: user.id });
124-
if (!adminMember?.hasAccessToAdminScope) {
125-
throw new ForbiddenAccess(
126-
'User does not have the rights to access the application',
127-
'PIX_ADMIN_ACCESS_NOT_ALLOWED',
128-
);
129-
}
130-
}
135+
async function _updateUserLastConnection({
136+
user,
137+
requestedApplication,
138+
oidcAuthenticationService,
139+
authenticationMethodRepository,
140+
lastUserApplicationConnectionsRepository,
141+
userLoginRepository,
142+
}) {
143+
await userLoginRepository.updateLastLoggedAt({ userId: user.id });
144+
await lastUserApplicationConnectionsRepository.upsert({
145+
userId: user.id,
146+
application: requestedApplication.applicationName,
147+
lastLoggedAt: new Date(),
148+
});
149+
await authenticationMethodRepository.updateLastLoggedAtByIdentityProvider({
150+
userId: user.id,
151+
identityProvider: oidcAuthenticationService.identityProvider,
152+
});
131153
}

api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,43 @@ async function createOidcUser({
7373
authenticationMethodRepository,
7474
});
7575

76+
await _updateUserLastConnection({
77+
userId,
78+
requestedApplication,
79+
oidcAuthenticationService,
80+
authenticationMethodRepository,
81+
lastUserApplicationConnectionsRepository,
82+
userLoginRepository,
83+
});
84+
7685
const accessToken = oidcAuthenticationService.createAccessToken({ userId, audience });
7786

7887
let logoutUrlUUID;
7988
if (oidcAuthenticationService.shouldCloseSession) {
8089
logoutUrlUUID = await oidcAuthenticationService.saveIdToken({ idToken: sessionContent.idToken, userId });
8190
}
8291

92+
return { accessToken, logoutUrlUUID };
93+
}
94+
95+
export { createOidcUser };
96+
97+
async function _updateUserLastConnection({
98+
userId,
99+
requestedApplication,
100+
oidcAuthenticationService,
101+
authenticationMethodRepository,
102+
lastUserApplicationConnectionsRepository,
103+
userLoginRepository,
104+
}) {
83105
await userLoginRepository.updateLastLoggedAt({ userId });
84106
await lastUserApplicationConnectionsRepository.upsert({
85107
userId,
86108
application: requestedApplication.applicationName,
87109
lastLoggedAt: new Date(),
88110
});
89-
90-
return { accessToken, logoutUrlUUID };
111+
await authenticationMethodRepository.updateLastLoggedAtByIdentityProvider({
112+
userId,
113+
identityProvider: oidcAuthenticationService.identityProvider,
114+
});
91115
}
92-
93-
export { createOidcUser };

api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,17 @@ export const reconcileOidcUserForAdmin = async function ({
6464
}),
6565
});
6666

67-
const accessToken = await oidcAuthenticationService.createAccessToken({ userId, audience });
68-
69-
await userLoginRepository.updateLastLoggedAt({ userId });
70-
await lastUserApplicationConnectionsRepository.upsert({
67+
await _updateUserLastConnection({
7168
userId,
72-
application: requestedApplication.applicationName,
73-
lastLoggedAt: new Date(),
69+
requestedApplication,
70+
oidcAuthenticationService,
71+
authenticationMethodRepository,
72+
lastUserApplicationConnectionsRepository,
73+
userLoginRepository,
7474
});
7575

76+
const accessToken = await oidcAuthenticationService.createAccessToken({ userId, audience });
77+
7678
return accessToken;
7779
};
7880

@@ -94,3 +96,23 @@ async function _assertExternalIdentifier({
9496
throw new DifferentExternalIdentifierError();
9597
}
9698
}
99+
100+
async function _updateUserLastConnection({
101+
userId,
102+
requestedApplication,
103+
oidcAuthenticationService,
104+
authenticationMethodRepository,
105+
lastUserApplicationConnectionsRepository,
106+
userLoginRepository,
107+
}) {
108+
await userLoginRepository.updateLastLoggedAt({ userId });
109+
await lastUserApplicationConnectionsRepository.upsert({
110+
userId,
111+
application: requestedApplication.applicationName,
112+
lastLoggedAt: new Date(),
113+
});
114+
await authenticationMethodRepository.updateLastLoggedAtByIdentityProvider({
115+
userId,
116+
identityProvider: oidcAuthenticationService.identityProvider,
117+
});
118+
}

api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ export const reconcileOidcUser = async function ({
6060
}),
6161
});
6262

63+
await _updateUserLastConnection({
64+
userId,
65+
requestedApplication,
66+
oidcAuthenticationService,
67+
authenticationMethodRepository,
68+
lastUserApplicationConnectionsRepository,
69+
userLoginRepository,
70+
});
71+
6372
const accessToken = await oidcAuthenticationService.createAccessToken({ userId, audience });
6473

6574
let logoutUrlUUID;
@@ -70,12 +79,25 @@ export const reconcileOidcUser = async function ({
7079
});
7180
}
7281

82+
return { accessToken, logoutUrlUUID };
83+
};
84+
85+
async function _updateUserLastConnection({
86+
userId,
87+
requestedApplication,
88+
oidcAuthenticationService,
89+
authenticationMethodRepository,
90+
lastUserApplicationConnectionsRepository,
91+
userLoginRepository,
92+
}) {
7393
await userLoginRepository.updateLastLoggedAt({ userId });
7494
await lastUserApplicationConnectionsRepository.upsert({
7595
userId,
7696
application: requestedApplication.applicationName,
7797
lastLoggedAt: new Date(),
7898
});
79-
80-
return { accessToken, logoutUrlUUID };
81-
};
99+
await authenticationMethodRepository.updateLastLoggedAtByIdentityProvider({
100+
userId,
101+
identityProvider: oidcAuthenticationService.identityProvider,
102+
});
103+
}

api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
3636
};
3737
authenticationMethodRepository = {
3838
updateAuthenticationComplementByUserIdAndIdentityProvider: sinon.stub(),
39+
updateLastLoggedAtByIdentityProvider: sinon.stub(),
3940
};
4041
authenticationSessionService = {
4142
save: sinon.stub(),
@@ -294,6 +295,15 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
294295
userId: 10,
295296
identityProvider: oidcAuthenticationService.identityProvider,
296297
});
298+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.have.been.calledWithExactly({
299+
userId: 10,
300+
identityProvider: oidcAuthenticationService.identityProvider,
301+
});
302+
expect(lastUserApplicationConnectionsRepository.upsert).to.have.been.calledWithExactly({
303+
userId: 10,
304+
application: 'app',
305+
lastLoggedAt: sinon.match.instanceOf(Date),
306+
});
297307
});
298308
});
299309

@@ -331,6 +341,15 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
331341
userId: 10,
332342
identityProvider: oidcAuthenticationService.identityProvider,
333343
});
344+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.have.been.calledWithExactly({
345+
userId: 10,
346+
identityProvider: oidcAuthenticationService.identityProvider,
347+
});
348+
expect(lastUserApplicationConnectionsRepository.upsert).to.have.been.calledWithExactly({
349+
userId: 10,
350+
application: 'app',
351+
lastLoggedAt: sinon.match.instanceOf(Date),
352+
});
334353
});
335354
});
336355
});
@@ -365,6 +384,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
365384
};
366385
authenticationMethodRepository = {
367386
updateAuthenticationComplementByUserIdAndIdentityProvider: sinon.stub(),
387+
updateLastLoggedAtByIdentityProvider: sinon.stub(),
368388
};
369389

370390
authenticationSessionService = {
@@ -416,6 +436,15 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
416436
userId: 1,
417437
identityProvider: oidcAuthenticationService.identityProvider,
418438
});
439+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.have.been.calledWithExactly({
440+
userId: 1,
441+
identityProvider: oidcAuthenticationService.identityProvider,
442+
});
443+
expect(lastUserApplicationConnectionsRepository.upsert).to.have.been.calledWithExactly({
444+
userId: 1,
445+
application: 'app',
446+
lastLoggedAt: sinon.match.instanceOf(Date),
447+
});
419448
});
420449

421450
it('returns an access token, the logout url uuid and update the last logged date with the existing external user id', async function () {
@@ -496,6 +525,15 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi
496525
userId: 10,
497526
identityProvider: oidcAuthenticationService.identityProvider,
498527
});
528+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.have.been.calledWithExactly({
529+
userId: 10,
530+
identityProvider: oidcAuthenticationService.identityProvider,
531+
});
532+
expect(lastUserApplicationConnectionsRepository.upsert).to.have.been.calledWithExactly({
533+
userId: 10,
534+
application: 'app',
535+
lastLoggedAt: sinon.match.instanceOf(Date),
536+
});
499537
});
500538
});
501539
});

api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use
1919

2020
authenticationMethodRepository = {
2121
findOneByExternalIdentifierAndIdentityProvider: sinon.stub(),
22+
updateLastLoggedAtByIdentityProvider: sinon.stub(),
2223
};
2324

2425
authenticationSessionService = {
@@ -148,12 +149,21 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use
148149
userToCreateRepository,
149150
authenticationMethodRepository,
150151
});
151-
sinon.assert.calledOnce(oidcAuthenticationService.createAccessToken);
152-
sinon.assert.calledOnce(oidcAuthenticationService.saveIdToken);
153-
sinon.assert.calledOnceWithExactly(userLoginRepository.updateLastLoggedAt, { userId: 10 });
152+
expect(oidcAuthenticationService.createAccessToken).to.have.been.calledOnce;
153+
expect(oidcAuthenticationService.saveIdToken).to.have.been.calledOnce;
154+
expect(userLoginRepository.updateLastLoggedAt).to.have.been.calledWithExactly({ userId: 10 });
154155
expect(result).to.deep.equal({
155156
accessToken: 'accessTokenForExistingExternalUser',
156157
logoutUrlUUID: 'logoutUrlUUID',
157158
});
159+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.have.been.calledWithExactly({
160+
userId: 10,
161+
identityProvider: oidcAuthenticationService.identityProvider,
162+
});
163+
expect(lastUserApplicationConnectionsRepository.upsert).to.have.been.calledWithExactly({
164+
userId: 10,
165+
application: 'app',
166+
lastLoggedAt: sinon.match.instanceOf(Date),
167+
});
158168
});
159169
});

api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc-
1919
const requestedApplication = new RequestedApplication('admin');
2020

2121
beforeEach(function () {
22-
authenticationMethodRepository = { create: sinon.stub(), findOneByUserIdAndIdentityProvider: sinon.stub() };
22+
authenticationMethodRepository = {
23+
create: sinon.stub(),
24+
findOneByUserIdAndIdentityProvider: sinon.stub(),
25+
updateLastLoggedAtByIdentityProvider: sinon.stub(),
26+
};
2327
userRepository = { getByEmail: sinon.stub() };
2428
userLoginRepository = { updateLastLoggedAt: sinon.stub() };
2529
authenticationSessionService = { getByKey: sinon.stub() };
@@ -144,7 +148,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc-
144148
expect(result).to.equal('accessToken');
145149
});
146150

147-
it('saves the last user application connection', async function () {
151+
it('saves the last user connection', async function () {
148152
// given
149153
const email = '[email protected]';
150154
const externalIdentifier = 'external_id';
@@ -182,6 +186,10 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc-
182186
application: 'admin',
183187
lastLoggedAt: sinon.match.instanceOf(Date),
184188
});
189+
expect(authenticationMethodRepository.updateLastLoggedAtByIdentityProvider).to.be.calledWithMatch({
190+
userId,
191+
identityProvider: oidcAuthenticationService.identityProvider,
192+
});
185193
});
186194

187195
context('when authentication key is expired', function () {

0 commit comments

Comments
 (0)