aboutsummaryrefslogtreecommitdiff
path: root/web/ui/src/lib/server/keys.ts
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2026-04-10 11:13:34 +0800
committerbenj <benj@rse8.com>2026-04-10 11:13:34 +0800
commit493746b14c1251a45b061d2e3edd9160c929d2b9 (patch)
tree1607cceb94c1aac1a17a01bb5c0d71b97342e892 /web/ui/src/lib/server/keys.ts
parentc041641634650c31e03c70dcad132fd94cb08e63 (diff)
downloadtidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.gz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.bz2
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.lz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.xz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.zst
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.zip
a basic ui and landing web interface for tidyindex.com
Diffstat (limited to '')
-rw-r--r--web/ui/src/lib/server/keys.ts79
1 files changed, 79 insertions, 0 deletions
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;
+}