Skip to content

Commit

Permalink
feat: respect x-forwarded-proto in auth (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
tripodsan authored Oct 26, 2022
1 parent 053e73e commit cae61e1
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 9 deletions.
19 changes: 12 additions & 7 deletions src/utils/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ export async function decodeIdToken(state, idp, idToken, lenient = false) {
*
* @param {PipelineState} state
* @param {PipelineRequest} req
* @return {string}
* @returns {{proto: (*|string), host: string}} the request host and protocol.
*/
function getRequestHost(state, req) {
function getRequestHostAndProto(state, req) {
// determine the location of 'this' document based on the xfh header. so that logins to
// .page stay on .page. etc. but fallback to the config.host if non set
let host = req.headers.get('x-forwarded-host');
Expand All @@ -91,8 +91,12 @@ function getRequestHost(state, req) {
if (!host) {
host = state.config.host;
}
state.log.info(`request host is: ${host}`);
return host;
const proto = req.headers.get('x-forwarded-proto') || 'https';
state.log.info(`request host is: ${host} (${proto})`);
return {
host,
proto,
};
}

/**
Expand Down Expand Up @@ -181,7 +185,7 @@ export class AuthInfo {

// determine the location of 'this' document based on the xfh header. so that logins to
// .page stay on .page. etc. but fallback to the config.host if non set
const host = getRequestHost(state, req);
const { host, proto } = getRequestHostAndProto(state, req);
if (!host) {
log.error('[auth] unable to create login redirect: no xfh or config.host.');
res.status = 401;
Expand All @@ -199,6 +203,7 @@ export class AuthInfo {
// this is our own login redirect, i.e. the current document
requestPath: state.info.path,
requestHost: host,
requestProto: proto,
}).encode();

url.searchParams.append('client_id', clientId);
Expand Down Expand Up @@ -239,9 +244,9 @@ export class AuthInfo {

// ensure that the request is made to the target host
if (req.params.state?.requestHost) {
const host = getRequestHost(state, req);
const { host } = getRequestHostAndProto(state, req);
if (host !== req.params.state.requestHost) {
const url = new URL(`https://${req.params.state.requestHost}/.auth`);
const url = new URL(`${req.params.state.requestProto}://${req.params.state.requestHost}/.auth`);
url.searchParams.append('state', req.params.rawState);
url.searchParams.append('code', req.params.code);
const location = state.createExternalLocation(url.href);
Expand Down
1 change: 1 addition & 0 deletions test/html-pipe.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('HTML Pipe Test', () => {
// this is our own login redirect, i.e. the current document
requestPath: '/en',
requestHost: 'www.hlx.live',
requestProto: 'https',
}).encode();

const req = new PipelineRequest('https://localhost/.auth', {
Expand Down
31 changes: 29 additions & 2 deletions test/utils/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ describe('AuthInfo tests', () => {
redirect_uri: 'https://login.hlx.page/.auth',
response_type: 'code',
scope: 'openid profile email',
state: 'eyJhbGciOiJub25lIn0.eyJyZXF1ZXN0UGF0aCI6Ii8iLCJyZXF1ZXN0SG9zdCI6Ind3dy5obHgubGl2ZSJ9.',
state: 'eyJhbGciOiJub25lIn0.eyJyZXF1ZXN0UGF0aCI6Ii8iLCJyZXF1ZXN0SG9zdCI6Ind3dy5obHgubGl2ZSIsInJlcXVlc3RQcm90byI6Imh0dHBzIn0.',
});
});

Expand All @@ -370,6 +370,30 @@ describe('AuthInfo tests', () => {
const reqState = new URL(res.headers.get('location')).searchParams.get('state');
assert.deepStrictEqual(decodeJwt(reqState), {
requestHost: 'www.hlx.live',
requestProto: 'https',
requestPath: '/',
});
});

it('redirects to the login page (xfh, proto)', async () => {
const authInfo = AuthInfo
.Default()
.withIdp(idpFakeTestIDP);

const state = new PipelineState({});
const req = new PipelineRequest('https://localhost', {
headers: {
'x-forwarded-host': 'localhost',
'x-forwarded-proto': 'http',
},
});
const res = new PipelineResponse();
await authInfo.redirectToLogin(state, req, res);
assert.strictEqual(res.status, 302);
const reqState = new URL(res.headers.get('location')).searchParams.get('state');
assert.deepStrictEqual(decodeJwt(reqState), {
requestHost: 'localhost',
requestProto: 'http',
requestPath: '/',
});
});
Expand All @@ -391,6 +415,7 @@ describe('AuthInfo tests', () => {
const reqState = new URL(res.headers.get('location')).searchParams.get('state');
assert.deepStrictEqual(decodeJwt(reqState), {
requestHost: 'bla.live',
requestProto: 'https',
requestPath: '/en/blog',
});
});
Expand Down Expand Up @@ -463,13 +488,14 @@ describe('AuthInfo tests', () => {
req.params.state = {
requestPath: '/en',
requestHost: 'localhost',
requestProto: 'http',
};
req.params.rawState = 'raw';

const res = new PipelineResponse();
await authInfo.exchangeToken(state, req, res);
assert.strictEqual(res.status, 302);
assert.strictEqual(res.headers.get('location'), 'https://localhost/.auth?state=raw&code=1234');
assert.strictEqual(res.headers.get('location'), 'http://localhost/.auth?state=raw&code=1234');
});

it('exchangeToken fetches the token', async () => {
Expand Down Expand Up @@ -502,6 +528,7 @@ describe('AuthInfo tests', () => {
req.params.state = {
requestPath: '/en',
requestHost: 'localhost',
requestProto: 'https',
};
req.headers.set('x-forwarded-host', 'localhost');

Expand Down

0 comments on commit cae61e1

Please sign in to comment.