Openstatus www.openstatus.dev
at 4c0f4c00a38753a5d0dfd7e7b7b7706dec6f1503 63 lines 2.0 kB view raw
1// biome-ignore lint/style/useNodejsImportProtocol: <explanation> 2import crypto from "crypto"; 3import bcrypt from "bcryptjs"; 4 5/** 6 * Generates a new API key with token, prefix, and hash 7 * @returns Object containing the full token, prefix for lookup, and SHA-256 hash 8 */ 9export async function generateApiKey(): Promise<{ 10 token: string; 11 prefix: string; 12 hash: string; 13}> { 14 const randomBytes = crypto.randomBytes(16).toString("hex"); // 32 hex chars 15 const token = `os_${randomBytes}`; 16 const prefix = token.slice(0, 11); // "os_" (3 chars) + 8 hex chars = 11 total 17 const hash = await bcrypt.hash(token, 10); 18 return { token, prefix, hash }; 19} 20 21/** 22 * Hashes an API key token using bcrypt 23 * @param token - The API key token to hash 24 * @returns The bcrypt hash of the token 25 */ 26export async function hashApiKey(token: string): Promise<string> { 27 return bcrypt.hash(token, 10); 28} 29 30/** 31 * Verifies an API key token against a stored hash 32 * Supports both bcrypt hashes (new) and SHA-256 hashes (legacy) for migration 33 * @param token - The API key token to verify 34 * @param storedHash - The stored hash to verify against 35 * @returns True if the token matches the hash 36 */ 37export async function verifyApiKeyHash( 38 token: string, 39 storedHash: string, 40): Promise<boolean> { 41 // Check if it's a bcrypt hash (starts with $2a$, $2b$, or $2y$) 42 if (storedHash.startsWith("$2")) { 43 return bcrypt.compare(token, storedHash); 44 } 45 46 // Unknown hash format 47 return false; 48} 49 50/** 51 * Determines if lastUsedAt should be updated based on debounce period 52 * @param lastUsedAt - The last time the key was used (or null) 53 * @param debounceMinutes - Minutes to wait before updating again (default: 5) 54 * @returns True if lastUsedAt should be updated 55 */ 56export function shouldUpdateLastUsed( 57 lastUsedAt: Date | null, 58 debounceMinutes = 5, 59): boolean { 60 if (!lastUsedAt) return true; 61 const diffMs = Date.now() - lastUsedAt.getTime(); 62 return diffMs > debounceMinutes * 60 * 1000; 63}