This commit is contained in:
parent
122bd7b574
commit
fd26975559
@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Verification Keys can now be synchronized through redis
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Logout function
|
||||
|
10
README.md
10
README.md
@ -1,6 +1,6 @@
|
||||
# auth-server-helper
|
||||
|
||||
version: 3.2.x
|
||||
version: 3.3.x
|
||||
|
||||
customizable and simple authentication
|
||||
|
||||
@ -196,6 +196,14 @@ const export = keystore.export_verification_data();
|
||||
keystore.import_verification_data(export);
|
||||
```
|
||||
|
||||
These keys can also be live synchronized with redis to allow sessions to be shared between servers
|
||||
|
||||
```js
|
||||
const {keystore} = require('@sapphirecode/auth-server-helper');
|
||||
|
||||
keystore.sync_redis('redis://localhost');
|
||||
```
|
||||
|
||||
### Exporting and importing blacklist entries across server instances
|
||||
|
||||
```js
|
||||
|
@ -1,13 +1,11 @@
|
||||
|
||||
{
|
||||
"spec_dir": "test",
|
||||
"spec_dir": "dist/test",
|
||||
"spec_files": [
|
||||
"spec/*.js",
|
||||
"spec/*.ts"
|
||||
"spec/*.js"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/*.js",
|
||||
"helpers/*.ts"
|
||||
"helpers/*.js"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
|
@ -231,12 +231,12 @@ interface CreateHandlerOptions {
|
||||
type ProcessRequestOptions = Omit<CreateHandlerOptions, 'parse_body'>
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
function process_request (
|
||||
async function process_request (
|
||||
request: AuthRequest,
|
||||
token: RegExpExecArray | null,
|
||||
default_handler: AuthRequestHandler,
|
||||
options?: ProcessRequestOptions
|
||||
): Promise<void> | void {
|
||||
): Promise<void> {
|
||||
if (token === null)
|
||||
return default_handler (request);
|
||||
|
||||
@ -259,7 +259,7 @@ function process_request (
|
||||
request.is_bearer = true;
|
||||
request.token = token?.groups?.token;
|
||||
|
||||
const token_data = auth.verify (request.token as string);
|
||||
const token_data = await auth.verify (request.token as string);
|
||||
|
||||
if (!token_data.valid)
|
||||
return default_handler (request);
|
||||
|
@ -41,7 +41,7 @@ interface SignatureOptions
|
||||
}
|
||||
|
||||
class Authority {
|
||||
public verify (key: string): VerificationResult {
|
||||
public async verify (key: string): Promise<VerificationResult> {
|
||||
logger ('verifying token');
|
||||
const result: VerificationResult = {
|
||||
authorized: false,
|
||||
@ -49,7 +49,7 @@ class Authority {
|
||||
type: 'none',
|
||||
id: ''
|
||||
};
|
||||
const data = verify_signature_get_info (
|
||||
const data = await verify_signature_get_info (
|
||||
key,
|
||||
(info) => {
|
||||
try {
|
||||
|
@ -17,7 +17,8 @@ const logger = debug ('gateway');
|
||||
type AnyFunc = (...args: unknown[]) => unknown;
|
||||
type Gateway = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse, next: AnyFunc
|
||||
res: ServerResponse,
|
||||
next: AnyFunc
|
||||
) => unknown;
|
||||
|
||||
interface RefreshSettings extends AccessSettings {
|
||||
@ -78,7 +79,7 @@ class GatewayClass {
|
||||
return auth.groups?.data;
|
||||
}
|
||||
|
||||
public try_access (req: IncomingMessage): boolean {
|
||||
public async try_access (req: IncomingMessage): Promise<boolean> {
|
||||
logger ('authenticating incoming request');
|
||||
let auth = this.get_header_auth (req);
|
||||
if (auth === null)
|
||||
@ -88,7 +89,7 @@ class GatewayClass {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ver = authority.verify (auth);
|
||||
const ver = await authority.verify (auth);
|
||||
|
||||
logger ('setting connection info');
|
||||
const con = req.connection as unknown as Record<string, unknown>;
|
||||
@ -119,20 +120,20 @@ class GatewayClass {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ver = authority.verify (refresh);
|
||||
const ver = await authority.verify (refresh);
|
||||
if (ver.type === 'refresh_token' && ver.valid) {
|
||||
logger ('refresh token valid, generating new tokens');
|
||||
const auth_request = new AuthRequest (
|
||||
req,
|
||||
res,
|
||||
''
|
||||
, this._options.cookie,
|
||||
'',
|
||||
this._options.cookie,
|
||||
this._options.refresh_cookie
|
||||
);
|
||||
const refresh_result = await auth_request.allow_access ({
|
||||
...this._options.refresh_settings,
|
||||
data: ver.data,
|
||||
leave_open: true
|
||||
data: ver.data,
|
||||
leave_open: true
|
||||
});
|
||||
|
||||
logger ('setting connection info');
|
||||
@ -155,7 +156,7 @@ class GatewayClass {
|
||||
res: ServerResponse
|
||||
): Promise<boolean> {
|
||||
logger ('trying to authenticate http request');
|
||||
if (this.try_access (req)) {
|
||||
if (await this.try_access (req)) {
|
||||
logger ('authenticated via access_token');
|
||||
return true;
|
||||
}
|
||||
@ -183,7 +184,7 @@ class GatewayClass {
|
||||
return this.redirect (res);
|
||||
}
|
||||
|
||||
public logout (req: IncomingMessage): void {
|
||||
public async logout (req: IncomingMessage): Promise<void> {
|
||||
const l = logger.extend ('logout');
|
||||
l ('invalidating all submitted tokens');
|
||||
const auth_strings = [
|
||||
@ -191,10 +192,13 @@ class GatewayClass {
|
||||
extract_cookie (this._options.cookie?.name, req.headers.cookie),
|
||||
extract_cookie (this._options.refresh_cookie?.name, req.headers.cookie)
|
||||
];
|
||||
const tokens = auth_strings
|
||||
.filter ((v) => v !== null)
|
||||
.map ((v) => authority.verify (v as string))
|
||||
.filter ((v) => v.valid);
|
||||
const tokens = (
|
||||
await Promise.all (
|
||||
auth_strings
|
||||
.filter ((v) => v !== null)
|
||||
.map ((v) => authority.verify (v as string))
|
||||
)
|
||||
).filter ((v) => v.valid);
|
||||
|
||||
l ('found %d tokens: %O', tokens.length, tokens);
|
||||
|
||||
@ -210,10 +214,4 @@ export default function create_gateway (options: GatewayOptions): Gateway {
|
||||
return g.process_request.bind (g);
|
||||
}
|
||||
|
||||
export {
|
||||
AnyFunc,
|
||||
Gateway,
|
||||
GatewayOptions,
|
||||
GatewayClass,
|
||||
RefreshSettings
|
||||
};
|
||||
export { AnyFunc, Gateway, GatewayOptions, GatewayClass, RefreshSettings };
|
||||
|
16
lib/Key.ts
Normal file
16
lib/Key.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export interface Key {
|
||||
key: string;
|
||||
valid_until: number;
|
||||
}
|
||||
|
||||
export interface LabelledKey extends Key {
|
||||
index: string;
|
||||
}
|
||||
|
||||
export interface KeyPair {
|
||||
private_key?: Key;
|
||||
public_key: Key;
|
||||
}
|
||||
|
||||
export type KeyStoreData = Record<string, KeyPair>;
|
||||
export type KeyStoreExport = LabelledKey[];
|
@ -8,49 +8,18 @@
|
||||
import { generate_keypair, random_hex } from '@sapphirecode/crypto-helper';
|
||||
import { to_b58 } from '@sapphirecode/encoding-helper';
|
||||
import { debug } from './debug';
|
||||
import { KeyStoreData, KeyStoreExport } from './Key';
|
||||
import { redis } from './Redis';
|
||||
|
||||
const logger = debug ('keystore');
|
||||
|
||||
const renew_interval = 3600;
|
||||
|
||||
interface Key {
|
||||
key: string;
|
||||
valid_until: number;
|
||||
}
|
||||
|
||||
interface LabelledKey extends Key {
|
||||
index: string;
|
||||
}
|
||||
|
||||
interface KeyPair {
|
||||
private_key?: Key;
|
||||
public_key: Key;
|
||||
}
|
||||
|
||||
type KeyStoreData = Record<string, KeyPair>;
|
||||
type KeyStoreExport = LabelledKey[];
|
||||
|
||||
async function create_key (valid_for: number) {
|
||||
logger ('generating new key');
|
||||
const time = (new Date)
|
||||
.getTime ();
|
||||
const pair = await generate_keypair ();
|
||||
return {
|
||||
private_key: {
|
||||
key: pair.private_key,
|
||||
valid_until: time + (renew_interval * 1000)
|
||||
},
|
||||
public_key: {
|
||||
key: pair.public_key,
|
||||
valid_until: time + (valid_for * 1000)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class KeyStore {
|
||||
private _keys: KeyStoreData = {};
|
||||
private _interval: NodeJS.Timeout;
|
||||
private _instance: string;
|
||||
private _sync_redis = false;
|
||||
|
||||
public get instance_id (): string {
|
||||
return this._instance;
|
||||
@ -69,6 +38,27 @@ class KeyStore {
|
||||
.toFixed (0);
|
||||
}
|
||||
|
||||
private async create_key (index: string, valid_for: number): Promise<void> {
|
||||
const log = logger.extend ('create_key');
|
||||
log ('generating new key');
|
||||
const time = (new Date)
|
||||
.getTime ();
|
||||
const pair = await generate_keypair ();
|
||||
const result = {
|
||||
private_key: {
|
||||
key: pair.private_key,
|
||||
valid_until: time + (renew_interval * 1000)
|
||||
},
|
||||
public_key: {
|
||||
key: pair.public_key,
|
||||
valid_until: time + (valid_for * 1000)
|
||||
}
|
||||
};
|
||||
if (this._sync_redis)
|
||||
await redis.set_key ({ ...result.public_key, index });
|
||||
this._keys[index] = result;
|
||||
}
|
||||
|
||||
private garbage_collect (): void {
|
||||
const time = (new Date)
|
||||
.getTime ();
|
||||
@ -128,19 +118,26 @@ class KeyStore {
|
||||
}
|
||||
|
||||
logger ('key does not exist, creating a new one');
|
||||
this._keys[index] = await create_key (valid_for);
|
||||
await this.create_key (index, valid_for);
|
||||
return this._keys[index].private_key?.key as string;
|
||||
}
|
||||
|
||||
public get_key (iat: number, instance?: string): string {
|
||||
public async get_key (iat: number, instance?: string): Promise<string> {
|
||||
logger ('querying public key from %s for timestamp %d', instance, iat);
|
||||
const index = this.get_index (iat, instance);
|
||||
|
||||
if (typeof this._keys[index] === 'undefined')
|
||||
let key = null;
|
||||
|
||||
if (typeof this._keys[index] === 'undefined') {
|
||||
if (this._sync_redis)
|
||||
key = await redis.get_key (index);
|
||||
}
|
||||
else { key = this._keys[index].public_key; }
|
||||
|
||||
if (key === null)
|
||||
throw new Error ('key could not be found');
|
||||
|
||||
const key = this._keys[index];
|
||||
return key.public_key.key;
|
||||
return key.key;
|
||||
}
|
||||
|
||||
public export_verification_data (): KeyStoreExport {
|
||||
@ -172,9 +169,16 @@ class KeyStore {
|
||||
logger ('resetting keystore');
|
||||
this._instance = to_b58 (random_hex (16), 'hex');
|
||||
this._keys = {};
|
||||
this._sync_redis = false;
|
||||
redis.disconnect ();
|
||||
}
|
||||
|
||||
public sync_redis (url: string): void {
|
||||
redis.connect (url);
|
||||
this._sync_redis = true;
|
||||
}
|
||||
}
|
||||
|
||||
const ks: KeyStore = (new KeyStore);
|
||||
export default ks;
|
||||
export { KeyStore, Key, LabelledKey, KeyStoreExport };
|
||||
export { KeyStore };
|
||||
|
86
lib/Redis.ts
Normal file
86
lib/Redis.ts
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, August 2022
|
||||
*/
|
||||
|
||||
import IORedis from 'ioredis';
|
||||
import { debug } from './debug';
|
||||
import { LabelledKey } from './Key';
|
||||
|
||||
const logger = debug ('redis');
|
||||
|
||||
export class Redis {
|
||||
private _redis: IORedis | null = null;
|
||||
|
||||
public connect (url: string): void {
|
||||
const log = logger.extend ('connect');
|
||||
log ('connecting to redis instance %s', url);
|
||||
if (this._redis !== null) {
|
||||
log ('disconnecting existing redis client');
|
||||
this.disconnect ();
|
||||
}
|
||||
|
||||
this._redis = new IORedis (url);
|
||||
this._redis.on ('connect', () => {
|
||||
log ('connected');
|
||||
});
|
||||
this._redis.on ('ready', () => {
|
||||
log ('ready');
|
||||
});
|
||||
this._redis.on ('error', (err) => {
|
||||
log ('error %o', err);
|
||||
});
|
||||
this._redis.on ('reconnecting', () => {
|
||||
log ('reconnecting');
|
||||
});
|
||||
this._redis.on ('end', () => {
|
||||
log ('connection ended');
|
||||
});
|
||||
}
|
||||
|
||||
public disconnect (): void {
|
||||
const log = logger.extend ('disconnect');
|
||||
log ('disconnecting redis client');
|
||||
if (this._redis === null) {
|
||||
log ('redis is inactive, skipping');
|
||||
return;
|
||||
}
|
||||
this._redis.quit ();
|
||||
this._redis = null;
|
||||
log ('done');
|
||||
}
|
||||
|
||||
public async set_key (key: LabelledKey): Promise<void> {
|
||||
const log = logger.extend ('set_key');
|
||||
log ('trying to set key %s to redis', key.index);
|
||||
if (this._redis === null) {
|
||||
log ('redis is inactive, skipping');
|
||||
return;
|
||||
}
|
||||
const valid_for = (key.valid_until - (new Date)
|
||||
.getTime ()) / 1000;
|
||||
log ('key is valid for %d seconds', valid_for);
|
||||
await this._redis.setex (key.index, valid_for, JSON.stringify (key));
|
||||
log ('saved key');
|
||||
}
|
||||
|
||||
public async get_key (index: string): Promise<LabelledKey | null> {
|
||||
const log = logger.extend ('get_key');
|
||||
log ('trying to get key %s from redis', index);
|
||||
if (this._redis === null) {
|
||||
log ('redis is inactive, skipping');
|
||||
return null;
|
||||
}
|
||||
const res = await this._redis.get (index);
|
||||
if (res === null) {
|
||||
log ('key not found in redis');
|
||||
return null;
|
||||
}
|
||||
log ('key found');
|
||||
return JSON.parse (res);
|
||||
}
|
||||
}
|
||||
|
||||
export const redis = new Redis;
|
@ -30,10 +30,11 @@ import create_gateway, {
|
||||
AnyFunc,
|
||||
RefreshSettings
|
||||
} from './Gateway';
|
||||
import keystore, {
|
||||
KeyStore, KeyStoreExport,
|
||||
import keystore, { KeyStore } from './KeyStore';
|
||||
import {
|
||||
KeyStoreExport,
|
||||
LabelledKey, Key
|
||||
} from './KeyStore';
|
||||
} from './Key';
|
||||
import {
|
||||
CookieSettings,
|
||||
SameSiteValue
|
||||
|
26
package.json
26
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sapphirecode/auth-server-helper",
|
||||
"version": "3.2.1",
|
||||
"version": "3.3.0",
|
||||
"main": "dist/index.js",
|
||||
"author": {
|
||||
"name": "Timo Hocker",
|
||||
@ -15,22 +15,23 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@sapphirecode/eslint-config-ts": "^1.1.27",
|
||||
"@stryker-mutator/core": "^4.3.1",
|
||||
"@stryker-mutator/jasmine-runner": "^4.3.1",
|
||||
"@stryker-mutator/core": "^6.1.2",
|
||||
"@stryker-mutator/jasmine-runner": "^6.1.2",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/jasmine": "^3.6.2",
|
||||
"@types/node": "^10.0.0",
|
||||
"eslint": "^7.14.0",
|
||||
"jasmine": "^3.6.3",
|
||||
"jasmine-ts": "^0.3.3",
|
||||
"@types/jasmine": "^4.0.3",
|
||||
"@types/node": "^18.6.4",
|
||||
"eslint": "^8.21.0",
|
||||
"jasmine": "^4.3.0",
|
||||
"nyc": "^15.1.0",
|
||||
"ts-node": "^9.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
|
||||
"test": "nyc jasmine-ts --config=\"jasmine.json\"",
|
||||
"pretest": "yarn compile",
|
||||
"test": "nyc jasmine --config=\"jasmine.json\"",
|
||||
"mutate": "stryker run",
|
||||
"precompile": "rm -rf dist",
|
||||
"compile": "tsc"
|
||||
},
|
||||
"files": [
|
||||
@ -45,10 +46,11 @@
|
||||
"middleware"
|
||||
],
|
||||
"dependencies": {
|
||||
"@sapphirecode/crypto-helper": "^1.3.0",
|
||||
"@sapphirecode/crypto-helper": "^2.0.0",
|
||||
"@sapphirecode/encoding-helper": "^1.1.0",
|
||||
"@sapphirecode/utilities": "^1.8.8",
|
||||
"debug": "^4.3.3"
|
||||
"debug": "^4.3.3",
|
||||
"ioredis": "^5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
@ -38,8 +38,8 @@ function check_headers (resp: Response): CheckHeaderResult {
|
||||
return { data, at, rt };
|
||||
}
|
||||
|
||||
function check_token (token: string|null, type: string): void {
|
||||
const v = auth.verify (token || '');
|
||||
async function check_token (token: string|null, type: string): Promise<void> {
|
||||
const v = await auth.verify (token || '');
|
||||
expect (v.valid)
|
||||
.toEqual (true);
|
||||
expect (v.authorized)
|
||||
@ -164,11 +164,11 @@ describe ('auth handler', () => {
|
||||
expect (resp1.headers['set-cookie'])
|
||||
.toContain (build_cookie ({ name: 'mint_cookies' }, res1.rt as string));
|
||||
|
||||
check_token (res1.at as string, 'access_token');
|
||||
await check_token (res1.at as string, 'access_token');
|
||||
expect (res1.data.expires_in)
|
||||
.toEqual (expires_seconds);
|
||||
|
||||
check_token (res1.rt as string, 'refresh_token');
|
||||
await check_token (res1.rt as string, 'refresh_token');
|
||||
expect (res1.data.refresh_expires_in)
|
||||
.toEqual (refresh_expires_seconds);
|
||||
|
||||
@ -185,12 +185,12 @@ describe ('auth handler', () => {
|
||||
expect (resp2.headers['set-cookie'])
|
||||
.toContain (build_cookie ({ name: 'mint_cookies' }, res2.rt as string));
|
||||
|
||||
check_token (res2.at as string, 'access_token');
|
||||
await check_token (res2.at as string, 'access_token');
|
||||
expect (res2.data.expires_in)
|
||||
.toEqual (expires_seconds);
|
||||
expect (res2.at).not.toEqual (res1.at);
|
||||
|
||||
check_token (res2.rt as string, 'refresh_token');
|
||||
await check_token (res2.rt as string, 'refresh_token');
|
||||
expect (res2.data.refresh_expires_in)
|
||||
.toEqual (refresh_expires_seconds);
|
||||
expect (res2.rt).not.toEqual (res1.rt);
|
||||
@ -217,11 +217,11 @@ describe ('auth handler', () => {
|
||||
expect (resp1.headers['set-cookie'])
|
||||
.toContain (build_cookie ({ name: 'mint_cookies' }, res1.rt as string));
|
||||
|
||||
check_token (res1.at as string, 'access_token');
|
||||
await check_token (res1.at as string, 'access_token');
|
||||
expect (res1.data.expires_in)
|
||||
.toEqual (expires_seconds);
|
||||
|
||||
check_token (res1.rt as string, 'refresh_token');
|
||||
await check_token (res1.rt as string, 'refresh_token');
|
||||
expect (res1.data.refresh_expires_in)
|
||||
.toEqual (refresh_expires_seconds);
|
||||
});
|
||||
@ -242,11 +242,11 @@ describe ('auth handler', () => {
|
||||
expect (resp1.headers['set-cookie'])
|
||||
.toContain (build_cookie ({ name: 'mint_cookies' }, res1.rt as string));
|
||||
|
||||
check_token (res1.at as string, 'access_token');
|
||||
await check_token (res1.at as string, 'access_token');
|
||||
expect (res1.data.expires_in)
|
||||
.toEqual (expires_seconds);
|
||||
|
||||
check_token (res1.rt as string, 'refresh_token');
|
||||
await check_token (res1.rt as string, 'refresh_token');
|
||||
expect (res1.data.refresh_expires_in)
|
||||
.toEqual (refresh_expires_seconds);
|
||||
});
|
||||
@ -299,7 +299,7 @@ describe ('auth handler', () => {
|
||||
.toEqual ('bearer');
|
||||
expect (res1.data.expires_in)
|
||||
.toEqual (part_expires_seconds);
|
||||
check_token (res1.data.part_token as string, 'part_token');
|
||||
await check_token (res1.data.part_token as string, 'part_token');
|
||||
|
||||
const resp2 = await get (
|
||||
{ authorization: `Bearer ${res1.data.part_token}` },
|
||||
@ -315,12 +315,12 @@ describe ('auth handler', () => {
|
||||
expect (resp2.headers['set-cookie'])
|
||||
.toContain (build_cookie ({ name: 'mint_cookies' }, res2.rt as string));
|
||||
|
||||
check_token (res2.at as string, 'access_token');
|
||||
await check_token (res2.at as string, 'access_token');
|
||||
expect (res2.data.expires_in)
|
||||
.toEqual (expires_seconds);
|
||||
expect (res2.at).not.toEqual (res1.at);
|
||||
|
||||
check_token (res2.rt as string, 'refresh_token');
|
||||
await check_token (res2.rt as string, 'refresh_token');
|
||||
expect (res2.data.refresh_expires_in)
|
||||
.toEqual (refresh_expires_seconds);
|
||||
expect (res2.rt).not.toEqual (res1.rt);
|
||||
@ -336,7 +336,7 @@ describe ('auth handler', () => {
|
||||
'cookie_jar',
|
||||
(resp1.headers['set-cookie'] || []).join ('\n')
|
||||
);
|
||||
check_token (signature, 'access_token');
|
||||
await check_token (signature, 'access_token');
|
||||
});
|
||||
|
||||
it ('should handle any authorization type', async () => {
|
||||
@ -363,7 +363,7 @@ describe ('auth handler', () => {
|
||||
(resp1.headers['set-cookie'] || []).join ('\n')
|
||||
);
|
||||
expect (signature).not.toEqual ('');
|
||||
check_token (signature, 'access_token');
|
||||
await check_token (signature, 'access_token');
|
||||
});
|
||||
|
||||
it ('should disallow access and refresh cookies with the same name', () => {
|
||||
|
@ -27,7 +27,7 @@ describe ('authority', () => {
|
||||
const token = await auth.sign ('access_token', 60);
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeTrue ();
|
||||
expect (res.valid)
|
||||
@ -46,7 +46,7 @@ describe ('authority', () => {
|
||||
const token = await auth.sign ('refresh_token', 600);
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
@ -65,7 +65,7 @@ describe ('authority', () => {
|
||||
const token = await auth.sign ('part_token', 60, { next_module: '2fa' });
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
@ -85,7 +85,7 @@ describe ('authority', () => {
|
||||
token.signature = modify_signature (token.signature);
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
@ -105,7 +105,7 @@ describe ('authority', () => {
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
bl.add_signature (token.id);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
@ -125,7 +125,7 @@ describe ('authority', () => {
|
||||
token.signature = modify_signature (token.signature);
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
@ -145,7 +145,7 @@ describe ('authority', () => {
|
||||
jasmine.clock ()
|
||||
.tick (30000);
|
||||
bl.add_signature (token.id);
|
||||
const res = auth.verify (token.signature);
|
||||
const res = await auth.verify (token.signature);
|
||||
expect (res.authorized)
|
||||
.toBeFalse ();
|
||||
expect (res.valid)
|
||||
|
@ -27,7 +27,7 @@ describe ('key store', () => {
|
||||
.getTime () / 1000;
|
||||
const duration = 10 * frame;
|
||||
const key = await ks.get_sign_key (iat, duration);
|
||||
const sign = ks.get_key (iat);
|
||||
const sign = await ks.get_key (iat);
|
||||
expect (typeof key)
|
||||
.toEqual ('string');
|
||||
expect (typeof sign)
|
||||
@ -39,7 +39,7 @@ describe ('key store', () => {
|
||||
const key = await ks.get_sign_key (keys[0].iat, 1);
|
||||
expect (key)
|
||||
.toEqual (keys[0].key);
|
||||
const sign = ks.get_key (keys[0].iat);
|
||||
const sign = await ks.get_key (keys[0].iat);
|
||||
expect (sign)
|
||||
.toEqual (keys[0].sign);
|
||||
});
|
||||
@ -48,7 +48,7 @@ describe ('key store', () => {
|
||||
const key = await ks.get_sign_key (keys[0].iat + (frame / 2), 1);
|
||||
expect (key)
|
||||
.toEqual (keys[0].key);
|
||||
const sign = ks.get_key (keys[0].iat + (frame / 2));
|
||||
const sign = await ks.get_key (keys[0].iat + (frame / 2));
|
||||
expect (sign)
|
||||
.toEqual (keys[0].sign);
|
||||
});
|
||||
@ -60,7 +60,7 @@ describe ('key store', () => {
|
||||
.getTime () / 1000;
|
||||
const duration = 10 * frame;
|
||||
const key = await ks.get_sign_key (iat, duration);
|
||||
const sign = ks.get_key (iat);
|
||||
const sign = await ks.get_key (iat);
|
||||
expect (typeof key)
|
||||
.toEqual ('string');
|
||||
expect (key).not.toEqual (keys[0].key);
|
||||
@ -69,32 +69,32 @@ describe ('key store', () => {
|
||||
});
|
||||
|
||||
it ('should return both keys, but not the first sign key', async () => {
|
||||
const sign = ks.get_key (keys[0].iat);
|
||||
const sign = await ks.get_key (keys[0].iat);
|
||||
expect (sign)
|
||||
.toEqual (keys[0].sign);
|
||||
await expectAsync (ks.get_sign_key (keys[0].iat, 1))
|
||||
.toBeRejectedWithError ('cannot access already expired keys');
|
||||
const k2 = await ks.get_sign_key (keys[1].iat, 1);
|
||||
const s2 = ks.get_key (keys[1].iat);
|
||||
const s2 = await ks.get_key (keys[1].iat);
|
||||
expect (k2)
|
||||
.toEqual (keys[1].key);
|
||||
expect (s2)
|
||||
.toEqual (keys[1].sign);
|
||||
});
|
||||
|
||||
it ('should throw on non existing key', () => {
|
||||
expect (() => ks.get_key (keys[1].iat + frame))
|
||||
.toThrowError ('key could not be found');
|
||||
it ('should throw on non existing key', async () => {
|
||||
await expectAsync (ks.get_key (keys[1].iat + frame))
|
||||
.toBeRejectedWithError ('key could not be found');
|
||||
});
|
||||
|
||||
it ('should delete a key after it expires', () => {
|
||||
it ('should delete a key after it expires', async () => {
|
||||
// go to 10 frames + 1ms after key creation
|
||||
jasmine.clock ()
|
||||
.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');
|
||||
await expectAsync (ks.get_key (keys[0].iat))
|
||||
.toBeRejectedWithError ('key could not be found');
|
||||
});
|
||||
|
||||
it (
|
||||
@ -102,7 +102,7 @@ describe ('key store', () => {
|
||||
async () => {
|
||||
await expectAsync (ks.get_sign_key (keys[1].iat, 1))
|
||||
.toBeRejectedWithError ('cannot access already expired keys');
|
||||
const sign = ks.get_key (keys[1].iat);
|
||||
const sign = await ks.get_key (keys[1].iat);
|
||||
expect (sign)
|
||||
.toEqual (keys[1].sign);
|
||||
}
|
||||
@ -129,12 +129,12 @@ describe ('key store', () => {
|
||||
jasmine.clock ()
|
||||
.tick (step * 1000);
|
||||
const key2 = await ks.get_sign_key (iat + step, duration2);
|
||||
const sign = ks.get_key (iat);
|
||||
const sign = await ks.get_key (iat);
|
||||
expect (key1)
|
||||
.toEqual (key2);
|
||||
jasmine.clock ()
|
||||
.tick (5000 * frame);
|
||||
const signv = ks.get_key (iat + step);
|
||||
const signv = await ks.get_key (iat + step);
|
||||
expect (signv)
|
||||
.toEqual (sign);
|
||||
});
|
||||
@ -151,7 +151,7 @@ describe ('key store', () => {
|
||||
.getTime () / 1000;
|
||||
|
||||
const sign = await ks.get_sign_key (iat, frame);
|
||||
const ver = ks.get_key (iat);
|
||||
const ver = await ks.get_key (iat);
|
||||
const exp = ks.export_verification_data ();
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect (Object.keys (ks['_keys']))
|
||||
@ -165,12 +165,12 @@ describe ('key store', () => {
|
||||
.toEqual (exp.map ((v) => v.index));
|
||||
|
||||
const sign2 = await ks2.get_sign_key (iat, frame);
|
||||
const ver2 = ks2.get_key (iat);
|
||||
const ver2 = await ks2.get_key (iat);
|
||||
expect (sign).not.toEqual (sign2);
|
||||
expect (ver).not.toEqual (ver2);
|
||||
await expectAsync (ks2.get_sign_key (iat, 60, ks.instance_id))
|
||||
.toBeRejectedWithError ('cannot access already expired keys');
|
||||
expect (ks2.get_key (iat, ks.instance_id))
|
||||
expect (await ks2.get_key (iat, ks.instance_id))
|
||||
.toEqual (ver);
|
||||
});
|
||||
|
||||
|
62
test/spec/Redis.ts
Normal file
62
test/spec/Redis.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of Auth-Server-Helper which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, August 2022
|
||||
*/
|
||||
|
||||
import ks from '../../lib/KeyStore';
|
||||
import { redis } from '../../lib/Redis';
|
||||
import { clock_finalize, clock_setup } from '../Helper';
|
||||
|
||||
const frame = 3600;
|
||||
const redis_url = process.env.TEST_REDIS_URL || 'redis://localhost';
|
||||
|
||||
describe ('redis', () => {
|
||||
beforeAll (() => {
|
||||
ks.reset_instance ();
|
||||
ks.sync_redis (redis_url);
|
||||
clock_setup ();
|
||||
});
|
||||
|
||||
afterAll (() => clock_finalize ());
|
||||
|
||||
it ('should write and read all keys', async () => {
|
||||
const iat1 = (new Date)
|
||||
.getTime () / 1000;
|
||||
await ks.get_sign_key (iat1, frame);
|
||||
const k1 = await ks.get_key (iat1);
|
||||
|
||||
jasmine.clock ()
|
||||
.tick (frame * 1000);
|
||||
|
||||
const iat2 = (new Date)
|
||||
.getTime () / 1000;
|
||||
await ks.get_sign_key (iat2, frame);
|
||||
const k2 = await ks.get_key (iat2);
|
||||
// eslint-disable-next-line dot-notation
|
||||
const index1 = ks['get_index'] (iat1);
|
||||
// eslint-disable-next-line dot-notation
|
||||
const index2 = ks['get_index'] (iat2);
|
||||
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect (JSON.parse (await redis['_redis']?.get (index1) as string).key)
|
||||
.toEqual (k1);
|
||||
// eslint-disable-next-line dot-notation
|
||||
expect (JSON.parse (await redis['_redis']?.get (index2) as string).key)
|
||||
.toEqual (k2);
|
||||
|
||||
const old_instance = ks.instance_id;
|
||||
ks.reset_instance ();
|
||||
expectAsync (ks.get_key (iat1, old_instance))
|
||||
.toBeRejectedWithError ('key could not be found');
|
||||
expectAsync (ks.get_key (iat1, old_instance))
|
||||
.toBeRejectedWithError ('key could not be found');
|
||||
|
||||
ks.sync_redis (redis_url);
|
||||
expect (await ks.get_key (iat1, old_instance))
|
||||
.toEqual (k1);
|
||||
expect (await ks.get_key (iat2, old_instance))
|
||||
.toEqual (k2);
|
||||
});
|
||||
});
|
@ -3,12 +3,14 @@
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./lib",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": ["test/**/*.ts"]
|
||||
"include": [
|
||||
"lib/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user