From 5986ba3489fc88166f83afbf72c09408a044e74a Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 15 Jan 2025 14:49:56 +0100 Subject: [PATCH 01/17] feat(api): add audience to RefreshToken --- .../domain/models/RefreshToken.js | 7 ++++--- .../unit/domain/models/RefreshToken.test.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/api/src/identity-access-management/domain/models/RefreshToken.js b/api/src/identity-access-management/domain/models/RefreshToken.js index 1268bddbba7..9daed2216e9 100644 --- a/api/src/identity-access-management/domain/models/RefreshToken.js +++ b/api/src/identity-access-management/domain/models/RefreshToken.js @@ -5,17 +5,18 @@ import { config } from '../../../shared/config.js'; const SEPARATOR = ':'; export class RefreshToken { - constructor({ userId, scope, source, value }) { + constructor({ userId, scope, source, value, audience }) { this.userId = userId; this.scope = scope; this.source = source; this.value = value; + this.audience = audience; } - static generate({ userId, scope, source }) { + static generate({ userId, scope, source, audience }) { const uuid = crypto.randomUUID(); const value = [userId, scope, uuid].filter(Boolean).join(SEPARATOR); - return new RefreshToken({ userId, scope, source, value }); + return new RefreshToken({ userId, scope, source, value, audience }); } get expirationDelaySeconds() { diff --git a/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js b/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js index fa73f4d2a94..5bdcab54e5b 100644 --- a/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js +++ b/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js @@ -16,13 +16,20 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu describe('#constructor', function () { it('builds a refresh token model', function () { // when - const refreshToken = new RefreshToken({ userId: 'userId!', scope: 'scope!', source: 'source!', value: 'token!' }); + const refreshToken = new RefreshToken({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + value: 'token!', + audience: 'audience!', + }); // then expect(refreshToken.value).to.equal('token!'); expect(refreshToken.userId).to.equal('userId!'); expect(refreshToken.scope).to.equal('scope!'); expect(refreshToken.source).to.equal('source!'); + expect(refreshToken.audience).to.equal('audience!'); expect(refreshToken.expirationDelaySeconds).to.equal(defaultRefreshTokenLifespanMs / 1000); }); @@ -40,6 +47,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu scope: 'pix-orga', source: 'source!', value: 'token!', + audience: 'audience!', }); // then @@ -49,7 +57,12 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu context('when no scope', function () { it('sets the default expiration delay', function () { // when - const refreshToken = new RefreshToken({ userId: 'userId!', source: 'source!', value: 'token!' }); + const refreshToken = new RefreshToken({ + userId: 'userId!', + source: 'source!', + value: 'token!', + audience: 'audience!', + }); // then expect(refreshToken.expirationDelaySeconds).to.equal(defaultRefreshTokenLifespanMs / 1000); @@ -68,6 +81,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu userId: 'userId!', scope: 'scope!', source: 'source!', + audience: 'audience!', }); // then From cb23be6ec958a8321a60fea086e73d4503f163f8 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 15 Jan 2025 14:59:05 +0100 Subject: [PATCH 02/17] feat(api): add audience to refresh token in #authenticateUser --- .../domain/usecases/authenticate-user.js | 3 +- .../domain/usecases/authenticate-user_test.js | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/api/src/identity-access-management/domain/usecases/authenticate-user.js b/api/src/identity-access-management/domain/usecases/authenticate-user.js index 091c3ed872f..e1fa6c9a6f5 100644 --- a/api/src/identity-access-management/domain/usecases/authenticate-user.js +++ b/api/src/identity-access-management/domain/usecases/authenticate-user.js @@ -15,6 +15,7 @@ const authenticateUser = async function ({ userRepository, userLoginRepository, adminMemberRepository, + audience, }) { try { const foundUser = await pixAuthenticationService.getUserByUsernameAndPassword({ @@ -30,7 +31,7 @@ const authenticateUser = async function ({ await _checkUserAccessScope(scope, foundUser, adminMemberRepository); - const refreshToken = RefreshToken.generate({ userId: foundUser.id, scope, source }); + const refreshToken = RefreshToken.generate({ userId: foundUser.id, scope, source, audience }); await refreshTokenRepository.save({ refreshToken }); const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser(foundUser.id, source); diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js index 1a7ec2f26e6..a6bf438acec 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js @@ -51,6 +51,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ORGA.SCOPE; const user = new User({ email: userEmail, memberships: [] }); + const audience = 'audience'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); // when @@ -62,6 +63,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userRepository, userLoginRepository, refreshTokenRepository, + audience, }); // then @@ -75,6 +77,8 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); + const audience = 'audience'; + pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); adminMemberRepository.get.withArgs({ userId: user.id }).resolves(); @@ -88,6 +92,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, adminMemberRepository, refreshTokenRepository, + audience, }); // then @@ -99,6 +104,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); + const audience = 'audience'; const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -123,6 +129,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userLoginRepository, adminMemberRepository, refreshTokenRepository, + audience, }); // then @@ -135,6 +142,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = PIX_ADMIN.SCOPE; const source = 'pix'; const user = new User({ id: 123, email: userEmail }); + const audience = 'audience'; const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -150,7 +158,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); adminMemberRepository.get.withArgs({ userId: user.id }).resolves(adminMember); - const refreshToken = { value: 'jwt.refresh.token', userId: user.id, scope }; + const refreshToken = { value: 'jwt.refresh.token', userId: user.id, scope, audience }; sinon.stub(RefreshToken, 'generate').returns(refreshToken); const accessToken = ''; @@ -172,6 +180,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u adminMemberRepository, refreshTokenRepository, tokenService, + audience, }); // then @@ -192,6 +201,8 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const accessToken = 'jwt.access.token'; const expirationDelaySeconds = 1; const source = 'pix'; + const audience = 'audience'; + const user = domainBuilder.buildUser({ email: userEmail, certificationCenterMemberships: [Symbol('certificationCenterMembership')], @@ -199,7 +210,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); - const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope }; + const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope, audience }; sinon.stub(RefreshToken, 'generate').returns(refreshToken); tokenService.createAccessTokenFromUser @@ -217,6 +228,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u refreshTokenRepository, userRepository, userLoginRepository, + audience, }); // then @@ -237,10 +249,11 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail }); + const audience = 'audience'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); - const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope }; + const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope, audience }; sinon.stub(RefreshToken, 'generate').returns(refreshToken); tokenService.createAccessTokenFromUser.withArgs(user.id, source).resolves({ accessToken, expirationDelaySeconds }); @@ -256,6 +269,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u tokenService, userRepository, userLoginRepository, + audience, }); // then @@ -273,6 +287,8 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; + const audience = 'audience'; + const user = domainBuilder.buildUser({ email: userEmail }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); @@ -289,6 +305,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u tokenService, userRepository, userLoginRepository, + audience, }); // then @@ -299,6 +316,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const unknownUserEmail = 'unknown_user_email@example.net'; pixAuthenticationService.getUserByUsernameAndPassword.rejects(new UserNotFoundError()); + const audience = 'audience'; // when const error = await catchErr(authenticateUser)({ @@ -307,6 +325,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userRepository, userLoginRepository, refreshTokenRepository, + audience, }); // then @@ -316,6 +335,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u it('should rejects an error when given password does not match the found user’s one', async function () { // given pixAuthenticationService.getUserByUsernameAndPassword.rejects(new MissingOrInvalidCredentialsError()); + const audience = 'audience'; // when const error = await catchErr(authenticateUser)({ @@ -324,6 +344,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u userRepository, userLoginRepository, refreshTokenRepository, + audience, }); // then @@ -334,6 +355,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u it('should throw UserShouldChangePasswordError', async function () { // given const tokenService = { createPasswordResetToken: sinon.stub() }; + const audience = 'audience'; const user = domainBuilder.buildUser({ username: 'jean.neymar2008' }); const authenticationMethod = domainBuilder.buildAuthenticationMethod.withPixAsIdentityProviderAndRawPassword({ @@ -361,6 +383,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u pixAuthenticationService, refreshTokenRepository, tokenService, + audience, }); // then @@ -377,6 +400,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail, locale: 'fr-FR' }); + const audience = 'audience'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); tokenService.createAccessTokenFromUser.resolves({ accessToken, expirationDelaySeconds }); @@ -392,6 +416,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u tokenService, userRepository, userLoginRepository, + audience, }); // then @@ -407,6 +432,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; + const audience = 'audience'; const user = domainBuilder.buildUser({ email: userEmail, locale: null }); const setLocaleIfNotAlreadySetStub = sinon.stub(user, 'setLocaleIfNotAlreadySet'); @@ -425,6 +451,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u refreshTokenRepository, userRepository, userLoginRepository, + audience, }); // then @@ -439,6 +466,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; + const audience = 'audience'; const user = domainBuilder.buildUser({ email: userEmail, locale: undefined }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); @@ -456,6 +484,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u tokenService, userRepository, userLoginRepository, + audience, }); // then From 057736d877394b35f16243d159fb5624693ac01b Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 15 Jan 2025 15:03:09 +0100 Subject: [PATCH 03/17] feat(api): save audience in refresh token via repository --- .../repositories/refresh-token.repository.js | 4 +- .../refresh-token.repository.test.js | 77 ++++++++++++++++--- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/api/src/identity-access-management/infrastructure/repositories/refresh-token.repository.js b/api/src/identity-access-management/infrastructure/repositories/refresh-token.repository.js index d42190aa791..d96a089ea9b 100644 --- a/api/src/identity-access-management/infrastructure/repositories/refresh-token.repository.js +++ b/api/src/identity-access-management/infrastructure/repositories/refresh-token.repository.js @@ -41,11 +41,11 @@ async function findAllByUserId({ userId }) { * @return {Promise} */ async function save({ refreshToken }) { - const { value, userId, scope, source, expirationDelaySeconds } = refreshToken; + const { value, userId, scope, source, expirationDelaySeconds, audience } = refreshToken; await refreshTokenTemporaryStorage.save({ key: value, - value: { type: 'refresh_token', userId, scope, source }, + value: { type: 'refresh_token', userId, scope, source, audience }, expirationDelaySeconds, }); diff --git a/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js b/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js index e78406f22b2..ea6d34cac44 100644 --- a/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js +++ b/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js @@ -15,10 +15,20 @@ describe('Integration | Identity Access Management | Infrastructure | Repository describe('#findByToken', function () { it('finds refresh token data for token', async function () { // given - const refreshToken = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken }); - const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken2 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); // when @@ -32,13 +42,28 @@ describe('Integration | Identity Access Management | Infrastructure | Repository describe('#findAllByUserId', function () { it('finds all refresh token data for an user id', async function () { // given - const refreshToken = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken }); - const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken2 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); - const refreshToken3 = RefreshToken.generate({ userId: 'userId2!', scope: 'scope!', source: 'source!' }); + const refreshToken3 = RefreshToken.generate({ + userId: 'userId2!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken3 }); // when @@ -52,7 +77,12 @@ describe('Integration | Identity Access Management | Infrastructure | Repository describe('#save', function () { it('saves a refresh token', async function () { // given - const refreshToken = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); // when await refreshTokenRepository.save({ refreshToken }); @@ -66,9 +96,19 @@ describe('Integration | Identity Access Management | Infrastructure | Repository describe('#revokeByToken', function () { it('revokes a refresh token', async function () { // given - const refreshToken1 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken1 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken1 }); - const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken2 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); // when @@ -83,11 +123,26 @@ describe('Integration | Identity Access Management | Infrastructure | Repository describe('#revokeAllByUserId', function () { it('revokes all refresh tokens for a user ID', async function () { // given - const refreshToken1 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken1 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken1 }); - const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!' }); + const refreshToken2 = RefreshToken.generate({ + userId: 'userId!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); - const refreshToken3 = RefreshToken.generate({ userId: 'userId2!', scope: 'scope!', source: 'source!' }); + const refreshToken3 = RefreshToken.generate({ + userId: 'userId2!', + scope: 'scope!', + source: 'source!', + audience: 'audience!', + }); await refreshTokenRepository.save({ refreshToken: refreshToken3 }); // when From d67b83a6add9b0cb2e728092c0e85e4e384b4a48 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 15 Jan 2025 15:16:22 +0100 Subject: [PATCH 04/17] feat(api): get audience from request headers --- .../application/token/token.controller.js | 12 +++++++++++- .../acceptance/application/token.route.test.js | 12 ++++++++++++ .../unit/application/token.controller.test.js | 9 +++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/api/src/identity-access-management/application/token/token.controller.js b/api/src/identity-access-management/application/token/token.controller.js index c977927f9ce..79770f10231 100644 --- a/api/src/identity-access-management/application/token/token.controller.js +++ b/api/src/identity-access-management/application/token/token.controller.js @@ -1,5 +1,6 @@ import { tokenService } from '../../../shared/domain/services/token-service.js'; import { usecases } from '../../domain/usecases/index.js'; +import { getForwardedOrigin } from '../../infrastructure/utils/network.js'; const authenticateAnonymousUser = async function (request, h) { const { campaign_code: campaignCode, lang } = request.payload; @@ -25,6 +26,8 @@ const createToken = async function (request, h, dependencies = { tokenService }) let accessToken, refreshToken; let expirationDelaySeconds; + const origin = getForwardedOrigin(request.headers); + const grantType = request.payload.grant_type; const scope = request.payload.scope; @@ -33,7 +36,14 @@ const createToken = async function (request, h, dependencies = { tokenService }) const localeFromCookie = request.state?.locale; const source = 'pix'; - const tokensInfo = await usecases.authenticateUser({ username, password, scope, source, localeFromCookie }); + const tokensInfo = await usecases.authenticateUser({ + username, + password, + scope, + source, + localeFromCookie, + audience: origin, + }); accessToken = tokensInfo.accessToken; refreshToken = tokensInfo.refreshToken; diff --git a/api/tests/identity-access-management/acceptance/application/token.route.test.js b/api/tests/identity-access-management/acceptance/application/token.route.test.js index 8f6ae8dac01..139683a8546 100644 --- a/api/tests/identity-access-management/acceptance/application/token.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/token.route.test.js @@ -37,6 +37,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', @@ -72,6 +74,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', @@ -95,6 +99,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', @@ -135,6 +141,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', @@ -316,6 +324,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () headers: { 'content-type': 'application/x-www-form-urlencoded', cookie: `locale=${localeFromCookie}`, + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', @@ -351,6 +361,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () headers: { 'content-type': 'application/x-www-form-urlencoded', cookie: `locale=${localeFromCookie}`, + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password', diff --git a/api/tests/identity-access-management/unit/application/token.controller.test.js b/api/tests/identity-access-management/unit/application/token.controller.test.js index 35c9c7a4381..f95c586aad9 100644 --- a/api/tests/identity-access-management/unit/application/token.controller.test.js +++ b/api/tests/identity-access-management/unit/application/token.controller.test.js @@ -33,6 +33,7 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const password = 'user_password'; const scope = 'pix-orga'; const source = 'pix'; + const audience = 'http-proto://pix/toto'; /** * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/ @@ -44,7 +45,11 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const refreshToken = 'refresh.token'; const localeFromCookie = 'fr-FR'; const request = { - headers: { 'content-type': 'application/x-www-form-urlencoded' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'http-proto', + 'x-forwarded-host': 'pix/toto', + }, payload: { grant_type: 'password', username, @@ -58,7 +63,7 @@ describe('Unit | Identity Access Management | Application | Controller | Token', sinon .stub(usecases, 'authenticateUser') - .withArgs({ username, password, scope, source, localeFromCookie }) + .withArgs({ username, password, scope, source, localeFromCookie, audience }) .resolves({ accessToken, refreshToken, expirationDelaySeconds }); const tokenServiceStub = { extractUserId: sinon.stub() }; From 4d1140b3dfe4ec2b469098117a5c3671fa24e752 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Thu, 16 Jan 2025 14:48:54 +0100 Subject: [PATCH 05/17] feat(api): add audience to #createAccessTokenFromUser --- api/src/shared/domain/services/token-service.js | 8 ++++---- .../shared/unit/domain/services/token-service_test.js | 10 ++++++---- api/tests/test-helper.js | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/api/src/shared/domain/services/token-service.js b/api/src/shared/domain/services/token-service.js index facb0bcf687..57045db9ced 100644 --- a/api/src/shared/domain/services/token-service.js +++ b/api/src/shared/domain/services/token-service.js @@ -11,15 +11,15 @@ import { const CERTIFICATION_RESULTS_BY_RECIPIENT_EMAIL_LINK_SCOPE = 'certificationResultsByRecipientEmailLink'; -function _createAccessToken({ userId, source, expirationDelaySeconds }) { - return jsonwebtoken.sign({ user_id: userId, source }, config.authentication.secret, { +function _createAccessToken({ userId, source, expirationDelaySeconds, audience }) { + return jsonwebtoken.sign({ user_id: userId, source, aud: audience }, config.authentication.secret, { expiresIn: expirationDelaySeconds, }); } -function createAccessTokenFromUser(userId, source) { +function createAccessTokenFromUser({ userId, source, audience }) { const expirationDelaySeconds = config.authentication.accessTokenLifespanMs / 1000; - const accessToken = _createAccessToken({ userId, source, expirationDelaySeconds }); + const accessToken = _createAccessToken({ userId, source, expirationDelaySeconds, audience }); return { accessToken, expirationDelaySeconds }; } diff --git a/api/tests/shared/unit/domain/services/token-service_test.js b/api/tests/shared/unit/domain/services/token-service_test.js index 06848ec0b84..b4b42a3dc60 100644 --- a/api/tests/shared/unit/domain/services/token-service_test.js +++ b/api/tests/shared/unit/domain/services/token-service_test.js @@ -48,14 +48,15 @@ describe('Unit | Shared | Domain | Services | Token Service', function () { sinon.stub(settings.authentication, 'secret').value('a secret'); sinon.stub(settings.authentication, 'accessTokenLifespanMs').value(1000); const accessToken = 'valid access token'; + const audience = 'http-proto://pix/toto'; const expirationDelaySeconds = 1; - const firstParameter = { user_id: userId, source }; + const firstParameter = { user_id: userId, source, aud: audience }; const secondParameter = 'a secret'; const thirdParameter = { expiresIn: 1 }; sinon.stub(jsonwebtoken, 'sign').withArgs(firstParameter, secondParameter, thirdParameter).returns(accessToken); // when - const result = tokenService.createAccessTokenFromUser(userId, source); + const result = tokenService.createAccessTokenFromUser({ userId, source, audience }); // then expect(result).to.be.deep.equal({ accessToken, expirationDelaySeconds }); @@ -120,7 +121,8 @@ describe('Unit | Shared | Domain | Services | Token Service', function () { it('should return userId if the accessToken is valid', function () { // given const userId = 123; - const accessToken = tokenService.createAccessTokenFromUser(userId, 'pix').accessToken; + const audience = 'http-proto://pix/toto'; + const accessToken = tokenService.createAccessTokenFromUser({ userId, source: 'pix', audience }).accessToken; // when const result = tokenService.extractUserId(accessToken); @@ -432,7 +434,7 @@ describe('Unit | Shared | Domain | Services | Token Service', function () { const userId = 1; sinon.stub(settings.authentication, 'secret').value('SECRET_KEY'); sinon.stub(settings.anonymous, 'accessTokenLifespanMs').value(10000); - const payload = { user_id: userId, source: 'pix' }; + const payload = { user_id: userId, source: 'pix', aud: undefined }; const secret = 'SECRET_KEY'; const options = { expiresIn: 10 }; sinon.stub(jsonwebtoken, 'sign').returns('VALID_ACCESS_TOKEN'); diff --git a/api/tests/test-helper.js b/api/tests/test-helper.js index 770dc3b7feb..80b317c8c7d 100644 --- a/api/tests/test-helper.js +++ b/api/tests/test-helper.js @@ -113,8 +113,8 @@ function toStream(data, encoding = 'utf8') { }); } -function generateValidRequestAuthorizationHeader(userId = 1234, source = 'pix') { - const accessToken = tokenService.createAccessTokenFromUser(userId, source).accessToken; +function generateValidRequestAuthorizationHeader(userId = 1234, source = 'pix', audience = 'http://app.pix.org') { + const accessToken = tokenService.createAccessTokenFromUser({ userId, source, audience }).accessToken; return `Bearer ${accessToken}`; } From 4a178b483e65ff850868efa7865493fdb2754d84 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Thu, 16 Jan 2025 15:03:03 +0100 Subject: [PATCH 06/17] feat(api): add audience for token creation in #authenticateUser --- .../domain/usecases/authenticate-user.js | 6 +++++- .../unit/domain/usecases/authenticate-user_test.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/api/src/identity-access-management/domain/usecases/authenticate-user.js b/api/src/identity-access-management/domain/usecases/authenticate-user.js index e1fa6c9a6f5..bf6b92ebb9a 100644 --- a/api/src/identity-access-management/domain/usecases/authenticate-user.js +++ b/api/src/identity-access-management/domain/usecases/authenticate-user.js @@ -34,7 +34,11 @@ const authenticateUser = async function ({ const refreshToken = RefreshToken.generate({ userId: foundUser.id, scope, source, audience }); await refreshTokenRepository.save({ refreshToken }); - const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser(foundUser.id, source); + const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser({ + userId: foundUser.id, + source, + audience, + }); foundUser.setLocaleIfNotAlreadySet(localeFromCookie); if (foundUser.hasBeenModified) { diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js index a6bf438acec..71536ca72b4 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js @@ -165,7 +165,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const expirationDelaySeconds = ''; tokenService.createAccessTokenFromUser - .withArgs(user.id, source) + .withArgs({ userId: user.id, source, audience }) .resolves({ accessToken, expirationDelaySeconds }); // when @@ -214,7 +214,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u sinon.stub(RefreshToken, 'generate').returns(refreshToken); tokenService.createAccessTokenFromUser - .withArgs(user.id, source) + .withArgs({ userId: user.id, source, audience }) .resolves({ accessToken, expirationDelaySeconds }); // when @@ -256,7 +256,9 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const refreshToken = { value: 'jwt.refresh.token', userId: '456', scope, audience }; sinon.stub(RefreshToken, 'generate').returns(refreshToken); - tokenService.createAccessTokenFromUser.withArgs(user.id, source).resolves({ accessToken, expirationDelaySeconds }); + tokenService.createAccessTokenFromUser + .withArgs({ userId: user.id, source, audience }) + .resolves({ accessToken, expirationDelaySeconds }); // when const result = await authenticateUser({ @@ -292,7 +294,9 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const user = domainBuilder.buildUser({ email: userEmail }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); - tokenService.createAccessTokenFromUser.withArgs(user.id, source).resolves({ accessToken, expirationDelaySeconds }); + tokenService.createAccessTokenFromUser + .withArgs({ userId: user.id, source, audience }) + .resolves({ accessToken, expirationDelaySeconds }); // when await authenticateUser({ From ae7e67b85086c8e92a9608741cdc89cee98c198d Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Thu, 16 Jan 2025 15:03:42 +0100 Subject: [PATCH 07/17] feat(api): add audience in #createAccessTokenFromRefreshToken --- .../application/token/token.controller.js | 2 +- ...access-token-from-refresh-token.usecase.js | 7 +++++- .../unit/application/token.controller.test.js | 8 +++++-- ...s-token-from-refresh-token.usecase.test.js | 24 ++++++++++++++----- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/api/src/identity-access-management/application/token/token.controller.js b/api/src/identity-access-management/application/token/token.controller.js index 79770f10231..d5f95346636 100644 --- a/api/src/identity-access-management/application/token/token.controller.js +++ b/api/src/identity-access-management/application/token/token.controller.js @@ -51,7 +51,7 @@ const createToken = async function (request, h, dependencies = { tokenService }) } else if (grantType === 'refresh_token') { refreshToken = request.payload.refresh_token; - const tokensInfo = await usecases.createAccessTokenFromRefreshToken({ refreshToken, scope }); + const tokensInfo = await usecases.createAccessTokenFromRefreshToken({ refreshToken, scope, audience: origin }); accessToken = tokensInfo.accessToken; expirationDelaySeconds = tokensInfo.expirationDelaySeconds; diff --git a/api/src/identity-access-management/domain/usecases/create-access-token-from-refresh-token.usecase.js b/api/src/identity-access-management/domain/usecases/create-access-token-from-refresh-token.usecase.js index 3563e82ad90..1edc03e5f6d 100644 --- a/api/src/identity-access-management/domain/usecases/create-access-token-from-refresh-token.usecase.js +++ b/api/src/identity-access-management/domain/usecases/create-access-token-from-refresh-token.usecase.js @@ -5,6 +5,7 @@ const createAccessTokenFromRefreshToken = async function ({ scope, refreshTokenRepository, tokenService, + audience, }) { const foundRefreshToken = await refreshTokenRepository.findByToken({ token: refreshToken }); @@ -14,7 +15,11 @@ const createAccessTokenFromRefreshToken = async function ({ throw new UnauthorizedError('Refresh token is invalid', 'INVALID_REFRESH_TOKEN'); } - return tokenService.createAccessTokenFromUser(foundRefreshToken.userId, foundRefreshToken.source); + return tokenService.createAccessTokenFromUser({ + userId: foundRefreshToken.userId, + source: foundRefreshToken.source, + audience, + }); }; export { createAccessTokenFromRefreshToken }; diff --git a/api/tests/identity-access-management/unit/application/token.controller.test.js b/api/tests/identity-access-management/unit/application/token.controller.test.js index f95c586aad9..68d87305bc7 100644 --- a/api/tests/identity-access-management/unit/application/token.controller.test.js +++ b/api/tests/identity-access-management/unit/application/token.controller.test.js @@ -99,13 +99,17 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const expirationDelaySeconds = 6666; const refreshToken = 'refresh.token'; const request = { - headers: { 'content-type': 'application/x-www-form-urlencoded' }, + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'http-proto', + 'x-forwarded-host': 'pix/toto', + }, payload: { grant_type: 'refresh_token', refresh_token: refreshToken, scope }, }; sinon .stub(usecases, 'createAccessTokenFromRefreshToken') - .withArgs({ refreshToken, scope }) + .withArgs({ refreshToken, scope, audience }) .resolves({ accessToken, expirationDelaySeconds }); const tokenServiceStub = { extractUserId: sinon.stub() }; diff --git a/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js index 76005fef8db..bcfc4256f24 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js @@ -12,14 +12,17 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; + const audience = 'http-proto://pix/toto'; - const refreshToken = RefreshToken.generate({ userId, scope, source }); + const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); const refreshTokenRepository = { findByToken: sinon.stub() }; refreshTokenRepository.findByToken.withArgs({ token: refreshToken.value }).resolves(refreshToken); const tokenService = { createAccessTokenFromUser: sinon.stub() }; - tokenService.createAccessTokenFromUser.withArgs(userId, source).resolves({ accessToken, expirationDelaySeconds }); + tokenService.createAccessTokenFromUser + .withArgs({ userId, source, audience }) + .resolves({ accessToken, expirationDelaySeconds }); // when const createdAccessToken = await createAccessTokenFromRefreshToken({ @@ -27,6 +30,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- scope, refreshTokenRepository, tokenService, + audience, }); // then @@ -42,14 +46,17 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; + const audience = 'http-proto://pix/toto'; - const refreshToken = RefreshToken.generate({ userId, scope, source }); + const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); const refreshTokenRepository = { findByToken: sinon.stub() }; refreshTokenRepository.findByToken.withArgs({ token: refreshToken.value }).resolves(null); const tokenService = { createAccessTokenFromUser: sinon.stub() }; - tokenService.createAccessTokenFromUser.withArgs(userId, source).resolves({ accessToken, expirationDelaySeconds }); + tokenService.createAccessTokenFromUser + .withArgs({ userId, source, audience }) + .resolves({ accessToken, expirationDelaySeconds }); // when const error = await catchErr(createAccessTokenFromRefreshToken)({ @@ -57,6 +64,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- scope, refreshTokenRepository, tokenService, + audience, }); // then @@ -74,14 +82,17 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; + const audience = 'http-proto://pix/toto'; - const refreshToken = RefreshToken.generate({ userId, scope, source }); + const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); const refreshTokenRepository = { findByToken: sinon.stub() }; refreshTokenRepository.findByToken.withArgs({ token: refreshToken.value }).resolves(refreshToken); const tokenService = { createAccessTokenFromUser: sinon.stub() }; - tokenService.createAccessTokenFromUser.withArgs(userId, source).resolves({ accessToken, expirationDelaySeconds }); + tokenService.createAccessTokenFromUser + .withArgs({ userId, source, audience }) + .resolves({ accessToken, expirationDelaySeconds }); // when const error = await catchErr(createAccessTokenFromRefreshToken)({ @@ -89,6 +100,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- scope: 'pix-orga', refreshTokenRepository, tokenService, + audience, }); // then From 56ad5c6da88e219551daa13fe2708208920fbf48 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Thu, 16 Jan 2025 15:32:07 +0100 Subject: [PATCH 08/17] feat(api): add audience in #oidcAuthenticationService.createAccessToken --- .../domain/services/oidc-authentication-service.js | 8 ++++++-- .../domain/usecases/authenticate-oidc-user.usecase.js | 2 +- .../domain/usecases/create-oidc-user.usecase.js | 2 +- .../usecases/reconcile-oidc-user-for-admin.usecase.js | 2 +- .../domain/usecases/reconcile-oidc-user.usecase.js | 2 +- .../domain/services/oidc-authentication-service_test.js | 7 ++++--- .../usecases/authenticate-oidc-user.usecase.test.js | 4 +++- .../unit/domain/usecases/create-oidc-user.usecase.test.js | 2 +- .../reconcile-oidc-user-for-admin.usecase.test.js | 4 ++-- .../domain/usecases/reconcile-oidc-user.usecase.test.js | 2 +- 10 files changed, 21 insertions(+), 14 deletions(-) diff --git a/api/src/identity-access-management/domain/services/oidc-authentication-service.js b/api/src/identity-access-management/domain/services/oidc-authentication-service.js index 3fdb68ad99a..a0a7b92df45 100644 --- a/api/src/identity-access-management/domain/services/oidc-authentication-service.js +++ b/api/src/identity-access-management/domain/services/oidc-authentication-service.js @@ -122,8 +122,12 @@ export class OidcAuthenticationService { } } - createAccessToken(userId) { - return jsonwebtoken.sign({ user_id: userId }, config.authentication.secret, this.accessTokenJwtOptions); + createAccessToken({ userId, audience }) { + return jsonwebtoken.sign( + { user_id: userId, aud: audience }, + config.authentication.secret, + this.accessTokenJwtOptions, + ); } async saveIdToken({ idToken, userId }) { 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 23a24a1c3de..dd8a4db2922 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 @@ -73,7 +73,7 @@ async function authenticateOidcUser({ authenticationMethodRepository, }); - const pixAccessToken = oidcAuthenticationService.createAccessToken(user.id); + const pixAccessToken = oidcAuthenticationService.createAccessToken({ userId: user.id }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { diff --git a/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js index 6c419ad1cf3..57a49aceb70 100644 --- a/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js @@ -68,7 +68,7 @@ async function createOidcUser({ authenticationMethodRepository, }); - const accessToken = oidcAuthenticationService.createAccessToken(userId); + const accessToken = oidcAuthenticationService.createAccessToken({ userId }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { diff --git a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js index 230c05d6b79..9abcbe42e27 100644 --- a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js +++ b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js @@ -58,7 +58,7 @@ export const reconcileOidcUserForAdmin = async function ({ }), }); - const accessToken = await oidcAuthenticationService.createAccessToken(userId); + const accessToken = await oidcAuthenticationService.createAccessToken({ userId }); await userLoginRepository.updateLastLoggedAt({ userId }); return accessToken; diff --git a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js index f27501eaf45..4556931b86a 100644 --- a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js @@ -52,7 +52,7 @@ export const reconcileOidcUser = async function ({ }), }); - const accessToken = await oidcAuthenticationService.createAccessToken(userId); + const accessToken = await oidcAuthenticationService.createAccessToken({ userId }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { diff --git a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js index b2b07952fd5..de854457d10 100644 --- a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js +++ b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js @@ -118,11 +118,12 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { }); describe('#createAccessToken', function () { - it('creates access token with user id', function () { + it('creates access token with user id and audience', function () { // given const userId = 42; const accessToken = Symbol('valid access token'); - const payload = { user_id: userId }; + const audience = 'http-proto://pix/toto'; + const payload = { user_id: userId, aud: audience }; const jwtOptions = { expiresIn: ms('48h') / 1000 }; sinon .stub(jsonwebtoken, 'sign') @@ -132,7 +133,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { const oidcAuthenticationService = new OidcAuthenticationService(settings.oidcExampleNet); // when - const result = oidcAuthenticationService.createAccessToken(userId); + const result = oidcAuthenticationService.createAccessToken({ userId, audience }); // then expect(result).to.equal(accessToken); 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 84acb87488e..a1dd23c1565 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 @@ -401,7 +401,9 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi .withArgs({ externalIdentityId, identityProvider: oidcAuthenticationService.identityProvider }) .resolves({ id: 10 }); oidcAuthenticationService.createAuthenticationComplement.returns(undefined); - oidcAuthenticationService.createAccessToken.withArgs(10).returns('accessTokenForExistingExternalUser'); + oidcAuthenticationService.createAccessToken + .withArgs({ userId: 10 }) + .returns('accessTokenForExistingExternalUser'); oidcAuthenticationService.saveIdToken .withArgs({ idToken: sessionContent.idToken, userId: 10 }) .resolves('logoutUrlUUID'); diff --git a/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js index 08812c19153..00e2763506e 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js @@ -102,7 +102,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use .withArgs({ externalIdentifier: 'externalId', identityProvider: 'SOME_IDP' }) .resolves(null); oidcAuthenticationService.createUserAccount.resolves(10); - oidcAuthenticationService.createAccessToken.withArgs(10).returns('accessTokenForExistingExternalUser'); + oidcAuthenticationService.createAccessToken.withArgs({ userId: 10 }).returns('accessTokenForExistingExternalUser'); oidcAuthenticationService.saveIdToken.withArgs({ idToken, userId: 10 }).resolves('logoutUrlUUID'); // when diff --git a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js index d274c39062d..0c0b3f1423f 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js @@ -109,7 +109,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- firstName: 'Anne', }), ); - oidcAuthenticationService.createAccessToken.withArgs(userId).returns('accessToken'); + oidcAuthenticationService.createAccessToken.withArgs({ userId }).returns('accessToken'); // when const result = await reconcileOidcUserForAdmin({ @@ -122,7 +122,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- }); // then - expect(oidcAuthenticationService.createAccessToken).to.be.calledOnceWith(userId); + expect(oidcAuthenticationService.createAccessToken).to.be.calledOnceWith({ userId }); expect(userLoginRepository.updateLastLoggedAt).to.be.calledOnceWith({ userId }); expect(result).to.equal('accessToken'); }); diff --git a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js index 5156223f6a3..3343985c2b5 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js @@ -220,7 +220,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- expiredDate: new Date(), }), ); - oidcAuthenticationService.createAccessToken.withArgs(userId).returns('accessToken'); + oidcAuthenticationService.createAccessToken.withArgs({ userId }).returns('accessToken'); oidcAuthenticationService.saveIdToken .withArgs({ idToken: sessionContent.idToken, userId }) .resolves('logoutUrlUUID'); From 354adcdb75519735e20a7fc2f9f638e13df9c9de Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 17 Jan 2025 10:12:10 +0100 Subject: [PATCH 09/17] feat(admin): rename audience to target --- admin/app/adapters/oidc-identity-provider.js | 4 ++-- admin/app/authenticators/oidc.js | 2 +- admin/app/routes/authentication/login-oidc.js | 2 +- admin/tests/unit/adapters/oidc-identity-provider-test.js | 4 ++-- admin/tests/unit/authenticators/oidc-test.js | 2 +- admin/tests/unit/routes/authentication/login-oidc-test.js | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/admin/app/adapters/oidc-identity-provider.js b/admin/app/adapters/oidc-identity-provider.js index f053b56b043..4b1b9f232d9 100644 --- a/admin/app/adapters/oidc-identity-provider.js +++ b/admin/app/adapters/oidc-identity-provider.js @@ -1,11 +1,11 @@ import ApplicationAdapter from './application'; -const PIX_ADMIN_AUDIENCE = 'admin'; +const PIX_ADMIN_TARGET = 'admin'; export default class OidcIdentityProviderAdapter extends ApplicationAdapter { urlForFindAll(_, snapshot) { if (snapshot.adapterOptions?.readyIdentityProviders) { - return `${this.host}/${this.namespace}/oidc/identity-providers?audience=${PIX_ADMIN_AUDIENCE}`; + return `${this.host}/${this.namespace}/oidc/identity-providers?target=${PIX_ADMIN_TARGET}`; } return `${this.host}/${this.namespace}/admin/oidc/identity-providers`; } diff --git a/admin/app/authenticators/oidc.js b/admin/app/authenticators/oidc.js index 35edb2433a8..82bbd0fade8 100644 --- a/admin/app/authenticators/oidc.js +++ b/admin/app/authenticators/oidc.js @@ -26,7 +26,7 @@ export default class OidcAuthenticator extends BaseAuthenticator { identity_provider: identityProvider.code, code, state: state, - audience: 'admin', + target: 'admin', }; if (this.session.isAuthenticated) { diff --git a/admin/app/routes/authentication/login-oidc.js b/admin/app/routes/authentication/login-oidc.js index 8c45454454b..e0307c7bb8b 100644 --- a/admin/app/routes/authentication/login-oidc.js +++ b/admin/app/routes/authentication/login-oidc.js @@ -95,7 +95,7 @@ export default class LoginOidcRoute extends Route { async _handleRedirectRequest(identityProvider) { const response = await fetch( - `${ENV.APP.API_HOST}/api/oidc/authorization-url?identity_provider=${identityProvider.code}&audience=admin`, + `${ENV.APP.API_HOST}/api/oidc/authorization-url?identity_provider=${identityProvider.code}&target=admin`, ); const { redirectTarget } = await response.json(); this.location.replace(redirectTarget); diff --git a/admin/tests/unit/adapters/oidc-identity-provider-test.js b/admin/tests/unit/adapters/oidc-identity-provider-test.js index 701c5876026..567a41dd089 100644 --- a/admin/tests/unit/adapters/oidc-identity-provider-test.js +++ b/admin/tests/unit/adapters/oidc-identity-provider-test.js @@ -4,7 +4,7 @@ import { module, test } from 'qunit'; module('Unit | Adapter | OidcIdentityProvider', function (hooks) { setupTest(hooks); module('#urlForFindAll', function () { - test('returns correct url for ready providers including audience parameter', function (assert) { + test('returns correct url for ready providers including target parameter', function (assert) { // given const adapter = this.owner.lookup('adapter:oidc-identity-provider'); @@ -16,7 +16,7 @@ module('Unit | Adapter | OidcIdentityProvider', function (hooks) { }); // then - assert.ok(url.endsWith('/oidc/identity-providers?audience=admin')); + assert.ok(url.endsWith('/oidc/identity-providers?target=admin')); }); test('returns correct url for all available providers', function (assert) { diff --git a/admin/tests/unit/authenticators/oidc-test.js b/admin/tests/unit/authenticators/oidc-test.js index 5f876707f3f..87fc4f746f1 100644 --- a/admin/tests/unit/authenticators/oidc-test.js +++ b/admin/tests/unit/authenticators/oidc-test.js @@ -27,7 +27,7 @@ module('Unit | Authenticator | oidc', function (hooks) { identity_provider: identityProviderCode, code: code, state, - audience: 'admin', + target: 'admin', }, }, }); diff --git a/admin/tests/unit/routes/authentication/login-oidc-test.js b/admin/tests/unit/routes/authentication/login-oidc-test.js index 6d22c9c526a..c6ab34e701b 100644 --- a/admin/tests/unit/routes/authentication/login-oidc-test.js +++ b/admin/tests/unit/routes/authentication/login-oidc-test.js @@ -60,7 +60,7 @@ module('Unit | Route | login-oidc', function (hooks) { // then sinon.assert.calledWith( fetchStub, - 'http://localhost:3000/api/oidc/authorization-url?identity_provider=OIDC_PARTNER&audience=admin', + 'http://localhost:3000/api/oidc/authorization-url?identity_provider=OIDC_PARTNER&target=admin', ); sinon.assert.calledWith(route.location.replace, 'https://oidc.example.net/connexion'); assert.ok(true); From 3e9ae3511e159c5d33ce1013d88ef8f5e9ebca94 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 17 Jan 2025 10:37:40 +0100 Subject: [PATCH 10/17] feat(api): rename audience to target --- api/src/authorization/domain/constants.js | 2 +- .../oidc-provider/oidc-provider.admin.controller.js | 2 +- .../oidc-provider/oidc-provider.controller.js | 12 ++++++------ .../application/oidc-provider/oidc-provider.route.js | 6 +++--- .../services/oidc-authentication-service-registry.js | 5 ++--- .../usecases/authenticate-oidc-user.usecase.js | 12 ++++++------ .../domain/usecases/get-authorization-url.usecase.js | 4 ++-- .../usecases/get-ready-identity-providers.usecase.js | 4 ++-- .../application/oidc-provider.route.test.js | 8 ++++---- .../oidc-authentication-service-registry.test.js | 4 ++-- .../application/oidc-provider.controller.test.js | 6 +++--- .../usecases/authenticate-oidc-user.usecase.test.js | 12 ++++++------ .../usecases/get-authorization-url.usecase.test.js | 6 +++--- .../get-ready-identity-providers.usecase.test.js | 8 ++++---- 14 files changed, 45 insertions(+), 46 deletions(-) diff --git a/api/src/authorization/domain/constants.js b/api/src/authorization/domain/constants.js index 5fd3069375a..8a590a942c8 100644 --- a/api/src/authorization/domain/constants.js +++ b/api/src/authorization/domain/constants.js @@ -7,7 +7,7 @@ const PIX_ADMIN = { SUPPORT: 'SUPPORT', }, SCOPE: 'pix-admin', - AUDIENCE: 'admin', + TARGET: 'admin', }; const PIX_ORGA = { diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js index 35641ed721f..e94cdfd3a98 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js @@ -49,7 +49,7 @@ async function reconcileUserForAdmin( const oidcAuthenticationService = dependencies.oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: identityProvider, - audience: PIX_ADMIN.AUDIENCE, + target: PIX_ADMIN.TARGET, }); const accessToken = await usecases.reconcileOidcUserForAdmin({ 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 f0e8b612db0..5cd9455b58b 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 @@ -11,7 +11,7 @@ import * as oidcSerializer from '../../infrastructure/serializers/jsonapi/oidc-s * @return {Promise<*>} */ async function authenticateOidcUser(request, h) { - const { code, state, iss, identityProvider: identityProviderCode, audience } = request.deserializedPayload; + const { code, state, iss, identityProvider: identityProviderCode, target } = request.deserializedPayload; const sessionState = request.yar.get('state', true); const nonce = request.yar.get('nonce', true); @@ -22,7 +22,7 @@ async function authenticateOidcUser(request, h) { } const result = await usecases.authenticateOidcUser({ - audience, + target, code, state, iss, @@ -95,9 +95,9 @@ async function findUserForReconciliation(request, h, dependencies = { oidcSerial * @return {Promise} */ async function getAuthorizationUrl(request, h) { - const { identity_provider: identityProvider, audience } = request.query; + const { identity_provider: identityProvider, target } = request.query; - const { nonce, state, ...payload } = await usecases.getAuthorizationUrl({ audience, identityProvider }); + const { nonce, state, ...payload } = await usecases.getAuthorizationUrl({ target, identityProvider }); request.yar.set('state', state); request.yar.set('nonce', nonce); @@ -113,8 +113,8 @@ async function getAuthorizationUrl(request, h) { * @return {Promise<*>} */ async function getIdentityProviders(request, h) { - const audience = request.query.audience; - const identityProviders = await usecases.getReadyIdentityProviders({ audience }); + const target = request.query.target; + const identityProviders = await usecases.getReadyIdentityProviders({ target }); return h.response(oidcProviderSerializer.serialize(identityProviders)).code(200); } diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js index 3ad73b8dee1..5cae43f497d 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.route.js @@ -10,7 +10,7 @@ export const oidcProviderRoutes = [ config: { validate: { query: Joi.object({ - audience: Joi.string().optional().default('app'), + target: Joi.string().optional().default('app'), }), }, auth: false, @@ -48,7 +48,7 @@ export const oidcProviderRoutes = [ validate: { query: Joi.object({ identity_provider: Joi.string().required(), - audience: Joi.string().valid('app', 'admin').optional(), + target: Joi.string().valid('app', 'admin').optional(), }), }, handler: (request, h) => oidcProviderController.getAuthorizationUrl(request, h), @@ -72,7 +72,7 @@ export const oidcProviderRoutes = [ code: Joi.string().required(), state: Joi.string().required(), iss: Joi.string().optional(), - audience: Joi.string().valid('app', 'admin').optional(), + target: Joi.string().valid('app', 'admin').optional(), }, }, }), diff --git a/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js b/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js index 7e2130d3c99..2987995e2ba 100644 --- a/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js +++ b/api/src/identity-access-management/domain/services/oidc-authentication-service-registry.js @@ -44,9 +44,8 @@ export class OidcAuthenticationServiceRegistry { return this.#readyOidcProviderServicesForPixAdmin; } - getOidcProviderServiceByCode({ identityProviderCode, audience = 'app' }) { - const services = - audience === 'admin' ? this.#readyOidcProviderServicesForPixAdmin : this.#readyOidcProviderServices; + getOidcProviderServiceByCode({ identityProviderCode, target = 'app' }) { + const services = target === 'admin' ? this.#readyOidcProviderServicesForPixAdmin : this.#readyOidcProviderServices; const oidcProviderService = services.find((service) => identityProviderCode === service.code); if (!oidcProviderService) { 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 dd8a4db2922..fe1ac818e6d 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 @@ -4,7 +4,7 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; /** * @typedef {function} authenticateOidcUser * @param {Object} params - * @param {string} params.audience + * @param {string} params.target * @param {string} params.code * @param {string} params.identityProviderCode * @param {string} params.nonce @@ -19,7 +19,7 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; * @return {Promise<{isAuthenticationComplete: boolean, givenName: string, familyName: string, authenticationKey: string, email: string}|{isAuthenticationComplete: boolean, pixAccessToken: string, logoutUrlUUID: string}>} */ async function authenticateOidcUser({ - audience, + target, code, state, iss, @@ -38,7 +38,7 @@ async function authenticateOidcUser({ const oidcAuthenticationService = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode, - audience, + target, }); const sessionContent = await oidcAuthenticationService.exchangeCodeForTokens({ @@ -63,7 +63,7 @@ async function authenticateOidcUser({ return { authenticationKey, givenName, familyName, email, isAuthenticationComplete: false }; } - await _assertUserWithPixAdminAccess({ audience, userId: user.id, adminMemberRepository }); + await _assertUserWithPixAdminAccess({ target, userId: user.id, adminMemberRepository }); await _updateAuthenticationMethodWithComplement({ userInfo, @@ -109,8 +109,8 @@ async function _updateAuthenticationMethodWithComplement({ }); } -async function _assertUserWithPixAdminAccess({ audience, userId, adminMemberRepository }) { - if (audience === PIX_ADMIN.AUDIENCE) { +async function _assertUserWithPixAdminAccess({ target, userId, adminMemberRepository }) { + if (target === PIX_ADMIN.TARGET) { const adminMember = await adminMemberRepository.get({ userId }); if (!adminMember?.hasAccessToAdminScope) { throw new ForbiddenAccess( diff --git a/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js b/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js index 45bec8fa4a4..e06dfda1e71 100644 --- a/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js +++ b/api/src/identity-access-management/domain/usecases/get-authorization-url.usecase.js @@ -6,13 +6,13 @@ * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @return {Promise} */ -async function getAuthorizationUrl({ audience, identityProvider, oidcAuthenticationServiceRegistry }) { +async function getAuthorizationUrl({ target, identityProvider, oidcAuthenticationServiceRegistry }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProvider); const oidcAuthenticationService = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: identityProvider, - audience, + target, }); return oidcAuthenticationService.getAuthorizationUrl(); diff --git a/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js b/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js index 58f673e4b1e..14742c33144 100644 --- a/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js +++ b/api/src/identity-access-management/domain/usecases/get-ready-identity-providers.usecase.js @@ -5,10 +5,10 @@ * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @return {Promise} */ -const getReadyIdentityProviders = async function ({ audience = 'app', oidcAuthenticationServiceRegistry }) { +const getReadyIdentityProviders = async function ({ target = 'app', oidcAuthenticationServiceRegistry }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); - if (audience === 'admin') { + if (target === 'admin') { return oidcAuthenticationServiceRegistry.getReadyOidcProviderServicesForPixAdmin(); } 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 d3131f969f0..e95742c336c 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 @@ -77,7 +77,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p // given const query = querystring.stringify({ identity_provider: 'OIDC_EXAMPLE_NET', - audience: 'app', + target: 'app', }); // when @@ -273,7 +273,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p }); }); - context('when audience is admin', function () { + context('when target is admin', function () { context('when user does not have an admin role', function () { it('returns 403', async function () { // given @@ -281,7 +281,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const lastName = 'Doe'; const externalIdentifier = 'sub'; - payload.data.attributes.audience = 'admin'; + payload.data.attributes.target = 'admin'; const userId = databaseBuilder.factory.buildUser({ firstName, @@ -349,7 +349,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const lastName = 'Doe'; const externalIdentifier = 'sub'; - payload.data.attributes.audience = 'admin'; + payload.data.attributes.target = 'admin'; const userId = databaseBuilder.factory.buildUser.withRole({ firstName, diff --git a/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js b/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js index 3831489fcf3..fa50af4ed03 100644 --- a/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js +++ b/api/tests/identity-access-management/integration/domain/services/oidc-authentication-service-registry.test.js @@ -164,7 +164,7 @@ describe('Integration | Identity Access Management | Domain | Service | oidc-aut expect(service.code).to.equal('OIDC_EXAMPLE'); }); - describe('when the audience is admin', function () { + describe('when the target is admin', function () { it('returns a ready OIDC provider for Pix Admin', async function () { // given await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); @@ -172,7 +172,7 @@ describe('Integration | Identity Access Management | Domain | Service | oidc-aut // when const service = oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode({ identityProviderCode: 'OIDC_EXAMPLE_FOR_PIX_ADMIN', - audience: PIX_ADMIN.AUDIENCE, + target: PIX_ADMIN.TARGET, }); // then 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 bc00bab62c3..1400a232cc4 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 @@ -46,7 +46,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr // then expect(usecases.authenticateOidcUser).to.have.been.calledWithExactly({ - audience: undefined, + target: undefined, code, identityProviderCode: identityProvider, nonce: 'nonce', @@ -215,7 +215,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr //then expect(usecases.getAuthorizationUrl).to.have.been.calledWithExactly({ - audience: undefined, + target: undefined, identityProvider: 'OIDC', }); expect(request.yar.set).to.have.been.calledTwice; @@ -249,7 +249,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr ]); // when - const response = await oidcProviderController.getIdentityProviders({ query: { audience: null } }, hFake); + const response = await oidcProviderController.getIdentityProviders({ query: { target: null } }, hFake); // then expect(usecases.getReadyIdentityProviders).to.have.been.called; 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 a1dd23c1565..0ed63e3e56d 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 @@ -48,19 +48,19 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi }; }); - context('check access by audience', function () { - context('when audience is pix-admin', function () { + context('check access by target', function () { + context('when target is pix-admin', function () { context('when user has no role and is therefore not an admin member', function () { it('throws an error', async function () { // given - const audience = appMessages.PIX_ADMIN.AUDIENCE; + const target = appMessages.PIX_ADMIN.TARGET; _fakeOidcAPI({ oidcAuthenticationService, externalIdentityId }); userRepository.findByExternalIdentifier.resolves({ id: 10 }); adminMemberRepository.get.resolves(null); // when const error = await catchErr(authenticateOidcUser)({ - audience, + target, oidcAuthenticationServiceRegistry, userRepository, adminMemberRepository, @@ -76,7 +76,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi context('when user has a role but admin membership is disabled', function () { it('throws an error', async function () { // given - const audience = appMessages.PIX_ADMIN.AUDIENCE; + const target = appMessages.PIX_ADMIN.TARGET; const adminMember = new AdminMember({ id: 567, role: 'CERTIF', @@ -88,7 +88,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi // when const error = await catchErr(authenticateOidcUser)({ - audience, + target, oidcAuthenticationServiceRegistry, userRepository, adminMemberRepository, diff --git a/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js index 6058f3f1d27..ab9c886ef9a 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/get-authorization-url.usecase.test.js @@ -4,7 +4,7 @@ import { expect, sinon } from '../../../../test-helper.js'; describe('Unit | Identity Access Management | Domain | UseCases | get-authorization-url', function () { it('returns the generated authorization url', async function () { // given - const audience = 'app'; + const target = 'app'; const identityProvider = 'OIDC'; const oidcAuthenticationService = { getAuthorizationUrl: sinon.stub().returns('https://authorization.url'), @@ -21,7 +21,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-authorizat // when const authorizationUrl = await getAuthorizationUrl({ - audience, + target, identityProvider, oidcAuthenticationServiceRegistry, }); @@ -33,7 +33,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-authorizat ); expect(oidcAuthenticationServiceRegistry.getOidcProviderServiceByCode).to.have.been.calledWithExactly({ identityProviderCode: 'OIDC', - audience: 'app', + target: 'app', }); expect(oidcAuthenticationService.getAuthorizationUrl).to.have.been.calledOnce; expect(authorizationUrl).to.equal('https://authorization.url'); diff --git a/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js index 50cc24a8102..8a37d079aa4 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/get-ready-identity-providers.usecase.test.js @@ -19,12 +19,12 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-ready-iden }; }); - describe('when an audience is provided', function () { - describe('when the provided audience is equal to "admin"', function () { + describe('when a target is provided', function () { + describe('when the provided target is equal to "admin"', function () { it('returns oidc providers from getReadyOidcProviderServicesForPixAdmin', async function () { // when const identityProviders = await getReadyIdentityProviders({ - audience: PIX_ADMIN.AUDIENCE, + target: PIX_ADMIN.TARGET, oidcAuthenticationServiceRegistry: oidcAuthenticationServiceRegistryStub, }); @@ -39,7 +39,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | get-ready-iden it('returns oidc providers from getReadyOidcProviderServices', async function () { // when const identityProviders = await getReadyIdentityProviders({ - audience: null, + target: null, oidcAuthenticationServiceRegistry: oidcAuthenticationServiceRegistryStub, }); From 64263b9ef9a8796d56a21e63c0516288e61d60ee Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 17 Jan 2025 11:20:24 +0100 Subject: [PATCH 11/17] feat(api): add audience to #authenticateOidcUser usecase --- .../domain/usecases/authenticate-oidc-user.usecase.js | 4 +++- .../usecases/authenticate-oidc-user.usecase.test.js | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) 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 fe1ac818e6d..9e42021f12b 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 @@ -10,6 +10,7 @@ import { ForbiddenAccess } from '../../../shared/domain/errors.js'; * @param {string} params.nonce * @param {string} params.sessionState * @param {string} params.state + * @param {string} params.audience * @param {AuthenticationSessionService} params.authenticationSessionService * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry * @param {AdminMemberRepository} params.adminMemberRepository @@ -26,6 +27,7 @@ async function authenticateOidcUser({ identityProviderCode, nonce, sessionState, + audience, authenticationSessionService, oidcAuthenticationServiceRegistry, adminMemberRepository, @@ -73,7 +75,7 @@ async function authenticateOidcUser({ authenticationMethodRepository, }); - const pixAccessToken = oidcAuthenticationService.createAccessToken({ userId: user.id }); + const pixAccessToken = oidcAuthenticationService.createAccessToken({ userId: user.id, audience }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { 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 0ed63e3e56d..86cdaabfab2 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 @@ -17,6 +17,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userLoginRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; + const audience = 'https://pix/toto.fr'; beforeEach(function () { oidcAuthenticationService = { @@ -217,6 +218,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + audience, }); // then @@ -245,6 +247,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + audience, }); // then @@ -330,6 +333,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userLoginRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; + const audience = 'https://pix/toto.fr'; beforeEach(function () { oidcAuthenticationService = { @@ -382,6 +386,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + audience, }); // then @@ -402,7 +407,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi .resolves({ id: 10 }); oidcAuthenticationService.createAuthenticationComplement.returns(undefined); oidcAuthenticationService.createAccessToken - .withArgs({ userId: 10 }) + .withArgs({ userId: 10, audience }) .returns('accessTokenForExistingExternalUser'); oidcAuthenticationService.saveIdToken .withArgs({ idToken: sessionContent.idToken, userId: 10 }) @@ -418,6 +423,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + audience, }); // then @@ -455,6 +461,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi authenticationMethodRepository, userRepository, userLoginRepository, + audience, }); // then From 8e1d59dc807b238c475193dd7c6c88bdb0d901f4 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 17 Jan 2025 11:30:44 +0100 Subject: [PATCH 12/17] feat(api): add audience to #authenticateOidcUser controller method --- .../application/oidc-provider/oidc-provider.controller.js | 3 +++ .../unit/application/oidc-provider.controller.test.js | 6 ++++++ 2 files changed, 9 insertions(+) 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 5cd9455b58b..864fb9698ef 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 @@ -3,6 +3,7 @@ import { requestResponseUtils } from '../../../shared/infrastructure/utils/reque import { usecases } from '../../domain/usecases/index.js'; import * as oidcProviderSerializer from '../../infrastructure/serializers/jsonapi/oidc-identity-providers.serializer.js'; import * as oidcSerializer from '../../infrastructure/serializers/jsonapi/oidc-serializer.js'; +import { getForwardedOrigin } from '../../infrastructure/utils/network.js'; /** * @typedef {function} authenticateOidcUser @@ -12,6 +13,7 @@ import * as oidcSerializer from '../../infrastructure/serializers/jsonapi/oidc-s */ async function authenticateOidcUser(request, h) { const { code, state, iss, identityProvider: identityProviderCode, target } = request.deserializedPayload; + const origin = getForwardedOrigin(request.headers); const sessionState = request.yar.get('state', true); const nonce = request.yar.get('nonce', true); @@ -29,6 +31,7 @@ async function authenticateOidcUser(request, h) { identityProviderCode, nonce, sessionState, + audience: origin, }); if (result.isAuthenticationComplete) { 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 1400a232cc4..9c39fb2f60f 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 @@ -12,12 +12,17 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr const iss = 'https://issuer.url'; const identityProvider = 'OIDC_EXAMPLE_NET'; const pixAccessToken = 'pixAccessToken'; + const audience = 'http-proto://pix/toto'; let request; beforeEach(function () { request = { auth: { credentials: { userId: 123 } }, + headers: { + 'x-forwarded-proto': 'http-proto', + 'x-forwarded-host': 'pix/toto', + }, deserializedPayload: { identityProvider, code, @@ -53,6 +58,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr sessionState: state, state: identityProviderState, iss, + audience, }); expect(request.yar.commit).to.have.been.calledOnce; }); From 9ece3d7ba2ad2f292743516cd4475e691596d715 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Fri, 17 Jan 2025 15:46:20 +0100 Subject: [PATCH 13/17] feat(api): make audience in test more real usage friendly --- .../refresh-token.repository.test.js | 22 ++++++++-------- .../oidc-provider.controller.test.js | 6 ++--- .../unit/application/token.controller.test.js | 10 +++---- .../unit/domain/models/RefreshToken.test.js | 10 +++---- .../oidc-authentication-service_test.js | 2 +- .../authenticate-oidc-user.usecase.test.js | 4 +-- .../domain/usecases/authenticate-user_test.js | 26 +++++++++---------- ...s-token-from-refresh-token.usecase.test.js | 6 ++--- .../domain/services/token-service_test.js | 4 +-- 9 files changed, 45 insertions(+), 45 deletions(-) diff --git a/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js b/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js index ea6d34cac44..5c4fd8f875c 100644 --- a/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js +++ b/api/tests/identity-access-management/integration/infrastructure/repositories/refresh-token.repository.test.js @@ -19,7 +19,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken }); @@ -27,7 +27,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://certif.pixfr', }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); @@ -46,7 +46,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://orga.pix.fr', }); await refreshTokenRepository.save({ refreshToken }); @@ -54,7 +54,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); @@ -62,7 +62,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId2!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken3 }); @@ -81,7 +81,7 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); // when @@ -100,14 +100,14 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken1 }); const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://orga.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); @@ -127,21 +127,21 @@ describe('Integration | Identity Access Management | Infrastructure | Repository userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken1 }); const refreshToken2 = RefreshToken.generate({ userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); await refreshTokenRepository.save({ refreshToken: refreshToken2 }); const refreshToken3 = RefreshToken.generate({ userId: 'userId2!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://certif.pixfr', }); await refreshTokenRepository.save({ refreshToken: refreshToken3 }); 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 9c39fb2f60f..3fbbf53722c 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 @@ -12,7 +12,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr const iss = 'https://issuer.url'; const identityProvider = 'OIDC_EXAMPLE_NET'; const pixAccessToken = 'pixAccessToken'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://app.pix.fr'; let request; @@ -20,8 +20,8 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr request = { auth: { credentials: { userId: 123 } }, headers: { - 'x-forwarded-proto': 'http-proto', - 'x-forwarded-host': 'pix/toto', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', }, deserializedPayload: { identityProvider, diff --git a/api/tests/identity-access-management/unit/application/token.controller.test.js b/api/tests/identity-access-management/unit/application/token.controller.test.js index 68d87305bc7..4c236a6cf18 100644 --- a/api/tests/identity-access-management/unit/application/token.controller.test.js +++ b/api/tests/identity-access-management/unit/application/token.controller.test.js @@ -33,7 +33,7 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const password = 'user_password'; const scope = 'pix-orga'; const source = 'pix'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://app.pix.fr'; /** * @see https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/ @@ -47,8 +47,8 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const request = { headers: { 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'http-proto', - 'x-forwarded-host': 'pix/toto', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', }, payload: { grant_type: 'password', @@ -101,8 +101,8 @@ describe('Unit | Identity Access Management | Application | Controller | Token', const request = { headers: { 'content-type': 'application/x-www-form-urlencoded', - 'x-forwarded-proto': 'http-proto', - 'x-forwarded-host': 'pix/toto', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', }, payload: { grant_type: 'refresh_token', refresh_token: refreshToken, scope }, }; diff --git a/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js b/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js index 5bdcab54e5b..0fba481ef23 100644 --- a/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js +++ b/api/tests/identity-access-management/unit/domain/models/RefreshToken.test.js @@ -21,7 +21,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu scope: 'scope!', source: 'source!', value: 'token!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); // then @@ -29,7 +29,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu expect(refreshToken.userId).to.equal('userId!'); expect(refreshToken.scope).to.equal('scope!'); expect(refreshToken.source).to.equal('source!'); - expect(refreshToken.audience).to.equal('audience!'); + expect(refreshToken.audience).to.equal('https://app.pix.fr'); expect(refreshToken.expirationDelaySeconds).to.equal(defaultRefreshTokenLifespanMs / 1000); }); @@ -47,7 +47,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu scope: 'pix-orga', source: 'source!', value: 'token!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); // then @@ -61,7 +61,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu userId: 'userId!', source: 'source!', value: 'token!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); // then @@ -81,7 +81,7 @@ describe('Unit | Identity Access Management | Domain | Model | RefreshToken', fu userId: 'userId!', scope: 'scope!', source: 'source!', - audience: 'audience!', + audience: 'https://app.pix.fr', }); // then diff --git a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js index de854457d10..7a7bf462d48 100644 --- a/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js +++ b/api/tests/identity-access-management/unit/domain/services/oidc-authentication-service_test.js @@ -122,7 +122,7 @@ describe('Unit | Domain | Services | oidc-authentication-service', function () { // given const userId = 42; const accessToken = Symbol('valid access token'); - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const payload = { user_id: userId, aud: audience }; const jwtOptions = { expiresIn: ms('48h') / 1000 }; sinon 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 86cdaabfab2..07a8f36813b 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 @@ -17,7 +17,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userLoginRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; - const audience = 'https://pix/toto.fr'; + const audience = 'https://app.pix.fr'; beforeEach(function () { oidcAuthenticationService = { @@ -333,7 +333,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | authenticate-oi let userLoginRepository; let oidcAuthenticationServiceRegistry; const externalIdentityId = '094b83ac-2e20-4aa8-b438-0bc91748e4a6'; - const audience = 'https://pix/toto.fr'; + const audience = 'https://app.pix.fr'; beforeEach(function () { oidcAuthenticationService = { diff --git a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js index 71536ca72b4..03fb2924b42 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/authenticate-user_test.js @@ -51,7 +51,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ORGA.SCOPE; const user = new User({ email: userEmail, memberships: [] }); - const audience = 'audience'; + const audience = 'https://orga.pix.fr'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); // when @@ -77,7 +77,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); - const audience = 'audience'; + const audience = 'https://admin.pix.fr'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); adminMemberRepository.get.withArgs({ userId: user.id }).resolves(); @@ -104,7 +104,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const scope = PIX_ADMIN.SCOPE; const user = new User({ email: userEmail }); - const audience = 'audience'; + const audience = 'https://admin.pix.fr'; const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -142,7 +142,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = PIX_ADMIN.SCOPE; const source = 'pix'; const user = new User({ id: 123, email: userEmail }); - const audience = 'audience'; + const audience = 'https://admin.pix.fr'; const adminMember = new AdminMember({ id: 567, userId: user.id, @@ -201,7 +201,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const accessToken = 'jwt.access.token'; const expirationDelaySeconds = 1; const source = 'pix'; - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; const user = domainBuilder.buildUser({ email: userEmail, @@ -249,7 +249,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const scope = 'mon-pix'; const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail }); - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); @@ -289,7 +289,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; const user = domainBuilder.buildUser({ email: userEmail }); @@ -320,7 +320,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u // given const unknownUserEmail = 'unknown_user_email@example.net'; pixAuthenticationService.getUserByUsernameAndPassword.rejects(new UserNotFoundError()); - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; // when const error = await catchErr(authenticateUser)({ @@ -339,7 +339,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u it('should rejects an error when given password does not match the found user’s one', async function () { // given pixAuthenticationService.getUserByUsernameAndPassword.rejects(new MissingOrInvalidCredentialsError()); - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; // when const error = await catchErr(authenticateUser)({ @@ -359,7 +359,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u it('should throw UserShouldChangePasswordError', async function () { // given const tokenService = { createPasswordResetToken: sinon.stub() }; - const audience = 'audience'; + const audience = 'https://certif.pix.fr'; const user = domainBuilder.buildUser({ username: 'jean.neymar2008' }); const authenticationMethod = domainBuilder.buildAuthenticationMethod.withPixAsIdentityProviderAndRawPassword({ @@ -404,7 +404,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const expirationDelaySeconds = 1; const user = domainBuilder.buildUser({ email: userEmail, locale: 'fr-FR' }); - const audience = 'audience'; + const audience = 'https://app.pix.fr'; pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); tokenService.createAccessTokenFromUser.resolves({ accessToken, expirationDelaySeconds }); @@ -436,7 +436,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; - const audience = 'audience'; + const audience = 'https://app.pix.fr'; const user = domainBuilder.buildUser({ email: userEmail, locale: null }); const setLocaleIfNotAlreadySetStub = sinon.stub(user, 'setLocaleIfNotAlreadySet'); @@ -470,7 +470,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | authenticate-u const source = 'pix'; const scope = 'mon-pix'; const expirationDelaySeconds = 1; - const audience = 'audience'; + const audience = 'https://app.pix.fr'; const user = domainBuilder.buildUser({ email: userEmail, locale: undefined }); pixAuthenticationService.getUserByUsernameAndPassword.resolves(user); diff --git a/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js index bcfc4256f24..b7004e8d5f6 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/create-access-token-from-refresh-token.usecase.test.js @@ -12,7 +12,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); @@ -46,7 +46,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); @@ -82,7 +82,7 @@ describe('Unit | Identity Access Management | Domain | UseCases | create-access- const expirationDelaySeconds = 1; const scope = 'mon-pix'; const source = 'pix'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const refreshToken = RefreshToken.generate({ userId, scope, source, audience }); diff --git a/api/tests/shared/unit/domain/services/token-service_test.js b/api/tests/shared/unit/domain/services/token-service_test.js index b4b42a3dc60..418f86262a7 100644 --- a/api/tests/shared/unit/domain/services/token-service_test.js +++ b/api/tests/shared/unit/domain/services/token-service_test.js @@ -48,7 +48,7 @@ describe('Unit | Shared | Domain | Services | Token Service', function () { sinon.stub(settings.authentication, 'secret').value('a secret'); sinon.stub(settings.authentication, 'accessTokenLifespanMs').value(1000); const accessToken = 'valid access token'; - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const expirationDelaySeconds = 1; const firstParameter = { user_id: userId, source, aud: audience }; const secondParameter = 'a secret'; @@ -121,7 +121,7 @@ describe('Unit | Shared | Domain | Services | Token Service', function () { it('should return userId if the accessToken is valid', function () { // given const userId = 123; - const audience = 'http-proto://pix/toto'; + const audience = 'https://admin.pix.fr'; const accessToken = tokenService.createAccessTokenFromUser({ userId, source: 'pix', audience }).accessToken; // when From 52a120ff9d6f6c41baae485da15495e6175e678a Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Tue, 21 Jan 2025 16:24:03 +0100 Subject: [PATCH 14/17] feat(api): add audience in #createUser oidc provider controller --- .../application/oidc-provider/oidc-provider.controller.js | 2 ++ .../domain/usecases/create-oidc-user.usecase.js | 3 ++- .../unit/application/oidc-provider.controller.test.js | 3 +++ .../unit/domain/usecases/create-oidc-user.usecase.test.js | 6 +++++- 4 files changed, 12 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 864fb9698ef..3e1db65b282 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 @@ -61,12 +61,14 @@ async function createUser(request, h, dependencies = { requestResponseUtils }) { const { identityProvider, authenticationKey } = request.deserializedPayload; const localeFromCookie = request.state?.locale; const language = dependencies.requestResponseUtils.extractLocaleFromRequest(request); + const origin = getForwardedOrigin(request.headers); const { accessToken: access_token, logoutUrlUUID: logout_url_uuid } = await usecases.createOidcUser({ authenticationKey, identityProvider, localeFromCookie, language, + audience: origin, }); return h.response({ access_token, logout_url_uuid }).code(200); diff --git a/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js index 57a49aceb70..1a18a9bc91e 100644 --- a/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/create-oidc-user.usecase.js @@ -21,6 +21,7 @@ async function createOidcUser({ authenticationKey, localeFromCookie, language, + audience, authenticationSessionService, oidcAuthenticationServiceRegistry, authenticationMethodRepository, @@ -68,7 +69,7 @@ async function createOidcUser({ authenticationMethodRepository, }); - const accessToken = oidcAuthenticationService.createAccessToken({ userId }); + const accessToken = oidcAuthenticationService.createAccessToken({ userId, audience }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { 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 3fbbf53722c..ccdf5a79c5f 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 @@ -126,6 +126,8 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr deserializedPayload: { identityProvider: 'OIDC', authenticationKey: 'abcde' }, headers: { 'accept-language': 'fr', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', }, state: { locale: 'fr-FR', @@ -145,6 +147,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr authenticationKey: 'abcde', localeFromCookie: 'fr-FR', language: 'fr', + audience: 'https://app.pix.fr', }); expect(response.statusCode).to.equal(200); expect(response.source).to.deep.equal({ diff --git a/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js index 00e2763506e..754e797f238 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/create-oidc-user.usecase.test.js @@ -94,6 +94,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use // given const idToken = 'idToken'; const language = 'nl'; + const audience = 'htttps://app.pix.fr'; authenticationSessionService.getByKey.withArgs('AUTHENTICATION_KEY').resolves({ sessionContent: { idToken, accessToken: 'accessToken' }, userInfo: { firstName: 'Jean', lastName: 'Heymar', externalIdentityId: 'externalId' }, @@ -102,7 +103,9 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use .withArgs({ externalIdentifier: 'externalId', identityProvider: 'SOME_IDP' }) .resolves(null); oidcAuthenticationService.createUserAccount.resolves(10); - oidcAuthenticationService.createAccessToken.withArgs({ userId: 10 }).returns('accessTokenForExistingExternalUser'); + oidcAuthenticationService.createAccessToken + .withArgs({ userId: 10, audience }) + .returns('accessTokenForExistingExternalUser'); oidcAuthenticationService.saveIdToken.withArgs({ idToken, userId: 10 }).resolves('logoutUrlUUID'); // when @@ -111,6 +114,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | create-oidc-use authenticationKey: 'AUTHENTICATION_KEY', localeFromCookie: 'nl-BE', language, + audience, authenticationSessionService, oidcAuthenticationServiceRegistry, authenticationMethodRepository, From 1c39276bcd6f552ec8eaa5f0143a1c2094e25c62 Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 22 Jan 2025 10:38:28 +0100 Subject: [PATCH 15/17] feat(api): add audience in oidc user access token for reconciliation --- .../oidc-provider/oidc-provider.controller.js | 3 +++ .../domain/usecases/reconcile-oidc-user.usecase.js | 4 +++- .../acceptance/application/oidc-provider.route.test.js | 4 ++++ .../unit/application/oidc-provider.controller.test.js | 5 +++++ .../domain/usecases/reconcile-oidc-user.usecase.test.js | 9 ++++++++- 5 files changed, 23 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 3e1db65b282..90ab463f4b7 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 @@ -151,9 +151,12 @@ async function getRedirectLogoutUrl(request, h) { async function reconcileUser(request, h) { const { identityProvider, authenticationKey } = request.deserializedPayload; + const origin = getForwardedOrigin(request.headers); + const result = await usecases.reconcileOidcUser({ authenticationKey, identityProvider, + audience: origin, }); return h.response({ access_token: result.accessToken, logout_url_uuid: result.logoutUrlUUID }).code(200); diff --git a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js index 4556931b86a..41e1573febe 100644 --- a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js +++ b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user.usecase.js @@ -6,6 +6,7 @@ import { AuthenticationMethod } from '../models/AuthenticationMethod.js'; * @param {Object} params * @param {string} params.authenticationKey * @param {string} params.identityProvider + * @param {string} params.audience * @param {AuthenticationSessionService} params.authenticationSessionService * @param {AuthenticationMethodRepository} params.authenticationMethodRepository * @param {OidcAuthenticationServiceRegistry} params.oidcAuthenticationServiceRegistry @@ -19,6 +20,7 @@ export const reconcileOidcUser = async function ({ authenticationMethodRepository, oidcAuthenticationServiceRegistry, userLoginRepository, + audience, }) { await oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProvider); @@ -52,7 +54,7 @@ export const reconcileOidcUser = async function ({ }), }); - const accessToken = await oidcAuthenticationService.createAccessToken({ userId }); + const accessToken = await oidcAuthenticationService.createAccessToken({ userId, audience }); let logoutUrlUUID; if (oidcAuthenticationService.shouldCloseSession) { 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 e95742c336c..429aeee3dc7 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 @@ -590,6 +590,10 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const response = await server.inject({ method: 'POST', url: `/api/oidc/user/reconcile`, + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, payload: { data: { attributes: { 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 ccdf5a79c5f..b7f4770fb92 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 @@ -311,6 +311,10 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr it('calls use case and return the result', async function () { // given const request = { + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', + }, deserializedPayload: { identityProvider: 'OIDC', authenticationKey: '123abc', @@ -329,6 +333,7 @@ describe('Unit | Identity Access Management | Application | Controller | oidc-pr expect(usecases.reconcileOidcUser).to.have.been.calledWithExactly({ authenticationKey: '123abc', identityProvider: 'OIDC', + audience: 'https://app.pix.fr', }); expect(result.source).to.deep.equal({ access_token: 'accessToken', logout_url_uuid: 'logoutUrlUUID' }); }); diff --git a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js index 3343985c2b5..182b75ad575 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user.usecase.test.js @@ -8,6 +8,7 @@ import { reconcileOidcUser } from '../../../../../src/identity-access-management import { catchErr, expect, sinon } from '../../../../test-helper.js'; describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc-user', function () { + const audience = 'https://app.pix.fr'; context('when identityProvider is generic', function () { let authenticationMethodRepository, authenticationSessionService, @@ -45,6 +46,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- const error = await catchErr(reconcileOidcUser)({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, @@ -69,6 +71,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- await reconcileOidcUser({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, @@ -90,6 +93,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- const error = await catchErr(reconcileOidcUser)({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, @@ -123,6 +127,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- await reconcileOidcUser({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, @@ -189,6 +194,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- await reconcileOidcUser({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, @@ -220,7 +226,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- expiredDate: new Date(), }), ); - oidcAuthenticationService.createAccessToken.withArgs({ userId }).returns('accessToken'); + oidcAuthenticationService.createAccessToken.withArgs({ userId, audience }).returns('accessToken'); oidcAuthenticationService.saveIdToken .withArgs({ idToken: sessionContent.idToken, userId }) .resolves('logoutUrlUUID'); @@ -229,6 +235,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- const result = await reconcileOidcUser({ authenticationKey: 'authenticationKey', identityProvider, + audience, authenticationSessionService, authenticationMethodRepository, oidcAuthenticationServiceRegistry, From 2d04c9190ec1fc13937ce893f7517ec00629dfcc Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 22 Jan 2025 10:51:04 +0100 Subject: [PATCH 16/17] feat(api): add audience in oidc admin user access token for admin reconciliation --- .../oidc-provider/oidc-provider.admin.controller.js | 3 +++ .../usecases/reconcile-oidc-user-for-admin.usecase.js | 4 +++- .../application/oidc-provider.admin.route.test.js | 4 ++++ .../application/oidc-provider.admin.controller.test.js | 4 ++++ .../reconcile-oidc-user-for-admin.usecase.test.js | 10 ++++++++-- 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js index e94cdfd3a98..81b6b426a20 100644 --- a/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js +++ b/api/src/identity-access-management/application/oidc-provider/oidc-provider.admin.controller.js @@ -3,6 +3,7 @@ import { PIX_ADMIN } from '../../../authorization/domain/constants.js'; import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js'; import { usecases } from '../../domain/usecases/index.js'; import * as oidcProviderSerializer from '../../infrastructure/serializers/jsonapi/oidc-identity-providers.serializer.js'; +import { getForwardedOrigin } from '../../infrastructure/utils/network.js'; /** * @param request @@ -43,6 +44,7 @@ async function reconcileUserForAdmin( }, ) { const { email, identityProvider, authenticationKey } = request.deserializedPayload; + const origin = getForwardedOrigin(request.headers); await dependencies.oidcAuthenticationServiceRegistry.loadOidcProviderServices(); await dependencies.oidcAuthenticationServiceRegistry.configureReadyOidcProviderServiceByCode(identityProvider); @@ -57,6 +59,7 @@ async function reconcileUserForAdmin( identityProvider, authenticationKey, oidcAuthenticationService, + audience: origin, }); return h.response({ access_token: accessToken }).code(200); diff --git a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js index 9abcbe42e27..775e75d3810 100644 --- a/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js +++ b/api/src/identity-access-management/domain/usecases/reconcile-oidc-user-for-admin.usecase.js @@ -14,6 +14,7 @@ import { AuthenticationMethod } from '../models/AuthenticationMethod.js'; * @param {string} params.authenticationKey * @param {string} params.email * @param {string} params.identityProvider + * @param {string} params.audience * @param {OidcAuthenticationService} params.oidcAuthenticationService * @param {AuthenticationSessionService} params.authenticationSessionService * @param {AuthenticationMethodRepository} params.authenticationMethodRepository @@ -29,6 +30,7 @@ export const reconcileOidcUserForAdmin = async function ({ authenticationMethodRepository, userRepository, userLoginRepository, + audience, }) { const sessionContentAndUserInfo = await authenticationSessionService.getByKey(authenticationKey); if (!sessionContentAndUserInfo) { @@ -58,7 +60,7 @@ export const reconcileOidcUserForAdmin = async function ({ }), }); - const accessToken = await oidcAuthenticationService.createAccessToken({ userId }); + const accessToken = await oidcAuthenticationService.createAccessToken({ userId, audience }); await userLoginRepository.updateLastLoggedAt({ userId }); return accessToken; diff --git a/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js b/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js index 2cf527380e4..1c03eb41f07 100644 --- a/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js @@ -116,6 +116,10 @@ describe('Acceptance | Identity Access Management | Route | Admin | oidc-provide const response = await server.inject({ method: 'POST', url: `/api/admin/oidc/user/reconcile`, + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'admin.pix.fr', + }, payload: { data: { attributes: { diff --git a/api/tests/identity-access-management/unit/application/oidc-provider.admin.controller.test.js b/api/tests/identity-access-management/unit/application/oidc-provider.admin.controller.test.js index ac425c8e9f0..e371e7d7148 100644 --- a/api/tests/identity-access-management/unit/application/oidc-provider.admin.controller.test.js +++ b/api/tests/identity-access-management/unit/application/oidc-provider.admin.controller.test.js @@ -162,6 +162,10 @@ describe('Unit | Identity Access Management | Application | Controller | Admin | getOidcProviderServiceByCode: sinon.stub(), }; const request = { + headers: { + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'admin.pix.fr', + }, deserializedPayload: { identityProvider: 'OIDC', authenticationKey: '123abc', diff --git a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js index 0c0b3f1423f..23e6b6255a1 100644 --- a/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js +++ b/api/tests/identity-access-management/unit/domain/usecases/reconcile-oidc-user-for-admin.usecase.test.js @@ -13,6 +13,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- authenticationSessionService, oidcAuthenticationService; const identityProvider = 'genericOidcProviderCode'; + const audience = 'https://admin.pix.fr'; beforeEach(function () { authenticationMethodRepository = { create: sinon.stub(), findOneByUserIdAndIdentityProvider: sinon.stub() }; @@ -45,6 +46,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- await reconcileOidcUserForAdmin({ email, authenticationKey: 'authenticationKey', + audience, oidcAuthenticationService, authenticationSessionService, authenticationMethodRepository, @@ -77,6 +79,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- email, identityProvider, authenticationKey: 'authenticationKey', + audience, oidcAuthenticationService, authenticationSessionService, authenticationMethodRepository, @@ -109,11 +112,12 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- firstName: 'Anne', }), ); - oidcAuthenticationService.createAccessToken.withArgs({ userId }).returns('accessToken'); + oidcAuthenticationService.createAccessToken.withArgs({ userId, audience }).returns('accessToken'); // when const result = await reconcileOidcUserForAdmin({ authenticationKey: 'authenticationKey', + audience, oidcAuthenticationService, authenticationSessionService, authenticationMethodRepository, @@ -122,7 +126,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- }); // then - expect(oidcAuthenticationService.createAccessToken).to.be.calledOnceWith({ userId }); + expect(oidcAuthenticationService.createAccessToken).to.be.calledOnceWith({ userId, audience }); expect(userLoginRepository.updateLastLoggedAt).to.be.calledOnceWith({ userId }); expect(result).to.equal('accessToken'); }); @@ -135,6 +139,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- // when const error = await catchErr(reconcileOidcUserForAdmin)({ authenticationKey: 'authenticationKey', + audience, oidcAuthenticationService, authenticationSessionService, authenticationMethodRepository, @@ -166,6 +171,7 @@ describe('Unit | Identity Access Management | Domain | UseCase | reconcile-oidc- authenticationKey: 'authenticationKey', email: 'anne@example.net', identityProvider: 'genericOidcProviderCode', + audience, oidcAuthenticationService, authenticationSessionService, authenticationMethodRepository, From 823a0f362b5c334a6c1a2beecc9c2743f0a4041a Mon Sep 17 00:00:00 2001 From: P-Jeremy Date: Wed, 22 Jan 2025 14:50:55 +0100 Subject: [PATCH 17/17] test(api): add audience check in acceptance tests --- .../oidc-provider.admin.route.test.js | 7 +++- .../application/oidc-provider.route.test.js | 34 ++++++++++++++++--- .../application/token.route.test.js | 17 ++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js b/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js index 1c03eb41f07..92a4d36881a 100644 --- a/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/oidc-provider.admin.route.test.js @@ -1,6 +1,7 @@ import jsonwebtoken from 'jsonwebtoken'; import { authenticationSessionService } from '../../../../src/identity-access-management/domain/services/authentication-session.service.js'; +import { decodeIfValid } from '../../../../src/shared/domain/services/token-service.js'; import { createServer, databaseBuilder, @@ -133,7 +134,11 @@ describe('Acceptance | Identity Access Management | Route | Admin | oidc-provide // then expect(response.statusCode).to.equal(200); - expect(response.result['access_token']).to.exist; + expect(response.result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(response.result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://admin.pix.fr', + }); }); }); }); 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 429aeee3dc7..a559fd77e5f 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 @@ -5,6 +5,7 @@ import jsonwebtoken from 'jsonwebtoken'; import { oidcAuthenticationServiceRegistry } from '../../../../lib/domain/usecases/index.js'; import { authenticationSessionService } from '../../../../src/identity-access-management/domain/services/authentication-session.service.js'; import { AuthenticationSessionContent } from '../../../../src/shared/domain/models/index.js'; +import { decodeIfValid } from '../../../../src/shared/domain/services/token-service.js'; import { createServer, databaseBuilder, @@ -254,7 +255,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers: { cookie: cookies[0] }, + headers: { + cookie: cookies[0], + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', + }, payload, }); @@ -267,8 +272,12 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p */ // expect(getAccessTokenRequest.isDone()).to.be.true; expect(oidcExampleNetProvider.client.callback).to.have.been.calledOnce; + expect(response.result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(response.result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://orga.pix.fr', + }); expect(response.statusCode).to.equal(200); - expect(response.result['access_token']).to.exist; expect(response.result['logout_url_uuid']).to.match(UUID_PATTERN); }); }); @@ -395,7 +404,7 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p const response = await server.inject({ method: 'POST', url: '/api/oidc/token', - headers: { cookie: cookies[0] }, + headers: { cookie: cookies[0], 'x-forwarded-proto': 'https', 'x-forwarded-host': 'admin.pix.fr' }, payload, }); @@ -408,6 +417,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p */ // expect(getAccessTokenRequest.isDone()).to.be.true; expect(oidcExampleNetProvider.client.callback).to.have.been.calledOnce; + expect(response.result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(response.result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://admin.pix.fr', + }); expect(response.statusCode).to.equal(200); }); }); @@ -449,6 +463,8 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p headers: { 'accept-language': 'fr', cookie: 'locale=fr-FR', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'app.pix.fr', }, payload: { data: { @@ -465,7 +481,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p // then expect(response.statusCode).to.equal(200); - expect(response.result['access_token']).to.exist; + expect(response.result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(response.result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://app.pix.fr', + }); const createdUser = await knex('users').first(); expect(createdUser.firstName).to.equal('Brice'); @@ -606,7 +626,11 @@ describe('Acceptance | Identity Access Management | Application | Route | oidc-p // then expect(response.statusCode).to.equal(200); - expect(response.result['access_token']).to.exist; + expect(response.result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(response.result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://app.pix.fr', + }); expect(response.result['logout_url_uuid']).to.match(UUID_PATTERN); }); }); diff --git a/api/tests/identity-access-management/acceptance/application/token.route.test.js b/api/tests/identity-access-management/acceptance/application/token.route.test.js index 139683a8546..333c45837cf 100644 --- a/api/tests/identity-access-management/acceptance/application/token.route.test.js +++ b/api/tests/identity-access-management/acceptance/application/token.route.test.js @@ -1,6 +1,7 @@ import querystring from 'node:querystring'; import { PIX_ADMIN } from '../../../../src/authorization/domain/constants.js'; +import { decodeIfValid } from '../../../../src/shared/domain/services/token-service.js'; import { createServer, databaseBuilder, expect, knex } from '../../../test-helper.js'; const { ROLES } = PIX_ADMIN; @@ -53,6 +54,10 @@ describe('Acceptance | Identity Access Management | Route | Token', function () expect(response.statusCode).to.equal(200); expect(result.token_type).to.equal('bearer'); expect(result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://orga.pix.fr', + }); expect(result.user_id).to.equal(userId); expect(result.refresh_token).to.exist; }); @@ -116,6 +121,8 @@ describe('Acceptance | Identity Access Management | Route | Token', function () url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'refresh_token', @@ -129,6 +136,10 @@ describe('Acceptance | Identity Access Management | Route | Token', function () expect(response.statusCode).to.equal(200); expect(result.token_type).to.equal('bearer'); expect(result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://orga.pix.fr', + }); expect(result.user_id).to.equal(userId); expect(result.refresh_token).to.exist; }); @@ -221,6 +232,10 @@ describe('Acceptance | Identity Access Management | Route | Token', function () const result = response.result; expect(result.token_type).to.equal('bearer'); expect(result.access_token).to.exist; + const decodedAccessToken = await decodeIfValid(result.access_token); + expect(decodedAccessToken).to.include({ + aud: 'https://orga.pix.fr', + }); expect(result.user_id).to.equal(userId); }); }); @@ -549,6 +564,8 @@ function _getOptions({ scope, password, username }) { url: '/api/token', headers: { 'content-type': 'application/x-www-form-urlencoded', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'orga.pix.fr', }, payload: querystring.stringify({ grant_type: 'password',