public
anord
read
Ledger
Why work hard when you can work easier?
Languages
Repository composition by tracked source files.
TypeScript
86%
CSS
10%
SQL
3%
Shell
1%
HTML
0%
Trace
apps/api/src/http/routes/auth.ts
Trace helps you understand code history line by line. See who changed each line, when it changed, and which commit introduced it.
Author
Date
Commit
Line
Code
1
import { Router } from "express";
2
import { z } from "zod";
3
import { createSessionToken, getUserForSession, hashPassword, verifyPassword } from "../../services/auth.js";
4
import { pool } from "../../db/pool.js";
5
import { env, isProduction } from "../../config/env.js";
6
import { logAudit } from "../../services/audit.js";
8
const loginSchema = z.object({
9
email: z.string().email(),
10
password: z.string().min(8)
11
});
13
const registerSchema = z.object({
14
email: z.string().email(),
15
password: z.string().min(8),
16
displayName: z.string().min(2)
17
});
19
export const authRouter = Router();
21
authRouter.post("/register", async (req, res) => {
22
const userCount = await pool.query(`SELECT COUNT(*)::int AS count FROM users`);
23
if (userCount.rows[0].count === 0) {
24
return res.status(403).json({ error: "Complete initial Ledger setup first" });
25
}
27
const input = registerSchema.parse(req.body);
28
const existing = await pool.query(`SELECT id FROM users WHERE email = $1`, [input.email]);
30
if (existing.rowCount) {
31
return res.status(409).json({ error: "User already exists" });
32
}
34
const role = await pool.query(`SELECT id FROM roles WHERE key = 'viewer'`);
35
const user = await pool.query(
36
`
37
INSERT INTO users (email, password_hash, display_name, primary_role_id)
38
VALUES ($1, $2, $3, $4)
39
RETURNING id
40
`,
41
[input.email, await hashPassword(input.password), input.displayName, role.rows[0].id]
42
);
44
const sessionUser = await getUserForSession(user.rows[0].id);
45
const token = createSessionToken(sessionUser!);
47
res.cookie(env.SESSION_COOKIE_NAME, token, {
48
httpOnly: true,
49
sameSite: "lax",
50
secure: isProduction
51
});
53
await logAudit(sessionUser!.id, "auth.register", "user", sessionUser!.id, {
54
email: sessionUser!.email
55
});
57
return res.status(201).json({ user: sessionUser });
58
});
60
authRouter.post("/login", async (req, res) => {
61
const input = loginSchema.parse(req.body);
62
const user = await pool.query(
63
`
64
SELECT u.id, u.password_hash
65
FROM users u
66
WHERE u.email = $1 AND u.is_active = true
67
`,
68
[input.email]
69
);
71
if (!user.rowCount || !user.rows[0].password_hash) {
72
return res.status(401).json({ error: "Invalid credentials" });
73
}
75
const passwordValid = await verifyPassword(input.password, user.rows[0].password_hash);
76
if (!passwordValid) {
77
return res.status(401).json({ error: "Invalid credentials" });
78
}
80
const sessionUser = await getUserForSession(user.rows[0].id);
81
const token = createSessionToken(sessionUser!);
83
res.cookie(env.SESSION_COOKIE_NAME, token, {
84
httpOnly: true,
85
sameSite: "lax",
86
secure: isProduction
87
});
89
await logAudit(sessionUser!.id, "auth.login", "user", sessionUser!.id);
90
return res.json({ user: sessionUser });
91
});
93
authRouter.post("/logout", async (_req, res) => {
94
res.clearCookie(env.SESSION_COOKIE_NAME);
95
return res.status(204).send();
96
});
98
authRouter.get("/session", async (req, res) => {
99
return res.json({ user: req.user ?? null });
100
});