/* * 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 , December 2020 */ import { debug } from './debug'; import { redis_blacklist_store } from './RedisData/RedisBlacklistStore'; import { parse_token_id } from './token_id'; const logger = debug ('blacklist'); interface Signature { token_id: string; iat: number; valid_until: Date; } interface ExportedSignature { token_id: string; iat: number; } class Blacklist { private _signatures: Signature[]; private _interval: NodeJS.Timeout; public constructor () { this._signatures = []; this._interval = setInterval ( this.garbage_collect.bind (this), 3600000 ); } public async clear ( before: number = Number.POSITIVE_INFINITY ): Promise { logger.extend ('clear') ('clearing blacklist'); for (let i = this._signatures.length - 1; i >= 0; i--) { if (this._signatures[i].iat < before) { // eslint-disable-next-line no-await-in-loop await this.remove_signature (i); } } } public async add_signature (token_id: string): Promise { logger.extend ('add_signature') ('blacklisting signature %s', token_id); const parsed = parse_token_id (token_id); this._signatures.push ({ iat: Date.now (), token_id, valid_until: parsed.valid_until }); await redis_blacklist_store.add (token_id, parsed.valid_until); } public async remove_signature (signature: number | string): Promise { const log = logger.extend ('remove_signature'); log ('removing signature from blacklist %s', signature); let key = ''; if (typeof signature === 'string') { log ('received string, searching through signatures'); key = signature; for (let i = this._signatures.length - 1; i >= 0; i--) { if (this._signatures[i].token_id === signature) { log ('removing sigature %s at %d', signature, i); this._signatures.splice (i, 1); } } } else { log ( 'received index, removing signature %s at index %s', this._signatures[signature].token_id, signature ); key = this._signatures[signature].token_id; this._signatures.splice (signature, 1); } await redis_blacklist_store.remove (key); } public async is_valid (hash: string): Promise { const log = logger.extend ('is_valid'); log ('checking signature for blacklist entry %s', hash); for (const sig of this._signatures) { if (sig.token_id === hash) { log ('found matching blacklist entry'); return false; } } log ('signature is not blacklisted locally, checking redis'); if (await redis_blacklist_store.get (hash)) { log ('signature is blacklisted in redis'); return false; } log ('signature is not blacklisted'); return true; } public export_blacklist (): ExportedSignature[] { logger.extend ('export_blacklist') ('exporting blacklist'); return this._signatures.map ((v) => ({ iat: v.iat, token_id: v.token_id })); } public import_blacklist (data: ExportedSignature[]): void { logger.extend ('import_blacklist') ( 'importing %d blacklist entries', data.length ); for (const token of data) { const parsed = parse_token_id (token.token_id); this._signatures.push ({ token_id: token.token_id, iat: token.iat, valid_until: parsed.valid_until }); } } public sync_redis (url: string): void { redis_blacklist_store.connect (url); } private async garbage_collect (): Promise { const log = logger.extend ('garbage_collect'); const time = new Date; log ('removing signatures expired before', time); for (let i = this._signatures.length - 1; i >= 0; i--) { if (this._signatures[i].valid_until < time) { log ('signature %s expired', this._signatures[i].token_id); await this.remove_signature (i); } } } } const bl = (new Blacklist); export { Blacklist }; export default bl;