From 11b363c7aa99655b456cb783866ec3dc654c0f73 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 26 Feb 2025 11:02:12 +0100 Subject: [PATCH] feat(api): save last user application connection in #authenticateOidcUser --- .../oidc-provider/oidc-provider.controller.js | 2 ++ .../authenticate-oidc-user.usecase.js | 9 ++++++++ .../application/oidc-provider.route.test.js | 9 ++++++-- .../oidc-provider.controller.test.js | 2 ++ .../authenticate-oidc-user.usecase.test.js | 21 +++++++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js index 3b751b6db7d..1fbbce464b0 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.controller.js @@ -14,6 +14,7 @@ import { getForwardedOrigin, RequestedApplication } from '../../infrastructure/u async function authenticateOidcUser(request, h) { const { code, state, iss, identityProvider: identityProviderCode, target } = request.deserializedPayload; const origin = getForwardedOrigin(request.headers); + const requestedApplication = RequestedApplication.fromOrigin(origin); const sessionState = request.yar.get('state', true); const nonce = request.yar.get('nonce', true); @@ -32,6 +33,7 @@ async function authenticateOidcUser(request, h) { nonce, sessionState, audience: origin, + requestedApplication, }); if (result.isAuthenticationComplete) { diff --git a/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js index 9e42021f12b..f4fc4ec413c 100644 --- a/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js @@ -17,6 +17,8 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; * @param {AuthenticationMethodRepository} params.authenticationMethodRepository * @param {UserLoginRepository} params.userLoginRepository * @param {UserRepository} params.userRepository + * @param {LastUserApplicationConnectionsRepository} params.LastUserApplicationConnectionsRepository, + * @param {RequestedApplication} params.RequestedApplication, * @return {Promise<{isAuthenticationComplete: boolean, givenName: string, familyName: string, authenticationKey: string, email: string}|{isAuthenticationComplete: boolean, pixAccessToken: string, logoutUrlUUID: string}>} */ async function authenticateOidcUser({ @@ -34,6 +36,8 @@ async function authenticateOidcUser({ authenticationMethodRepository, userLoginRepository, userRepository, + lastUserApplicationConnectionsRepository, + requestedApplication, }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProviderCode); @@ -86,6 +90,11 @@ async function authenticateOidcUser({ } await userLoginRepository.updateLastLoggedAt({ userId: user.id }); + await lastUserApplicationConnectionsRepository.upsert({ + userId: user.id, + application: requestedApplication.applicationName, + lastLoggedAt: new Date(), + }); return { pixAccessToken, logoutUrlUUID, isAuthenticationComplete: true }; } diff --git a/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js b/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js index ae136956753..2b362fae244 100644 --- a/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/oidc-provider.route.test.js @@ -185,12 +185,14 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p lastName: 'Doe', }, }; + const headers = generateAuthenticatedUserRequestHeaders(); + headers.cookie = cookies[0]; // when const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers: { cookie: cookies[0] }, + headers, payload, }); @@ -331,11 +333,14 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p // const getAccessTokenRequest = nock(settings.poleEmploi.tokenUrl).post('/').reply(200, getAccessTokenResponse); oidcExampleNetProvider.client.callback.resolves(getAccessTokenResponse); + const headers = generateAuthenticatedUserRequestHeaders(); + headers.cookie = cookies[0]; + // when const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers: { cookie: cookies[0] }, + headers, payload, }); diff --git a/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js b/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js index c40d8e769fb..4e70aa0bea6 100644 --- a/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js +++ b/api/tests/identity-access-management/unit/application/oidc-provider.controller.test.js @@ -14,6 +14,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr const identityProvider = 'OIDC_EXAMPLE_NET'; const pixAccessToken = 'pixAccessToken'; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); let request; @@ -60,6 +61,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr state: identityProviderState, iss, audience, + requestedApplication, }); expect(request.yar.commit).to.have.been.calledOnce; }); diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js index 07a8f36813b..2b5eaa79be1 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-oidc-user.usecase.test.js @@ -2,6 +2,7 @@ import * as appMessages from '../../../../../src/authorization/domain/constants. import { POLE_EMPLOI } from '../../../../../src/identity-access-management/domain/constants/oidc-identity-providers.js'; import { AuthenticationMethod } from '../../../../../src/identity-access-management/domain/models/AuthenticationMethod.js'; import { authenticateOidcUser } from '../../../../../src/identity-access-management/domain/usecases/authenticate-oidc-user.usecase.js'; +import { RequestedApplication } from '../../../../../src/identity-access-management/infrastructure/utils/network.js'; import { ForbiddenAccess } from '../../../../../src/shared/domain/errors.js'; import { AdminMember } from '../../../../../src/shared/domain/models/AdminMember.js'; import { AuthenticationSessionContent } from '../../../../../src/shared/domain/models/AuthenticationSessionContent.js'; @@ -15,9 +16,11 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userRepository; let adminMemberRepository; let userLoginRepository; + let lastUserApplicationConnectionsRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); beforeEach(function () { oidcAuthenticationService = { @@ -47,6 +50,9 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userLoginRepository = { updateLastLoggedAt: sinon.stub().resolves(), }; + lastUserApplicationConnectionsRepository = { + upsert: sinon.stub().resolves(), + }; }); context('check access by target', function () { @@ -276,6 +282,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + lastUserApplicationConnectionsRepository, + requestedApplication, }); // then @@ -310,6 +318,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + requestedApplication, + lastUserApplicationConnectionsRepository, }); // then @@ -332,8 +342,10 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userRepository; let userLoginRepository; let oidcAuthenticationServiceRegistry; + let lastUserApplicationConnectionsRepository; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; const audience = 'https://app.pix.fr'; + const requestedApplication = new RequestedApplication('app'); beforeEach(function () { oidcAuthenticationService = { @@ -362,6 +374,9 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userLoginRepository = { updateLastLoggedAt: sinon.stub().resolves(), }; + lastUserApplicationConnectionsRepository = { + upsert: sinon.stub().resolves(), + }; }); context('when user has an account', function () { @@ -387,6 +402,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, audience, + requestedApplication, + lastUserApplicationConnectionsRepository, }); // then @@ -424,6 +441,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, audience, + requestedApplication, + lastUserApplicationConnectionsRepository, }); // then @@ -462,6 +481,8 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi userRepository, userLoginRepository, audience, + requestedApplication, + lastUserApplicationConnectionsRepository, }); // then