File size: 2,959 Bytes
077865a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import crypto from 'crypto';
import { getDb } from '../db/index.js';
import { hashPassword, verifyPassword } from '../lib/password.js';
// Dashboard authentication: email + password accounts with opaque session
// tokens. Distinct from the unified API key, which authenticates the /v1 proxy
// for apps — this gates the /api/* admin surface for the human operator (#35).
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
function sha256(s) {
    return crypto.createHash('sha256').update(s).digest('hex');
}
function normalizeEmail(email) {
    return email.trim().toLowerCase();
}
export function userCount() {
    const row = getDb().prepare('SELECT COUNT(*) AS c FROM users').get();
    return row.c;
}
/** Create a user. Throws { code: 'email_taken' } if the email already exists. */
export function createUser(email, password) {
    const db = getDb();
    const normalized = normalizeEmail(email);
    const existing = db.prepare('SELECT id FROM users WHERE email = ?').get(normalized);
    if (existing) {
        const err = new Error('An account with that email already exists');
        err.code = 'email_taken';
        throw err;
    }
    const result = db.prepare('INSERT INTO users (email, password_hash) VALUES (?, ?)')
        .run(normalized, hashPassword(password));
    return { userId: Number(result.lastInsertRowid), email: normalized };
}
/** Verify credentials. Returns the user on success, null on failure. */
export function verifyCredentials(email, password) {
    const db = getDb();
    const row = db.prepare('SELECT id, email, password_hash FROM users WHERE email = ?')
        .get(normalizeEmail(email));
    if (!row)
        return null;
    if (!verifyPassword(password, row.password_hash))
        return null;
    return { userId: row.id, email: row.email };
}
/** Mint a session and return the raw token (only the hash is persisted). */
export function createSession(userId) {
    const token = crypto.randomBytes(32).toString('hex');
    getDb().prepare('INSERT INTO sessions (token_hash, user_id, expires_at_ms) VALUES (?, ?, ?)')
        .run(sha256(token), userId, Date.now() + SESSION_TTL_MS);
    return token;
}
/** Resolve a session token to its user, or null if missing/expired. */
export function validateSession(token) {
    if (!token)
        return null;
    const db = getDb();
    const row = db.prepare(`
    SELECT s.user_id, s.expires_at_ms, u.email
    FROM sessions s JOIN users u ON u.id = s.user_id
    WHERE s.token_hash = ?
  `).get(sha256(token));
    if (!row)
        return null;
    if (row.expires_at_ms < Date.now()) {
        db.prepare('DELETE FROM sessions WHERE token_hash = ?').run(sha256(token));
        return null;
    }
    return { userId: row.user_id, email: row.email };
}
export function deleteSession(token) {
    if (!token)
        return;
    getDb().prepare('DELETE FROM sessions WHERE token_hash = ?').run(sha256(token));
}
//# sourceMappingURL=auth.js.map