import { Router, Request, Response } from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { z } from 'zod'; import prisma from '../lib/prisma.js'; import { authenticate } from '../middleware/auth.js'; const router = Router(); const loginSchema = z.object({ email: z.string().email(), password: z.string().min(1), }); const registerSchema = z.object({ username: z.string().min(2).max(32), email: z.string().email(), password: z.string().min(6), }); function signToken(userId: string, role: string, isAdmin: boolean): string { return jwt.sign({ userId, role, isAdmin }, process.env.JWT_SECRET!, { expiresIn: '7d' }); } function safeUser(user: { id: string; username: string; email: string; role: string; isAdmin: boolean; isBanned: boolean; avatarUrl: string | null; createdAt: Date }) { return { id: user.id, username: user.username, email: user.email, role: user.role, isAdmin: user.isAdmin, isBanned: user.isBanned, avatarUrl: user.avatarUrl, createdAt: user.createdAt.toISOString(), }; } // POST /api/auth/login router.post('/login', async (req: Request, res: Response): Promise => { const parsed = loginSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ error: parsed.error.flatten() }); return; } const { email, password } = parsed.data; const user = await prisma.user.findUnique({ where: { email: email.toLowerCase() } }); if (!user) { res.status(401).json({ error: 'No account found with that email address.' }); return; } const valid = await bcrypt.compare(password, user.password); if (!valid) { res.status(401).json({ error: 'Incorrect password.' }); return; } if (user.isBanned) { res.status(403).json({ error: 'This account has been suspended.' }); return; } const token = signToken(user.id, user.role, user.isAdmin); res.json({ token, user: safeUser(user) }); }); // POST /api/auth/register (public site only — creates role=user) router.post('/register', async (req: Request, res: Response): Promise => { const parsed = registerSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ error: parsed.error.flatten() }); return; } const { username, email, password } = parsed.data; const emailTaken = await prisma.user.findUnique({ where: { email: email.toLowerCase() } }); if (emailTaken) { res.status(409).json({ error: 'An account with this email already exists.' }); return; } const usernameTaken = await prisma.user.findUnique({ where: { username } }); if (usernameTaken) { res.status(409).json({ error: 'This username is already taken.' }); return; } const hashed = await bcrypt.hash(password, 10); const user = await prisma.user.create({ data: { username, email: email.toLowerCase(), password: hashed, role: 'user', }, }); const token = signToken(user.id, user.role, user.isAdmin); res.status(201).json({ token, user: safeUser(user) }); }); // GET /api/auth/me router.get('/me', authenticate, async (req: Request, res: Response): Promise => { const user = await prisma.user.findUnique({ where: { id: req.user!.userId } }); if (!user) { res.status(404).json({ error: 'User not found' }); return; } res.json(safeUser(user)); }); export default router;