78 lines
1.8 KiB
TypeScript
78 lines
1.8 KiB
TypeScript
import * as crypto from 'crypto';
|
|
|
|
export abstract class IApiKeyAuth {
|
|
abstract isValid(plain: string): boolean;
|
|
|
|
abstract skipAuth(): boolean;
|
|
}
|
|
|
|
export class NoAuth implements IApiKeyAuth {
|
|
isValid(plain: string): boolean {
|
|
return true;
|
|
}
|
|
|
|
skipAuth(): boolean {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export class PlainApiKeyAuth implements IApiKeyAuth {
|
|
constructor(private key: string) {}
|
|
|
|
isValid(plain: string): boolean {
|
|
return compare(plain, this.key);
|
|
}
|
|
|
|
skipAuth(): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export class HashAuth implements IApiKeyAuth {
|
|
constructor(
|
|
private hash: string,
|
|
private algorithm: string,
|
|
) {}
|
|
|
|
isValid(plain: string): boolean {
|
|
if (!plain) {
|
|
return false;
|
|
}
|
|
const hash = crypto.createHash(this.algorithm).update(plain).digest('hex');
|
|
return compare(hash, this.hash);
|
|
}
|
|
|
|
skipAuth(): boolean {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Securely compare 2 strings
|
|
*/
|
|
export function compare(provided: string, stored: string | undefined): boolean {
|
|
if (!stored || !provided) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Convert strings to buffers for constant-time comparison
|
|
const providedBuffer = Buffer.from(provided);
|
|
const storedBuffer = Buffer.from(stored);
|
|
|
|
// If lengths are different, return false but use a dummy comparison to prevent timing attacks
|
|
if (providedBuffer.length !== storedBuffer.length) {
|
|
// Create a dummy buffer of the same length as the provided key
|
|
const dummyBuffer = Buffer.alloc(providedBuffer.length);
|
|
// Perform comparison with dummy buffer to maintain constant time
|
|
crypto.timingSafeEqual(providedBuffer, dummyBuffer);
|
|
return false;
|
|
}
|
|
|
|
// Perform constant-time comparison
|
|
return crypto.timingSafeEqual(providedBuffer, storedBuffer);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|