Skip to content

Commit

Permalink
feat(api): demonstrate audience setting with origin
Browse files Browse the repository at this point in the history
  • Loading branch information
lego-technix committed Dec 17, 2024
1 parent 965ce2e commit c992002
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 20 deletions.
22 changes: 21 additions & 1 deletion api/lib/infrastructure/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import boom from '@hapi/boom';
import { config } from '../../src/shared/config.js';
import { tokenService } from '../../src/shared/domain/services/token-service.js';

function getOriginFromHeaders(headers) {
const urlParts = [];

urlParts.push(_getHeaderFirstValue(headers['x-forwarded-proto']));
urlParts.push('://');
urlParts.push(_getHeaderFirstValue(headers['x-forwarded-host']));

return urlParts.join('');
}

function _getHeaderFirstValue(headerValue) {
return headerValue.split(',')[0];
}

async function _checkIsAuthenticated(request, h, { key, validate }) {
if (!request.headers.authorization) {
return boom.unauthorized(null, 'jwt');
Expand All @@ -21,6 +35,12 @@ async function _checkIsAuthenticated(request, h, { key, validate }) {

const decodedAccessToken = tokenService.getDecodedToken(accessToken, key);
if (decodedAccessToken) {
const audience = getOriginFromHeaders(request.headers);
console.log('\n\n\n audience:', audience, 'decodedAccessToken.aud:', decodedAccessToken.aud);
if (decodedAccessToken.aud != audience) {
return { isValid: false, errorCode: 403 };
}

const { isValid, credentials, errorCode } = validate(decodedAccessToken, request, h);
if (isValid) {
return h.authenticated({ credentials });
Expand Down Expand Up @@ -93,4 +113,4 @@ const authentication = {
defaultStrategy: 'jwt-user',
};

export { authentication };
export { authentication, getOriginFromHeaders };
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getOriginFromHeaders } from '../../../../lib/infrastructure/authentication.js';
import { BadRequestError } from '../../../shared/application/http-errors.js';
import { tokenService } from '../../../shared/domain/services/token-service.js';
import { usecases } from '../../domain/usecases/index.js';
Expand Down Expand Up @@ -30,11 +31,12 @@ const createToken = async function (request, h, dependencies = { tokenService })
const scope = request.payload.scope;

if (grantType === 'password') {
const { username, password, scope } = request.payload;
const { username, password } = request.payload;
const localeFromCookie = request.state?.locale;
const audience = getOriginFromHeaders(request.headers);
const source = 'pix';

const tokensInfo = await usecases.authenticateUser({ username, password, scope, source, localeFromCookie });
const tokensInfo = await usecases.authenticateUser({ username, password, audience, source, localeFromCookie });

accessToken = tokensInfo.accessToken;
refreshToken = tokensInfo.refreshToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RefreshToken } from '../models/RefreshToken.js';

const authenticateUser = async function ({
password,
audience,
scope,
source,
username,
Expand All @@ -28,12 +29,17 @@ const authenticateUser = async function ({
throw new UserShouldChangePasswordError(undefined, passwordResetToken);
}

await _checkUserAccessScope(scope, foundUser, adminMemberRepository);
// await _checkUserAccessScope(scope, foundUser, adminMemberRepository);

const refreshToken = RefreshToken.generate({ userId: foundUser.id, scope, source });
await refreshTokenRepository.save({ refreshToken });

const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser(foundUser.id, source);
const { accessToken, expirationDelaySeconds } = await tokenService.createAccessTokenFromUser({
userId: foundUser.id,
audience,
source,
});
console.log('authenticateUser accessToken:', accessToken);

foundUser.setLocaleIfNotAlreadySet(localeFromCookie);
if (foundUser.hasBeenModified) {
Expand All @@ -56,17 +62,17 @@ const authenticateUser = async function ({
}
};

async function _checkUserAccessScope(scope, user, adminMemberRepository) {
if (scope === PIX_ORGA.SCOPE && !user.isLinkedToOrganizations()) {
throw new ForbiddenAccess(PIX_ORGA.NOT_LINKED_ORGANIZATION_MSG);
}
// async function _checkUserAccessScope(scope, user, adminMemberRepository) {
// if (scope === PIX_ORGA.SCOPE && !user.isLinkedToOrganizations()) {
// throw new ForbiddenAccess(PIX_ORGA.NOT_LINKED_ORGANIZATION_MSG);
// }

if (scope === PIX_ADMIN.SCOPE) {
const adminMember = await adminMemberRepository.get({ userId: user.id });
if (!adminMember?.hasAccessToAdminScope) {
throw new ForbiddenAccess(PIX_ADMIN.NOT_ALLOWED_MSG);
}
}
}
// if (scope === PIX_ADMIN.SCOPE) {
// const adminMember = await adminMemberRepository.get({ userId: user.id });
// if (!adminMember?.hasAccessToAdminScope) {
// throw new ForbiddenAccess(PIX_ADMIN.NOT_ALLOWED_MSG);
// }
// }
// }

export { authenticateUser };
9 changes: 5 additions & 4 deletions api/src/shared/domain/services/token-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ 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, audience, source, expirationDelaySeconds }) {
return jsonwebtoken.sign({ user_id: userId, aud: audience, source }, config.authentication.secret, {
expiresIn: expirationDelaySeconds,
});
}

function createAccessTokenFromUser(userId, source) {
function createAccessTokenFromUser({ userId, audience, source }) {
console.log('createAccessTokenFromUser audience:', audience);
const expirationDelaySeconds = config.authentication.accessTokenLifespanMs / 1000;
const accessToken = _createAccessToken({ userId, source, expirationDelaySeconds });
const accessToken = _createAccessToken({ userId, audience, source, expirationDelaySeconds });
return { accessToken, expirationDelaySeconds };
}

Expand Down
74 changes: 74 additions & 0 deletions api/tests/unit/infrastructure/authentication_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { getOriginFromHeaders } from '../../../lib/infrastructure/authentication.js';
import { expect } from '../../../tests/test-helper.js';

describe('Unit | Infrastructure | Authentication', function () {
describe('#getOriginFromHeaders', function () {
context('when port is HTTP standard port 80', function () {
it('returns an HTTP URL', async function () {
// given
const headers = {
'x-forwarded-proto': 'http',
'x-forwarded-port': '80',
'x-forwarded-host': 'localhost',
};

// when
const origin = getOriginFromHeaders(headers);

// then
expect(origin).to.equal('http://localhost');
});
});

context('when port is HTTPS standard port 443', function () {
it('returns an HTTPS URL', async function () {
// given
const headers = {
'x-forwarded-proto': 'https',
'x-forwarded-port': '443',
'x-forwarded-host': 'app-pr10823.review.pix.fr',
};

// when
const origin = getOriginFromHeaders(headers);

// then
expect(origin).to.equal('https://app-pr10823.review.pix.fr');
});
});

context('when port is neither HTTP nor HTTPS standard ports', function () {
it('returns an URL with a specific port', async function () {
// given
const headers = {
'x-forwarded-proto': 'http',
'x-forwarded-port': '4200',
'x-forwarded-host': 'localhost:4200',
};

// when
const origin = getOriginFromHeaders(headers);

// then
expect(origin).to.equal('http://localhost:4200');
});
});
});

context('when x-forwarded-proto and x-forwarded-port have multiple values (ember serve --proxy)', function () {
it('returns an URL corresponding to the first HTTP proxy facing the user', async function () {
// given
const headers = {
'x-forwarded-proto': 'https,http',
'x-forwarded-port': '80',
'x-forwarded-host': 'app.dev.pix.org',
};

// when
const origin = getOriginFromHeaders(headers);

// then
expect(origin).to.equal('https://app.dev.pix.org');
});
});
});

0 comments on commit c992002

Please sign in to comment.