This commit is contained in:
		| @@ -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<string> { | ||||
|     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 | ||||
|   } | ||||
|   | ||||
| @@ -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 (); | ||||
| } | ||||
|   | ||||
| @@ -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 () => { | ||||
|   | ||||
| @@ -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 () => { | ||||
|   | ||||
| @@ -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', () => { | ||||
|   | ||||
| @@ -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 () => { | ||||
|   | ||||
| @@ -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 (); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user