From e4e2834753921481d9b49854668917392f7f7908 Mon Sep 17 00:00:00 2001 From: Michael Newman Date: Wed, 16 Aug 2023 12:33:22 -0700 Subject: [PATCH] Convert TokenModel to an ES6 class and extract utils function for calculating lifetime --- lib/models/token-model.js | 99 ++++++++++++++++------------ lib/utils/date-util.js | 13 ++++ test/unit/models/token-model_test.js | 33 ++++++++++ test/unit/utils/date-util__test.js | 26 ++++++++ 4 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 lib/utils/date-util.js create mode 100644 test/unit/utils/date-util__test.js diff --git a/lib/models/token-model.js b/lib/models/token-model.js index 473c7ace..40dee37c 100644 --- a/lib/models/token-model.js +++ b/lib/models/token-model.js @@ -3,63 +3,78 @@ /** * Module dependencies. */ - const InvalidArgumentError = require('../errors/invalid-argument-error'); +const { getLifetimeFromExpiresAt } = require('../utils/date-util'); /** - * Constructor. + * The core model attributes allowed when allowExtendedTokenAttributes is false. */ +const modelAttributes = new Set([ + 'accessToken', + 'accessTokenExpiresAt', + 'refreshToken', + 'refreshTokenExpiresAt', + 'scope', + 'client', + 'user' +]); + +class TokenModel { + constructor(data = {}, options = {}) { + const { + accessToken, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + scope, + client, + user, + } = data; + + if (!accessToken) { + throw new InvalidArgumentError('Missing parameter: `accessToken`'); + } -const modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user']; - -function TokenModel(data, options) { - data = data || {}; + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } - if (!data.accessToken) { - throw new InvalidArgumentError('Missing parameter: `accessToken`'); - } + if (!user) { + throw new InvalidArgumentError('Missing parameter: `user`'); + } - if (!data.client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } + if (accessTokenExpiresAt && !(accessTokenExpiresAt instanceof Date)) { + throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); + } - if (!data.user) { - throw new InvalidArgumentError('Missing parameter: `user`'); - } + if (refreshTokenExpiresAt && !(refreshTokenExpiresAt instanceof Date)) { + throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); + } - if (data.accessTokenExpiresAt && !(data.accessTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`'); - } + this.accessToken = accessToken; + this.accessTokenExpiresAt = accessTokenExpiresAt; + this.client = client; + this.refreshToken = refreshToken; + this.refreshTokenExpiresAt = refreshTokenExpiresAt; + this.scope = scope; + this.user = user; - if (data.refreshTokenExpiresAt && !(data.refreshTokenExpiresAt instanceof Date)) { - throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`'); - } + if (accessTokenExpiresAt) { + this.accessTokenLifetime = getLifetimeFromExpiresAt(accessTokenExpiresAt); + } - this.accessToken = data.accessToken; - this.accessTokenExpiresAt = data.accessTokenExpiresAt; - this.client = data.client; - this.refreshToken = data.refreshToken; - this.refreshTokenExpiresAt = data.refreshTokenExpiresAt; - this.scope = data.scope; - this.user = data.user; + const { allowExtendedTokenAttributes } = options; - if (options && options.allowExtendedTokenAttributes) { - this.customAttributes = {}; + if (allowExtendedTokenAttributes) { + this.customAttributes = {}; - for (const key in data) { - if ( Object.prototype.hasOwnProperty.call(data, key) && (modelAttributes.indexOf(key) < 0)) { - this.customAttributes[key] = data[key]; - } + Object.keys(data).forEach(key => { + if (!modelAttributes.has(key)) { + this.customAttributes[key] = data[key]; + } + }); } } - - if(this.accessTokenExpiresAt) { - this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000); - } } -/** - * Export constructor. - */ - module.exports = TokenModel; diff --git a/lib/utils/date-util.js b/lib/utils/date-util.js new file mode 100644 index 00000000..4071a11f --- /dev/null +++ b/lib/utils/date-util.js @@ -0,0 +1,13 @@ +'use strict'; + +/** + * @param expiresAt {Date} The date at which something (e.g. a token) expires. + * @return {number} The number of seconds until the expiration date. + */ +function getLifetimeFromExpiresAt(expiresAt) { + return Math.floor((expiresAt - new Date()) / 1000); +} + +module.exports = { + getLifetimeFromExpiresAt, +}; diff --git a/test/unit/models/token-model_test.js b/test/unit/models/token-model_test.js index 7dcac615..19c00bf8 100644 --- a/test/unit/models/token-model_test.js +++ b/test/unit/models/token-model_test.js @@ -22,5 +22,38 @@ describe('Model', function() { model.accessTokenLifetime.should.a('number'); model.accessTokenLifetime.should.be.approximately(3600, 2); }); + + it('should throw if the required arguments are not provided', () => { + should.throw(() => { + new TokenModel({}); + }); + }); + + it('should ignore custom attributes if allowExtendedTokenAttributes is not specified as true', () => { + const model = new TokenModel({ + accessToken: 'token', + client: 'client', + user: 'user', + myCustomAttribute: 'myCustomValue' + }); + + should.not.exist(model['myCustomAttribute']); + should.not.exist(model['customAttributes']); + }); + + it('should set custom attributes on the customAttributes field if allowExtendedTokenAttributes is specified as true', () => { + const model = new TokenModel({ + accessToken: 'token', + client: 'client', + user: 'user', + myCustomAttribute: 'myCustomValue' + }, { + allowExtendedTokenAttributes: true + }); + + should.not.exist(model['myCustomAttribute']); + model['customAttributes'].should.be.an('object'); + model['customAttributes']['myCustomAttribute'].should.equal('myCustomValue'); + }); }); }); diff --git a/test/unit/utils/date-util__test.js b/test/unit/utils/date-util__test.js new file mode 100644 index 00000000..47b8e7d5 --- /dev/null +++ b/test/unit/utils/date-util__test.js @@ -0,0 +1,26 @@ +const dateUtil = require('../../../lib/utils/date-util'); + +const sinon = require('sinon'); +require('chai').should(); + +describe('DateUtil', function() { + describe('getLifetimeFromExpiresAt', () => { + const now = new Date('2023-01-01T00:00:00.000Z'); + + beforeEach(() => { + sinon.useFakeTimers(now); + }); + + it('should convert a valid expiration date into seconds from now', () => { + const expiresAt = new Date('2023-01-01T00:00:10.000Z'); + const lifetime = dateUtil.getLifetimeFromExpiresAt(expiresAt); + + lifetime.should.be.a('number'); + lifetime.should.be.approximately(10, 2); + }); + + afterEach(() => { + sinon.restore(); + }); + }); +});