MyKindlebox/mykindlebox/server/routes/auth.ts

77 lines
2.2 KiB
TypeScript

import { Router } from 'express';
import { db } from '../db/client';
import { users } from '../db/schema';
import {
anyUsersExist,
createSession,
destroySession,
failedLoginDelayMs,
findUserByUsername,
getSessionUser,
hashPassword,
sleep,
validateNewCredentials,
verifyPassword,
} from '../auth';
const router = Router();
// Drives the client's auth gate: `configured: false` → show the first-run
// setup screen; unauthenticated → login screen; otherwise `user` identifies
// the session for the UI.
router.get('/status', (req, res) => {
const user = getSessionUser(req);
res.json({
configured: anyUsersExist(),
authenticated: user !== null,
user: user ?? null,
});
});
// First-run onboarding: creates the first account and signs it in. Locked to
// the empty-users state so it can never be used to add accounts later (that's
// POST /api/users, which requires a session).
router.post('/setup', (req, res) => {
if (anyUsersExist()) {
res.status(409).json({ error: 'Setup has already been completed' });
return;
}
const { username, password } = req.body ?? {};
const invalid = validateNewCredentials(username, password);
if (invalid) {
res.status(400).json({ error: invalid });
return;
}
const created = db
.insert(users)
.values({ username: (username as string).trim(), passwordHash: hashPassword(password as string) })
.returning({ id: users.id, username: users.username })
.get();
createSession(res, created.id);
res.status(201).json({ authenticated: true, user: created });
});
router.post('/login', async (req, res) => {
const { username, password } = req.body ?? {};
const user = typeof username === 'string' ? findUserByUsername(username) : undefined;
if (!user || typeof password !== 'string' || !verifyPassword(password, user.passwordHash)) {
await sleep(failedLoginDelayMs); // flat delay = cheap brute-force damping
res.status(401).json({ error: 'Incorrect username or password' });
return;
}
createSession(res, user.id);
res.json({ authenticated: true, user: { id: user.id, username: user.username } });
});
router.post('/logout', (req, res) => {
destroySession(req, res);
res.json({ authenticated: false });
});
export default router;