import { createHash, randomBytes } from 'node:crypto'; import { queries, newId, now, rowToKey, type ApiKey } from './db'; import { normalizeScopes } from '$lib/keys'; const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; /** * Generate a new API key string (not yet persisted). * Format: "ti_" + 32 random alphanumeric characters. */ export function mintKey(): { key: string; hash: string; prefix: string } { const bytes = randomBytes(32); let random = ''; for (let i = 0; i < 32; i++) { random += ALPHABET[bytes[i] % ALPHABET.length]; } const key = `ti_${random}`; const hash = createHash('sha256').update(key).digest('hex'); const prefix = key.slice(0, 8); // "ti_" + 5 chars return { key, hash, prefix }; } // -------- persistence -------- export interface CreateKeyInput { accountId: string; name: string; scopes: string[]; } export interface CreatedKey extends ApiKey { /** The full plaintext key. Only returned on creation — never stored. */ plaintext: string; } export function createKey(input: CreateKeyInput): CreatedKey { const { key, hash, prefix } = mintKey(); const id = newId(); const ts = now(); const name = input.name.trim() || 'Untitled key'; const scopes = normalizeScopes(input.scopes); queries.insertKey.run( id, input.accountId, hash, prefix, name, JSON.stringify(scopes), ts ); return { id, account_id: input.accountId, key_prefix: prefix, name, scopes, active: true, created_at: ts, last_used_at: null, plaintext: key }; } export function listKeys(accountId: string): ApiKey[] { return queries.keysForAccount.all(accountId).map(rowToKey); } export function revokeKey(accountId: string, keyId: string): boolean { const row = queries.keyById.get(keyId, accountId); if (!row) return false; queries.revokeKey.run(keyId, accountId); return true; } export function countActiveKeys(accountId: string): number { return queries.countActiveKeys.get(accountId)?.c ?? 0; }