feat(2fa): check acr values during authorization flow

This commit is contained in:
Vijay Budhram 2018-10-02 11:22:38 -04:00
Родитель 57cccb51fa
Коммит c20682a242
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: D49B640E659DCB9E
3 изменённых файлов: 57 добавлений и 3 удалений

Просмотреть файл

@ -276,4 +276,13 @@ AppError.staleAuthAt = function staleAuthAt(authAt) {
}); });
}; };
AppError.mismatchAcr = function mismatchAcr(foundValue) {
return new AppError({
code: 400,
error: 'Bad Request',
errno: 120,
message: 'Mismatch acr value'
}, {foundValue});
};
module.exports = AppError; module.exports = AppError;

Просмотреть файл

@ -22,6 +22,8 @@ const TOKEN = 'token';
const ACCESS_TYPE_ONLINE = 'online'; const ACCESS_TYPE_ONLINE = 'online';
const ACCESS_TYPE_OFFLINE = 'offline'; const ACCESS_TYPE_OFFLINE = 'offline';
const ACR_VALUE_AAL2 = 'AAL2';
const PKCE_SHA256_CHALLENGE_METHOD = 'S256'; // This server only supports S256 PKCE, no 'plain' const PKCE_SHA256_CHALLENGE_METHOD = 'S256'; // This server only supports S256 PKCE, no 'plain'
const PKCE_CODE_CHALLENGE_LENGTH = 43; const PKCE_CODE_CHALLENGE_LENGTH = 43;
@ -189,7 +191,8 @@ module.exports = {
is: CODE, is: CODE,
then: Joi.optional(), then: Joi.optional(),
otherwise: Joi.forbidden() otherwise: Joi.forbidden()
}) }),
acr_values: Joi.string().max(256).optional()
} }
}, },
response: { response: {
@ -228,6 +231,16 @@ module.exports = {
exitEarly = true; exitEarly = true;
throw AppError.invalidAssertion(); throw AppError.invalidAssertion();
} }
// Check to see if the acr value requested by oauth matches what is expected
const acrValues = req.payload.acr_values;
if (acrValues) {
const acrTokens = acrValues.split('\s+');
if (acrTokens.includes(ACR_VALUE_AAL2) && ! (claims['fxa-aal'] >= 2)) {
throw AppError.mismatchAcr(claims['fxa-aal']);
}
}
// Any request for a key-bearing scope should be using a verified token. // Any request for a key-bearing scope should be using a verified token.
// Double-check that here as a defense-in-depth measure. // Double-check that here as a defense-in-depth measure.
if (! claims['fxa-tokenVerified']) { if (! claims['fxa-tokenVerified']) {

Просмотреть файл

@ -119,7 +119,8 @@ function authParams(params, options) {
assertion: AN_ASSERTION, assertion: AN_ASSERTION,
client_id: options.clientId || clientId, client_id: options.clientId || clientId,
state: '1', state: '1',
scope: 'a' scope: 'a',
acr_values: options.acr_values || undefined
}; };
params = params || {}; params = params || {};
@ -1001,7 +1002,6 @@ describe('/v1', function() {
}); });
}); });
describe('response', function() { describe('response', function() {
describe('with a trusted client', function() { describe('with a trusted client', function() {
it('should redirect to the redirect_uri', function() { it('should redirect to the redirect_uri', function() {
@ -1025,6 +1025,38 @@ describe('/v1', function() {
}); });
}); });
describe('check acr payload', () => {
it('should throw error if mismatch with claims', () => {
const options = {aal: 1};
const payload = {acr_values: 'AAL2'};
mockAssertion().reply(200, mockVerifierResult(options));
return Server.api.post({
url: '/authorization',
payload: authParams(payload)
}).then(function (res) {
assert.equal(res.statusCode, 400);
assertSecurityHeaders(res);
assert.equal(res.result.message, 'Mismatch acr value');
assert.equal(res.result.errno, 120, 'correct errno');
});
});
it('process request when correct acr_values in claims', () => {
const options = {aal: 2};
const payload = {acr_values: 'AAL2'};
mockAssertion().reply(200, mockVerifierResult(options));
return Server.api.post({
url: '/authorization',
payload: authParams(payload)
}).then(function (res) {
assert.equal(res.statusCode, 200);
assertSecurityHeaders(res);
assert.ok(res.result.code, 'code set');
assert.ok(res.result.redirect, 'redirect set');
assert.equal(res.result.state, 1, 'correct state');
});
});
});
}); });
describe('/token', function() { describe('/token', function() {