From 493746b14c1251a45b061d2e3edd9160c929d2b9 Mon Sep 17 00:00:00 2001 From: benj Date: Fri, 10 Apr 2026 11:13:34 +0800 Subject: a basic ui and landing web interface for tidyindex.com --- web/ui/src/lib/server/keys.ts | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 web/ui/src/lib/server/keys.ts (limited to 'web/ui/src/lib/server/keys.ts') diff --git a/web/ui/src/lib/server/keys.ts b/web/ui/src/lib/server/keys.ts new file mode 100644 index 0000000..350839b --- /dev/null +++ b/web/ui/src/lib/server/keys.ts @@ -0,0 +1,79 @@ +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; +} -- cgit v1.2.3