Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions app/oauth2/access-token-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,31 @@ const { Logger } = require('@hmcts/nodejs-logging');

const logger = Logger.getLogger('accessTokenRequest');

const completeRedirectURI = (uri) => {
if (uri.startsWith('undefined')){
throw ERROR_INVALID_REDIRECT_URI;
} else if (!uri.startsWith('http')) {
return `https://${uri}`;
}
return uri;
};

const ERROR_INVALID_REDIRECT_URI = {
code: 'INVALID_REDIRECT_URI',
error: 'Bad Request',
message: 'Redirect URI cannot start with undefined',
message: 'Redirect URI is not permitted',
status: 400
};

const completeRedirectURI = (uri) => {
let parsedUrl;
try {
const fullUri = uri.startsWith('http') ? uri : `https://${uri}`;
parsedUrl = new URL(fullUri);
} catch (e) {
logger.error('Invalid redirect URI:', e.message);
throw ERROR_INVALID_REDIRECT_URI;
}
const allowedHosts = config.get('idam.oauth2.redirect_uri_allowlist')
.split(',')
.map(h => h.trim());
if (!allowedHosts.includes(parsedUrl.hostname)) {
throw ERROR_INVALID_REDIRECT_URI;
}
return parsedUrl.href;
};

function accessTokenRequest(request) {
const options = {
method: 'POST',
Expand Down
2 changes: 1 addition & 1 deletion charts/ccd-api-gateway-web/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ description: Helm chart for the HMCTS CCD API Gateway
apiVersion: v2
name: ccd-api-gateway-web
home: https://github.com/hmcts/ccd-api-gateway
version: 1.2.11
version: 1.2.12
maintainers:
- name: HMCTS CCD Dev Team
email: ccd-devops@HMCTS.NET
Expand Down
1 change: 1 addition & 0 deletions charts/ccd-api-gateway-web/values.aat.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ nodejs:
image: ${IMAGE_NAME}
ingressHost: ${SERVICE_FQDN}
environment:
IDAM_OAUTH2_REDIRECT_URI_ALLOWLIST: "${SERVICE_FQDN}"
CORS_ORIGIN_WHITELIST: "*"
1 change: 1 addition & 0 deletions charts/ccd-api-gateway-web/values.preview.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ nodejs:
image: ${IMAGE_NAME}
ingressHost: ${SERVICE_FQDN}
environment:
IDAM_OAUTH2_REDIRECT_URI_ALLOWLIST: "${SERVICE_FQDN}"
IDAM_OAUTH2_TOKEN_ENDPOINT: https://idam-api.aat.platform.hmcts.net/oauth2/token
IDAM_OAUTH2_LOGOUT_ENDPOINT: https://idam-api.aat.platform.hmcts.net/session/:token
IDAM_BASE_URL: https://idam-api.aat.platform.hmcts.net
Expand Down
1 change: 1 addition & 0 deletions charts/ccd-api-gateway-web/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ nodejs:
minReplicas: 8
environment:
IDAM_OAUTH2_CLIENT_ID: ccd_gateway
IDAM_OAUTH2_REDIRECT_URI_ALLOWLIST: "gateway-ccd.{{ .Values.global.environment }}.platform.hmcts.net"
CORS_ORIGIN_METHODS: GET,POST,OPTIONS,PUT,DELETE
IDAM_SERVICE_NAME: ccd_gw
SECURE_AUTH_COOKIE_ENABLED: true
Expand Down
1 change: 1 addition & 0 deletions config/custom-environment-variables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ idam:
token_endpoint: IDAM_OAUTH2_TOKEN_ENDPOINT
logout_endpoint: IDAM_OAUTH2_LOGOUT_ENDPOINT
client_id: IDAM_OAUTH2_CLIENT_ID
redirect_uri_allowlist: IDAM_OAUTH2_REDIRECT_URI_ALLOWLIST
address_lookup:
detect_proxy: ADDRESS_LOOKUP_DETECT_PROXY
url: ADDRESS_LOOKUP_URL
Expand Down
1 change: 1 addition & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ idam:
token_endpoint: http://localhost:5000/oauth2/token
logout_endpoint: http://localhost:5000/session/:token
client_id: ccd_gateway
redirect_uri_allowlist: "localhost,127.0.0.1"
address_lookup:
detect_proxy: false
url: https://api.os.uk/search/places/v1/postcode?postcode=${postcode}&key=${key}
Expand Down
2 changes: 2 additions & 0 deletions config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ appInsights:
enabled: false
idam:
base_url: http://test-idam:1234
oauth2:
redirect_uri_allowlist: "localhost"
secrets:
ccd:
ccd-api-gateway-oauth2-client-secret: ccd_gateway_secret
Expand Down
23 changes: 22 additions & 1 deletion test/oauth2/access-token-request.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ describe('Access Token Request', () => {
const REDIRECT_URN = 'localhost/redirect/to';
const REDIRECT_URL = 'https://localhost/redirect/to';
const UNDEFINED_URI = 'undefined:///oauth2redirect';
const DISALLOWED_URI = 'https://attacker.com/steal';
const AUTH_CODE = 'xyz789';
const REDIRECT_ALLOWLIST = 'localhost';

const REQUEST = sinonExpressMock.mockReq({
query: {
Expand Down Expand Up @@ -60,6 +62,7 @@ describe('Access Token Request', () => {
config = {
get: sinon.stub()
};
config.get.withArgs('idam.oauth2.redirect_uri_allowlist').returns(REDIRECT_ALLOWLIST);

fetch = fetchMock.sandbox().post(`begin:${TOKEN_ENDPOINT}`, SUCCESSFUL_RESPONSE);
accessTokenRequest = proxyquire('../../app/oauth2/access-token-request', {
Expand Down Expand Up @@ -134,10 +137,28 @@ describe('Access Token Request', () => {
config.get.withArgs('idam.oauth2.token_endpoint').returns(TOKEN_ENDPOINT);
try {
await accessTokenRequest(REQUEST_UNDEFINED_URI);
expect.fail('Expected error to be thrown');
} catch (error) {
expect(error.status).to.deep.equal(400);
expect(error.error).to.deep.equal('Bad Request');
expect(error.message).to.deep.equal('Redirect URI cannot start with undefined');
expect(error.message).to.deep.equal('Redirect URI is not permitted');
}
});

it('should reject redirect URIs with disallowed hosts.', async () => {
config.get.withArgs('idam.oauth2.client_id').returns(CLIENT_ID);
config.get.withArgs('secrets.ccd.ccd-api-gateway-oauth2-client-secret').returns(CLIENT_SECRET);
config.get.withArgs('idam.oauth2.token_endpoint').returns(TOKEN_ENDPOINT);
const REQUEST_DISALLOWED = sinonExpressMock.mockReq({
query: { code: AUTH_CODE, redirect_uri: DISALLOWED_URI }
});
try {
await accessTokenRequest(REQUEST_DISALLOWED);
expect.fail('Expected error to be thrown');
} catch (error) {
expect(error.status).to.deep.equal(400);
expect(error.error).to.deep.equal('Bad Request');
expect(error.message).to.deep.equal('Redirect URI is not permitted');
}
});
});