diff --git a/lib/KeyStore.ts b/lib/KeyStore.ts index 63bd5d8..147c42d 100644 --- a/lib/KeyStore.ts +++ b/lib/KeyStore.ts @@ -42,30 +42,32 @@ async function create_key (valid_for: number) { }; } -function garbage_collect (set: KeyStoreData): void { - const time = (new Date) - .getTime (); - for (const index of Object.keys (set)) { - const entry = set[index]; - if (typeof entry.private_key !== 'undefined' - && entry.private_key.valid_until < time - ) - delete entry.private_key; - if (entry.public_key.valid_until < time) - delete set[index]; - } -} - class KeyStore { private _keys: KeyStoreData = {}; private _interval: NodeJS.Timeout; public constructor () { this._interval = setInterval (() => { - garbage_collect (this._keys); + this.garbage_collect (); }, renew_interval); } + private garbage_collect (set: KeyStoreData = this._keys): void { + const time = (new Date) + .getTime (); + const keys = Object.keys (set); + for (const index of keys) { + const entry = set[index]; + if (typeof entry.private_key !== 'undefined' + && entry.private_key.valid_until < time + ) + delete entry.private_key; + + if (entry.public_key.valid_until < time) + delete set[index]; + } + } + public async get_sign_key (iat: number, valid_for: number): Promise { if (valid_for <= 0) throw new Error ('cannot create infinitely valid key'); @@ -87,7 +89,7 @@ class KeyStore { return key.private_key?.key as string; } - this._keys[index] = await create_key (valid_until); + this._keys[index] = await create_key (valid_for); return this._keys[index].private_key?.key as string; } @@ -102,7 +104,7 @@ class KeyStore { } public export_verification_data (): KeyStoreData { - garbage_collect (this._keys); + this.garbage_collect (); const out: KeyStoreData = {}; for (const index of Object.keys (this._keys)) out[index] = { public_key: this._keys[index].public_key }; @@ -112,7 +114,7 @@ class KeyStore { public import_verification_data (data: KeyStoreData): void { const import_set = { ...data }; - garbage_collect (import_set); + this.garbage_collect (import_set); // TODO: import } diff --git a/test/Helper.ts b/test/Helper.ts index 9d0d9b6..f60bcf2 100644 --- a/test/Helper.ts +++ b/test/Helper.ts @@ -7,6 +7,7 @@ /* eslint-disable no-console */ import http from 'http'; +import { type } from 'os'; import ks from '../lib/KeyStore'; export class Response extends http.IncomingMessage { @@ -46,24 +47,35 @@ export function modify_signature (signature: string): string { /* eslint-disable dot-notation */ export function assert_keystore_state (): void { - if (Object.keys (ks['_keys']).length !== 0) { + const set = ks['_keys']; + const keys = Object.keys (set); + if (keys.length !== 0) { + const has_sign = keys.filter ( + (v) => typeof set[v].private_key !== 'undefined' + ).length; console.warn ('keystore gc not running!'); + console.warn (`${keys.length} keys with ${has_sign} signature keys left`); ks['_keys'] = {}; } } /* eslint-enable dot-notation */ -export function flush_routine (install_clock = true):void { - if (install_clock) { - jasmine.clock () - .install (); - } +export function clock_setup ():void { + assert_keystore_state (); + + const date = (new Date); + date.setSeconds (2, 0); jasmine.clock () - .mockDate (new Date); + .install (); + jasmine.clock () + .mockDate (date); +} + +export function clock_finalize ():void { jasmine.clock () .tick (30 * 24 * 60 * 60 * 1000); - if (install_clock) { - jasmine.clock () - .uninstall (); - } + // eslint-disable-next-line dot-notation + ks['garbage_collect'] (); + jasmine.clock () + .uninstall (); } diff --git a/test/spec/AuthHandler.ts b/test/spec/AuthHandler.ts index 751e2cc..2a48e46 100644 --- a/test/spec/AuthHandler.ts +++ b/test/spec/AuthHandler.ts @@ -9,7 +9,8 @@ import http, { IncomingMessage, ServerResponse } from 'http'; import { to_b64 } from '@sapphirecode/encoding-helper'; import auth from '../../lib/Authority'; import { - assert_keystore_state, flush_routine, + clock_finalize, + clock_setup, get, modify_signature, Response } from '../Helper'; import { create_auth_handler } from '../../lib/index'; @@ -52,8 +53,7 @@ describe ('auth handler', () => { let server: http.Server|null = null; // eslint-disable-next-line max-lines-per-function beforeAll (() => { - flush_routine (); - assert_keystore_state (); + clock_setup (); const ah = create_auth_handler ((req) => { if (!req.is_basic && !req.is_bearer) { @@ -100,19 +100,13 @@ describe ('auth handler', () => { ah (req, res); }); server.listen (3000); - - jasmine.clock () - .install (); - jasmine.clock () - .mockDate (new Date); }); afterAll (() => { if (server === null) throw new Error ('server is null'); server.close (); - jasmine.clock () - .uninstall (); + clock_finalize (); }); it ('auth test sequence', async () => { diff --git a/test/spec/Authority.ts b/test/spec/Authority.ts index a0e130a..58f6d71 100644 --- a/test/spec/Authority.ts +++ b/test/spec/Authority.ts @@ -8,24 +8,19 @@ import auth from '../../lib/Authority'; import bl from '../../lib/Blacklist'; import { - assert_keystore_state, - flush_routine, modify_signature + clock_finalize, + clock_setup, + modify_signature } from '../Helper'; // eslint-disable-next-line max-lines-per-function describe ('authority', () => { beforeEach (() => { - jasmine.clock () - .install (); - jasmine.clock () - .mockDate (new Date); + clock_setup (); }); afterEach (() => { - flush_routine (false); - assert_keystore_state (); - jasmine.clock () - .uninstall (); + clock_finalize (); }); it ('should create an access token', async () => { diff --git a/test/spec/Blacklist.ts b/test/spec/Blacklist.ts index 142c99f..bfa56d5 100644 --- a/test/spec/Blacklist.ts +++ b/test/spec/Blacklist.ts @@ -6,19 +6,16 @@ */ import blacklist from '../../lib/Blacklist'; +import { clock_finalize, clock_setup } from '../Helper'; // eslint-disable-next-line max-lines-per-function describe ('blacklist', () => { beforeAll (() => { - jasmine.clock () - .install (); - jasmine.clock () - .mockDate (new Date); + clock_setup (); }); afterAll (() => { - jasmine.clock () - .uninstall (); + clock_finalize (); }); it ('should validate any string', () => { diff --git a/test/spec/Gateway.ts b/test/spec/Gateway.ts index 902572c..adf0247 100644 --- a/test/spec/Gateway.ts +++ b/test/spec/Gateway.ts @@ -9,19 +9,14 @@ import http from 'http'; import { create_gateway } from '../../lib/index'; import authority from '../../lib/Authority'; import blacklist from '../../lib/Blacklist'; -import { assert_keystore_state, flush_routine, get } from '../Helper'; +import { clock_finalize, clock_setup, get } from '../Helper'; // eslint-disable-next-line max-lines-per-function describe ('gateway', () => { let server: http.Server|null = null; beforeAll (() => { - flush_routine (); - assert_keystore_state (); - jasmine.clock () - .install (); - jasmine.clock () - .mockDate (new Date); + clock_setup (); const g = create_gateway ({ redirect_url: 'http://localhost/auth', @@ -44,8 +39,7 @@ describe ('gateway', () => { throw new Error ('server is null'); server.close (); - jasmine.clock () - .uninstall (); + clock_finalize (); }); it ('should redirect any unauthorized request', async () => { diff --git a/test/spec/KeyStore.ts b/test/spec/KeyStore.ts index b4ac6d0..60759de 100644 --- a/test/spec/KeyStore.ts +++ b/test/spec/KeyStore.ts @@ -6,21 +6,18 @@ */ import ks from '../../lib/KeyStore'; -import { assert_keystore_state, flush_routine } from '../Helper'; +import { clock_finalize, clock_setup } from '../Helper'; const frame = 60; /* eslint-disable-next-line max-lines-per-function */ describe ('key store', () => { beforeAll (() => { - flush_routine (); - assert_keystore_state (); - jasmine.clock () - .install (); - const base_date = (new Date); - base_date.setSeconds (2); - jasmine.clock () - .mockDate (base_date); + clock_setup (); + }); + + afterAll (() => { + clock_finalize (); }); const keys: {key:string, sign:string, iat:number}[] = []; @@ -91,8 +88,11 @@ describe ('key store', () => { }); it ('should delete a key after it expires', () => { + // go to 10 frames + 1ms after key creation jasmine.clock () - .tick (10000 * frame); + .tick ((frame * 9e3) + 1); + // eslint-disable-next-line dot-notation + ks['garbage_collect'] (); expect (() => ks.get_key (keys[0].iat)) .toThrowError ('key could not be found'); }); @@ -117,12 +117,8 @@ describe ('key store', () => { }); it ('key should live as long as the longest created token', async () => { - const base = new Date; - base.setSeconds (2, 0); jasmine.clock () - .mockDate (base); - jasmine.clock () - .tick (24 * 60 * 60 * 1000); + .tick (frame * 10e3); const iat = (new Date) .getTime () / 1000; const duration1 = frame; @@ -151,9 +147,4 @@ describe ('key store', () => { }); // TODO: required use case: insert keys for verification of old tokens - - afterAll (() => { - jasmine.clock () - .uninstall (); - }); });