feat(keys): Check lastAuthAt freshness when fetching key data. (#502) r=@vladikoff

This commit is contained in:
Ryan Kelly 2017-11-24 06:38:39 +11:00 коммит произвёл Vlad Filippov
Родитель 48ec2a359c
Коммит 855adee4fe
4 изменённых файлов: 54 добавлений и 3 удалений

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

@ -127,6 +127,12 @@ const conf = convict({
format: 'duration',
default: '15 minutes',
env: 'FXA_EXPIRATION_CODE'
},
keyDataAuth: {
doc: 'Key data can only be fetched if lastAuthAt is within this duration',
format: 'duration',
default: '1 hour',
env: 'FXA_EXPIRATION_KEY_DATA_AUTH'
}
},
refreshToken: {

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

@ -116,6 +116,7 @@ AppError.invalidAssertion = function invalidAssertion() {
message: 'Invalid assertion'
});
};
AppError.unknownCode = function unknownCode(code) {
return new AppError({
code: 400,
@ -264,4 +265,15 @@ AppError.missingPkceParameters = function missingPkceParameters() {
});
};
AppError.staleAuthAt = function staleAuthAt(authAt) {
return new AppError({
code: 401,
error: 'Bad Request',
errno: 119,
message: 'Stale authentication timestamp'
}, {
authAt: authAt
});
};
module.exports = AppError;

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

@ -11,6 +11,9 @@ const P = require('../promise');
const validators = require('../validators');
const verify = require('../browserid');
const Scope = require('../scope');
const config = require('../config');
const AUTH_EXPIRES_AFTER_MS = config.get('expiration.keyDataAuth');
/**
* We're using a static value for key material on purpose, in future this value can read from the DB.
@ -71,6 +74,10 @@ module.exports = {
const scopeResults = results[1];
const response = {};
if (assertionData['fxa-lastAuthAt'] < Date.now() - AUTH_EXPIRES_AFTER_MS) {
throw AppError.staleAuthAt(assertionData['fxa-lastAuthAt']);
}
scopeResults.forEach((keyScope) => {
response[keyScope.scope] = {
identifier: keyScope.scope,

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

@ -21,13 +21,25 @@ const assertSecurityHeaders = require('./lib/util').assertSecurityHeaders;
const USERID = unique(16).toString('hex');
const VEMAIL = unique(4).toString('hex') + '@mozilla.com';
const AUTH_AT = Date.now();
const STALE_AUTH_AT = Date.now() - (2 * 24 * 60 * 60 * 1000);
const VERIFY_GOOD = JSON.stringify({
status: 'okay',
email: USERID + '@' + config.get('browserid.issuer'),
issuer: config.get('browserid.issuer'),
idpClaims: {
'fxa-verifiedEmail': VEMAIL,
'fxa-lastAuthAt': 123456,
'fxa-lastAuthAt': AUTH_AT,
'fxa-generation': 123456
}
});
const VERIFY_GOOD_BUT_STALE = JSON.stringify({
status: 'okay',
email: USERID + '@' + config.get('browserid.issuer'),
issuer: config.get('browserid.issuer'),
idpClaims: {
'fxa-verifiedEmail': VEMAIL,
'fxa-lastAuthAt': STALE_AUTH_AT,
'fxa-generation': 123456
}
});
@ -1226,7 +1238,7 @@ describe('/v1', function() {
assert.equal(res.result.access_token.length,
config.get('unique.token') * 2);
assert.equal(res.result.scope, 'foo bar');
assert.equal(res.result.auth_at, 123456);
assert.equal(res.result.auth_at, AUTH_AT);
});
});
});
@ -1262,7 +1274,7 @@ describe('/v1', function() {
assert.equal(res.result.refresh_token.length,
config.get('unique.token') * 2);
assert.equal(res.result.scope, 'foo bar');
assert.equal(res.result.auth_at, 123456);
assert.equal(res.result.auth_at, AUTH_AT);
});
});
});
@ -2336,6 +2348,20 @@ describe('/v1', function() {
assert.equal(Object.keys(res.result).length, 0, 'no scoped keys');
});
});
it('fails for assertions with lastAuthAt too far in the past', () => {
genericRequest.payload.client_id = NO_KEY_SCOPES_CLIENT_ID;
mockAssertion().reply(200, VERIFY_GOOD_BUT_STALE);
return Server.api.post(genericRequest)
.then((res) => {
assert.equal(res.statusCode, 401);
assertSecurityHeaders(res);
const body = res.result;
assert.equal(body.errno, 119);
assert.equal(body.message, 'Stale authentication timestamp');
});
});
});
});