1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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;
}
|