import type { CookieOptions, NextFunction, Request, Response } from 'express'; import { prisma } from '@vector/db'; import type { LoginRequest } from '@vector/shared'; import { env } from '../env.js'; import * as authService from '../services/auth.js'; import { issueCsrfToken } from '../middleware/csrf.js'; import { errors } from '../lib/http-error.js'; const accessCookieOpts: CookieOptions = { httpOnly: true, sameSite: 'lax', secure: env.NODE_ENV === 'production', path: '/', maxAge: authService.ACCESS_TOKEN_TTL_MS, }; const refreshCookieOpts: CookieOptions = { httpOnly: true, sameSite: 'lax', secure: env.NODE_ENV === 'production', path: '/api/auth', maxAge: authService.REFRESH_TOKEN_TTL_MS, }; function setAuthCookies(res: Response, tokens: authService.AuthTokens) { res.cookie('token', tokens.accessToken, accessCookieOpts); res.cookie('refresh', tokens.refreshToken, refreshCookieOpts); issueCsrfToken(res); } function clearAuthCookies(res: Response) { res.clearCookie('token', { path: '/' }); res.clearCookie('refresh', { path: '/api/auth' }); res.clearCookie('csrf', { path: '/' }); } export async function login(req: Request, res: Response, next: NextFunction) { try { const { username, password } = req.validated!.body as LoginRequest; const { user, tokens } = await prisma.$transaction((tx) => authService.login(tx, username, password), ); setAuthCookies(res, tokens); res.json(user); } catch (err) { next(err); } } export async function refresh(req: Request, res: Response, next: NextFunction) { try { const presented = req.cookies?.refresh; if (!presented) throw errors.unauthorized('Missing refresh token'); const { user, tokens } = await prisma.$transaction((tx) => authService.rotate(tx, presented), ); setAuthCookies(res, tokens); res.json(user); } catch (err) { next(err); } } export async function logout(req: Request, res: Response, next: NextFunction) { try { const presented = req.cookies?.refresh; await prisma.$transaction((tx) => authService.revoke(tx, presented)); clearAuthCookies(res); res.json({ message: 'Logged out' }); } catch (err) { next(err); } } export async function me(req: Request, res: Response, next: NextFunction) { try { const user = await prisma.user.findUnique({ where: { id: req.user!.id } }); if (!user) throw errors.unauthorized(); res.json({ id: user.id, username: user.username, email: user.email, role: user.role }); } catch (err) { next(err); } }