diff --git a/.kuzzlerc.sample b/.kuzzlerc.sample index d30986997b..f701f0dd86 100644 --- a/.kuzzlerc.sample +++ b/.kuzzlerc.sample @@ -149,6 +149,10 @@ // * gracePeriod: // Duration in ms during which a renewed jwt is still // considered valid + // * maxTTL: + // Maximum duration in milliseconds a token can be requested + // to be valid. + // If set to -1 (default), no maximum duration is set. // * secret: // String or buffer data containing either the secret for HMAC // algorithms, or the PEM encoded private key for RSA and ECDSA. @@ -158,6 +162,7 @@ "algorithm": "HS256", "expiresIn": "1h", "gracePeriod": 1000, + "maxTTL": -1, "secret": null }, // [default] diff --git a/default.config.js b/default.config.js index c75bdafde5..4ed50392bc 100644 --- a/default.config.js +++ b/default.config.js @@ -80,6 +80,7 @@ module.exports = { algorithm: 'HS256', expiresIn: '1h', gracePeriod: 1000, + maxTTL: -1, secret: null }, default: { diff --git a/lib/api/core/models/repositories/tokenRepository.js b/lib/api/core/models/repositories/tokenRepository.js index a633ae998b..827ebe78ac 100644 --- a/lib/api/core/models/repositories/tokenRepository.js +++ b/lib/api/core/models/repositories/tokenRepository.js @@ -29,6 +29,7 @@ const Token = require('../security/token'), Repository = require('./repository'), { + BadRequestError, InternalError: KuzzleInternalError, UnauthorizedError } = require('kuzzle-common-objects').errors; @@ -71,12 +72,10 @@ class TokenRepository extends Repository { /** * @param {User} user * @param {Request} request - * @param {object} opts + * @param {object} options * @returns {*} */ - generateToken(user, request, opts) { - const options = opts || {}; - + generateToken(user, request, options = {}) { if (!user || user._id === null) { return Bluebird.reject(new KuzzleInternalError('Unknown User : cannot generate token')); } @@ -94,6 +93,12 @@ class TokenRepository extends Repository { } const expiresIn = parseTimespan(options.expiresIn); + + if (this.kuzzle.config.security.jwt.maxTTL > -1 + && expiresIn > this.kuzzle.config.security.jwt.maxTTL) { + return Bluebird.reject(new BadRequestError('expiresIn value exceeds maximum allowed value')); + } + let encodedToken; try { diff --git a/test/api/core/models/repositories/tokenRepository.test.js b/test/api/core/models/repositories/tokenRepository.test.js index 1e7afda976..42730082c6 100644 --- a/test/api/core/models/repositories/tokenRepository.test.js +++ b/test/api/core/models/repositories/tokenRepository.test.js @@ -10,6 +10,7 @@ const RequestContext = require('kuzzle-common-objects').models.RequestContext, TokenRepository = require('../../../../../lib/api/core/models/repositories/tokenRepository'), { + BadRequestError, InternalError: KuzzleInternalError, UnauthorizedError } = require('kuzzle-common-objects').errors; @@ -198,6 +199,53 @@ describe('Test: repositories/tokenRepository', () => { return should(tokenRepository.generateToken(user, request)) .be.rejectedWith(KuzzleInternalError, {message: 'Unable to generate token for unknown user'}); }); + + it('should allow a big ttl if no maxTTL is set', () => { + const user = new User(); + user._id = 'id'; + + const request = new Request({}, { + user, + connectionId: 'connectionId' + }); + + return tokenRepository.generateToken(user, request, {expiresIn: '1000y'}) + .then(token => { + should(token).be.an.instanceOf(Token); + }); + }); + + it('should allow a ttl lower than the maxTTL', () => { + const user = new User(); + user._id = 'id'; + + const request = new Request({}, { + user, + connectionId: 'connectionId' + }); + + kuzzle.config.security.jwt.maxTTL = 42000; + + return tokenRepository.generateToken(user, request, {expiresIn: '30s'}) + .then(token => { + should(token).be.an.instanceOf(Token); + }); + }); + + it('should reject if the ttl exceeds the maxTTL', () => { + const user = new User(); + user._id = 'id'; + + const request = new Request({}, { + user, + connectionId: 'connectionId' + }); + + kuzzle.config.security.jwt.maxTTL = 42000; + + return should(tokenRepository.generateToken(user, request, {expiresIn: '1m'})) + .be.rejectedWith(BadRequestError, {message: 'expiresIn value exceeds maximum allowed value'}); + }); }); describe('#serializeToCache', () => {