Skip to content

Commit

Permalink
[TECH] Migrer la route POST /api/revoke vers src/identity-access-mana…
Browse files Browse the repository at this point in the history
…gement (PIX-13121)

 #9377
  • Loading branch information
pix-service-auto-merge authored Jun 27, 2024
2 parents abbc074 + 68d4294 commit 8c31b31
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,9 @@ const authenticateApplication = async function (request, h) {
.header('Pragma', 'no-cache');
};

const revokeToken = async function (request, h) {
if (request.payload.token_type_hint === 'access_token') return null;

await usecases.revokeRefreshToken({ refreshToken: request.payload.token });
return h.response().code(204);
};

const authenticationController = {
authenticateExternalUser,
authenticateApplication,
revokeToken,
};

export { authenticationController };
28 changes: 0 additions & 28 deletions api/lib/application/authentication/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Joi from 'joi';

import { responseAuthenticationDoc } from '../../infrastructure/open-api-doc/authentication/response-authentication-doc.js';
import { responseObjectErrorDoc } from '../../infrastructure/open-api-doc/livret-scolaire/response-object-error-doc.js';
import { BadRequestError, sendJsonApiError } from '../http-errors.js';
import { authenticationController as AuthenticationController } from './authentication-controller.js';

const register = async function (server) {
Expand Down Expand Up @@ -49,33 +48,6 @@ const register = async function (server) {
tags: ['api', 'authorization-server'],
},
},
{
method: 'POST',
path: '/api/revoke',
config: {
auth: false,
payload: {
allow: 'application/x-www-form-urlencoded',
},
validate: {
payload: Joi.object()
.required()
.keys({
token: Joi.string().required(),
token_type_hint: ['access_token', 'refresh_token'],
}),
failAction: (request, h) => {
return sendJsonApiError(
new BadRequestError('The server could not understand the request due to invalid token.'),
h,
);
},
},
handler: AuthenticationController.revokeToken,
notes: ['- Cette route permet de supprimer le refresh token du temporary storage'],
tags: ['api'],
},
},
{
method: 'POST',
path: '/api/token-from-external-user',
Expand Down
5 changes: 0 additions & 5 deletions api/lib/domain/usecases/revoke-refresh-token.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ const authenticateAnonymousUser = async function (request, h) {
return h.response(response).code(200);
};

/**
* @param request
* @param h
* @param {{
* tokenService: TokenService
* }} dependencies
* @return {Promise<*>}
*/
const createToken = async function (request, h, dependencies = { tokenService }) {
let accessToken, refreshToken;
let expirationDelaySeconds;
Expand Down Expand Up @@ -56,4 +64,11 @@ const createToken = async function (request, h, dependencies = { tokenService })
.header('Pragma', 'no-cache');
};

export const tokenController = { authenticateAnonymousUser, createToken };
const revokeToken = async function (request, h) {
if (request.payload.token_type_hint === 'access_token') return null;

await usecases.revokeRefreshToken({ refreshToken: request.payload.token });
return h.response().code(204);
};

export const tokenController = { authenticateAnonymousUser, createToken, revokeToken };
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Joi from 'joi';

import { BadRequestError, sendJsonApiError } from '../../../../lib/application/http-errors.js';
import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js';
import { tokenController } from './token.controller.js';

Expand Down Expand Up @@ -58,4 +59,31 @@ export const tokenRoutes = [
tags: ['identity-access-management', 'api', 'token'],
},
},
{
method: 'POST',
path: '/api/revoke',
config: {
auth: false,
payload: {
allow: 'application/x-www-form-urlencoded',
},
validate: {
payload: Joi.object()
.required()
.keys({
token: Joi.string().required(),
token_type_hint: ['access_token', 'refresh_token'],
}),
failAction: (request, h) => {
return sendJsonApiError(
new BadRequestError('The server could not understand the request due to invalid token.'),
h,
);
},
},
handler: (request, h) => tokenController.revokeToken(request, h),
notes: ['- Cette route permet de supprimer le refresh token du temporary storage'],
tags: ['identity-access-management', 'api', 'token'],
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @param {{
* refreshToken: string,
* refreshTokenService: RefreshTokenService
* }} params
* @return {Promise<void>}
*/
export const revokeRefreshToken = async function ({ refreshToken, refreshTokenService }) {
await refreshTokenService.revokeRefreshToken({ refreshToken });
};
4 changes: 4 additions & 0 deletions api/src/shared/domain/services/token-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ const tokenService = {
extractCampaignResultsTokenContent,
};

/**
* @typedef TokenService
*/

export {
createAccessTokenForSaml,
createAccessTokenFromAnonymousUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,19 +433,95 @@ describe('Acceptance | Identity Access Management | Route | Token', function ()
});
});

function _getOptions({ scope, password, username }) {
return {
method: 'POST',
url: '/api/token',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
payload: querystring.stringify({
grant_type: 'password',
username,
password,
scope,
}),
describe('POST /api/revoke', function () {
const method = 'POST';
const url = '/api/revoke';
const headers = {
'content-type': 'application/x-www-form-urlencoded',
};
}

let payload;

beforeEach(function () {
payload = querystring.stringify({
token: 'jwt.access.token',
token_type_hint: 'access_token',
});
});

it('returns a response with HTTP status code 204 when route handler (a.k.a. controller) is successful', async function () {
// when
const response = await server.inject({ method, url, payload, auth: null, headers });

// then
expect(response.statusCode).to.equal(204);
});

it('returns a 400 when grant type is not "access_token" nor "refresh_token"', async function () {
// given
payload = querystring.stringify({
token: 'jwt.access.token',
token_type_hint: 'not_standard_token_type',
});

// when
const response = await server.inject({ method, url, payload, auth: null, headers });

// then
expect(response.statusCode).to.equal(400);
});

it('returns a 400 when token is missing', async function () {
// given
payload = querystring.stringify({
token_type_hint: 'access_token',
});

// when
const response = await server.inject({ method, url, payload, auth: null, headers });

// then
expect(response.statusCode).to.equal(400);
});

it('returns a response with HTTP status code 204 even when token type hint is missing', async function () {
// given
payload = querystring.stringify({
token: 'jwt.access.token',
});

// when
const response = await server.inject({ method, url, payload, auth: null, headers });

// then
expect(response.statusCode).to.equal(204);
});

it('returns a JSON API error (415) when request "Content-Type" header is not "application/x-www-form-urlencoded"', async function () {
// given
headers['content-type'] = 'text/html';

// when
const response = await server.inject({ method, url, payload, auth: null, headers });

// then
expect(response.statusCode).to.equal(415);
});
});
});

function _getOptions({ scope, password, username }) {
return {
method: 'POST',
url: '/api/token',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
payload: querystring.stringify({
grant_type: 'password',
username,
password,
scope,
}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,48 @@ describe('Unit | Identity Access Management | Application | Controller | Token',
});
});
});

describe('#revokeToken', function () {
it('returns 204', async function () {
// given
const token = 'jwt.refresh.token';
const request = {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
payload: {
token,
},
};
sinon.stub(usecases, 'revokeRefreshToken').resolves();

// when
const response = await tokenController.revokeToken(request, hFake);

// then
expect(response.statusCode).to.equal(204);
sinon.assert.calledWith(usecases.revokeRefreshToken, { refreshToken: token });
});

it('returns null when token hint is of type access token', async function () {
// given
const token = 'jwt.refresh.token';
const request = {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
payload: {
token,
token_type_hint: 'access_token',
},
};
sinon.stub(usecases, 'revokeRefreshToken').resolves();

// when
const response = await tokenController.revokeToken(request, hFake);

// then
expect(response).to.be.null;
});
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { revokeRefreshToken } from '../../../../lib/domain/usecases/revoke-refresh-token.js';
import { expect, sinon } from '../../../test-helper.js';
import { revokeRefreshToken } from '../../../../../src/identity-access-management/domain/usecases/revoke-refresh-token.usecase.js';
import { expect, sinon } from '../../../../test-helper.js';

describe('Unit | UseCase | revoke-refresh-token', function () {
it('should revoke refresh token', async function () {
describe('Unit | Identity Access Management | Domain | UseCase | revoke-refresh-token', function () {
it('revokes refresh token', async function () {
// given
const refreshToken = 'valid refresh token';
const refreshTokenService = { revokeRefreshToken: sinon.stub() };
Expand Down
Loading

0 comments on commit 8c31b31

Please sign in to comment.