Skip to content

Commit

Permalink
feat(api): check if access token is revoked in authentication process
Browse files Browse the repository at this point in the history
  • Loading branch information
EmmanuelleBonnemay committed Jan 31, 2025
1 parent 23a1209 commit 51a3c0c
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 1 deletion.
9 changes: 8 additions & 1 deletion api/lib/infrastructure/authentication.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import boom from '@hapi/boom';
import lodash from 'lodash';

import { revokedUserAccessRepository } from '../../src/identity-access-management/infrastructure/repositories/revoked-user-access.repository.js';
import { getForwardedOrigin } from '../../src/identity-access-management/infrastructure/utils/network.js';
import { config } from '../../src/shared/config.js';
import { tokenService } from '../../src/shared/domain/services/token-service.js';
Expand Down Expand Up @@ -90,7 +91,13 @@ async function _checkIsAuthenticated(request, h, { key, validate }) {

// Only tokens including user_id are User Access Tokens.
// This is why applications Access Tokens are not subject to audience validation for now.
if (decodedAccessToken.user_id && config.featureToggles.isUserTokenAudConfinementEnabled) {
const userId = decodedAccessToken.user_id;
if (config.featureToggles.isUserTokenAudConfinementEnabled && userId) {
const revokedUserAccess = await revokedUserAccessRepository.findByUserId(userId);
if (revokedUserAccess.isAccessTokenRevoked(decodedAccessToken)) {
return boom.unauthorized();
}

const audience = getForwardedOrigin(request.headers);
if (decodedAccessToken.aud !== audience) {
return boom.unauthorized();
Expand Down
41 changes: 41 additions & 0 deletions api/tests/unit/infrastructure/authentication_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { authentication } from '../../../lib/infrastructure/authentication.js';
import { RevokedUserAccess } from '../../../src/identity-access-management/domain/models/RevokedUserAccess.js';
import { revokedUserAccessRepository } from '../../../src/identity-access-management/infrastructure/repositories/revoked-user-access.repository.js';
import { tokenService } from '../../../src/shared/domain/services/token-service.js';
import { expect, sinon } from '../../test-helper.js';

Expand Down Expand Up @@ -115,6 +117,45 @@ describe('Unit | Infrastructure | Authentication', function () {
});
});

describe('when the user access is revoked', function () {
it('should throw an error', async function () {
// given
const date = new Date();
const revokedUserAccess = new RevokedUserAccess(date.getTime() / 1000);
sinon.stub(revokedUserAccessRepository, 'findByUserId').resolves(revokedUserAccess);
sinon.stub(revokedUserAccess, 'isAccessTokenRevoked').returns(true);

const request = {
headers: {
authorization: 'Bearer token',
'x-forwarded-proto': 'https',
'x-forwarded-host': 'app.pix.fr',
},
};
const h = { authenticated: sinon.stub() };
tokenService.extractTokenFromAuthChain.withArgs('Bearer token').returns('token');
tokenService.getDecodedToken.withArgs('token', 'dummy-secret').returns({
user_id: 'user_id',
aud: 'https://app.pix.fr',
});

// when
const { authenticate } = authentication.scheme(undefined, {
key: 'dummy-secret',
validate: sinon.stub().returns({ isValid: true, credentials: {}, errorCode: null }),
});
const response = await authenticate(request, h);

// then
expect(h.authenticated).to.not.have.been.called;
expect(response.output.payload).to.include({
statusCode: 401,
error: 'Unauthorized',
message: 'Unauthorized',
});
});
});

describe('when there is no forwarded origin in the request', function () {
it('should throw an error', async function () {
// given
Expand Down

0 comments on commit 51a3c0c

Please sign in to comment.