Openstatus
www.openstatus.dev
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}