diff --git a/lib/KeyStore.ts b/lib/KeyStore.ts index 0b445ce..33c43ef 100644 --- a/lib/KeyStore.ts +++ b/lib/KeyStore.ts @@ -7,26 +7,50 @@ import { create_salt } from '@sapphirecode/crypto-helper'; +interface Key { + key: string; + valid_until: number; + timeout: NodeJS.Timeout; +} + class KeyStore { - private _keys: Record = {}; + private _keys: Record = {}; + + private set_timeout (index: string, valid_for: number): NodeJS.Timeout { + return setTimeout (() => { + delete this._keys[index]; + }, (valid_for + 5) * 1000); + } public get_key (iat: number, valid_for = 0): string { - const key = Math.floor (iat / 60) + const index = Math.floor (iat / 60) .toFixed (0); - if (typeof this._keys[key] === 'string') - return this._keys[key]; + const valid_until = (new Date) + .getTime () + (valid_for * 1000); + + if (typeof this._keys[index] !== 'undefined') { + const key = this._keys[index]; + if (valid_for !== 0 && key.valid_until < valid_until) { + clearTimeout (key.timeout); + key.timeout = this.set_timeout (index, valid_for); + key.valid_until = valid_until; + } + return key.key; + } if (valid_for !== 0) { - if ((iat + valid_for) * 1000 < (new Date) + if ((iat + 1) * 1000 < (new Date) .getTime ()) throw new Error ('cannot create already expired keys'); - this._keys[key] = create_salt (); - setTimeout (() => { - delete this._keys[key]; - }, (valid_for + 5) * 1000); - return this._keys[key]; + this._keys[index] = { + key: create_salt (), + timeout: this.set_timeout (index, valid_for), + valid_until + }; + + return this._keys[index].key; } throw new Error ('key could not be found'); diff --git a/test/spec/KeyStore.ts b/test/spec/KeyStore.ts index 5d539a5..6ea6efe 100644 --- a/test/spec/KeyStore.ts +++ b/test/spec/KeyStore.ts @@ -7,6 +7,8 @@ import ks from '../../lib/KeyStore'; +const frame = 60; + /* eslint-disable-next-line max-lines-per-function */ describe ('key store', () => { beforeAll (() => { @@ -23,7 +25,7 @@ describe ('key store', () => { it ('should generate a new key', () => { const iat = (new Date) .getTime () / 1000; - const duration = 600; + const duration = 10 * frame; const key = ks.get_key (iat, duration); expect (typeof key) .toEqual ('string'); @@ -39,17 +41,17 @@ describe ('key store', () => { }); it ('should return the same key on a different time', () => { - const key = ks.get_key (keys[0].iat + 30); + const key = ks.get_key (keys[0].iat + (frame / 2)); expect (key) .toEqual (keys[0].key); }); - it ('should generate a new key after 60 seconds', () => { + it ('should generate a new key after time frame is over', () => { jasmine.clock () - .tick (60000); + .tick (frame * 1000); const iat = (new Date) .getTime () / 1000; - const duration = 600; + const duration = 10 * frame; const key = ks.get_key (iat, duration); expect (typeof key) .toEqual ('string'); @@ -69,13 +71,13 @@ describe ('key store', () => { }); it ('should throw on non existing key', () => { - expect (() => ks.get_key (keys[1].iat + 60)) + expect (() => ks.get_key (keys[1].iat + frame)) .toThrowError ('key could not be found'); }); it ('should delete a key after it expires', () => { jasmine.clock () - .tick (600000); + .tick (10000 * frame); expect (() => ks.get_key (keys[0].iat)) .toThrowError ('key could not be found'); }); @@ -88,12 +90,40 @@ describe ('key store', () => { it ('should reject key generation of expired keys', () => { const iat = ((new Date) - .getTime () / 1000) - 10; + .getTime () / 1000) - 2; const duration = 5; expect (() => ks.get_key (iat, duration)) .toThrowError ('cannot create already expired keys'); }); + it ('key should live as long as the longest created token', () => { + const base = new Date; + base.setSeconds (2, 0); + jasmine.clock () + .mockDate (base); + jasmine.clock () + .tick (24 * 60 * 60 * 1000); + const iat = (new Date) + .getTime () / 1000; + const duration1 = frame; + const duration2 = frame * 10; + + const key1 = ks.get_key (iat, duration1); + const step = 0.9 * frame; + jasmine.clock () + .tick (step * 1000); + const key2 = ks.get_key (iat + step, duration2); + expect (key1) + .toEqual (key2); + jasmine.clock () + .tick (5000 * frame); + const keyv = ks.get_key (iat + step); + expect (keyv) + .toEqual (key1); + }); + + // required use case: insert keys for verification of old tokens + afterAll (() => { jasmine.clock () .tick (24 * 60 * 60 * 1000);