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