import { createHash, randomBytes, randomUUID } from "node:crypto"; import { openSiteDatabase } from "./site-database"; export type DocumentKeyScope = "read" | "read-write"; export type DocumentKey = { id: string; name: string; scope: DocumentKeyScope; createdAt: number; expiresAt: number; revokedAt: number | null; lastUsedAt: number | null }; function database() { const db = openSiteDatabase(); db.exec(`CREATE TABLE IF NOT EXISTS document_api_keys ( id TEXT PRIMARY KEY, name TEXT NOT NULL, digest TEXT NOT NULL UNIQUE, scope TEXT NOT NULL CHECK(scope IN ('read','read-write')), created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, revoked_at INTEGER, last_used_at INTEGER );`); return db; } export function createDocumentKey(name: string, scope: DocumentKeyScope = "read-write", days = 90) { if (!name.trim() || name.length > 100 || !["read", "read-write"].includes(scope) || !Number.isSafeInteger(days) || days < 1 || days > 365) throw Error("Invalid key name, scope or lifetime"); const id = randomUUID(); const token = "tdoc_" + randomBytes(32).toString("base64url"); const createdAt = Date.now(); const expiresAt = createdAt + days * 86400000; const db = database(); try { db.prepare("INSERT INTO document_api_keys(id,name,digest,scope,created_at,expires_at) VALUES (?,?,?,?,?,?)") .run(id, name.trim(), createHash("sha256").update(token).digest("hex"), scope, createdAt, expiresAt); return { id, token, name: name.trim(), scope, createdAt, expiresAt }; } finally { db.close(); } } export function authenticateDocumentKey(token: string | undefined, now = Date.now()) { if (!token || !/^tdoc_[A-Za-z0-9_-]{43}$/.test(token)) return null; const db = database(); try { const row = db.prepare("SELECT id,scope,last_used_at AS lastUsedAt FROM document_api_keys WHERE digest=? AND revoked_at IS NULL AND expires_at>?") .get(createHash("sha256").update(token).digest("hex"), now) as { id: string; scope: DocumentKeyScope; lastUsedAt: number | null } | undefined; if (!row) return null; if (row.lastUsedAt === null || row.lastUsedAt < now - 60000) db.prepare("UPDATE document_api_keys SET last_used_at=? WHERE id=? AND (last_used_at IS NULL OR last_used_at