import { Router, Request, Response } from 'express'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { prisma } from '../lib/prisma'; import { authMiddleware } from '../lib/auth'; const router = Router(); const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret'; const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d'; // POST /api/auth/register router.post('/register', async (req: Request, res: Response) => { try { const { email, name, password } = req.body; if (!email || !name || !password) { res.status(400).json({ error: 'email, name, and password are required' }); return; } if (password.length < 6) { res.status(400).json({ error: 'Password must be at least 6 characters' }); return; } const existing = await prisma.user.findUnique({ where: { email } }); if (existing) { res.status(409).json({ error: 'Email already registered' }); return; } const hashed = await bcrypt.hash(password, 12); const user = await prisma.user.create({ data: { email, name, password: hashed }, select: { id: true, email: true, name: true, role: true, avatarUrl: true }, }); const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN } as jwt.SignOptions ); res.cookie('token', token, { httpOnly: true, secure: false, // set true if serving over HTTPS sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days }); res.status(201).json({ user, token }); } catch (err) { console.error('Register error:', err); res.status(500).json({ error: 'Internal server error' }); } }); // POST /api/auth/login router.post('/login', async (req: Request, res: Response) => { try { const { email, password } = req.body; if (!email || !password) { res.status(400).json({ error: 'email and password are required' }); return; } const user = await prisma.user.findUnique({ where: { email } }); if (!user) { res.status(401).json({ error: 'Invalid credentials' }); return; } const valid = await bcrypt.compare(password, user.password); if (!valid) { res.status(401).json({ error: 'Invalid credentials' }); return; } const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN } as jwt.SignOptions ); res.cookie('token', token, { httpOnly: true, secure: false, // set true if serving over HTTPS sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, }); res.json({ user: { id: user.id, email: user.email, name: user.name, role: user.role, avatarUrl: user.avatarUrl, }, token, }); } catch (err) { console.error('Login error:', err); res.status(500).json({ error: 'Internal server error' }); } }); // POST /api/auth/logout router.post('/logout', (_req: Request, res: Response) => { res.clearCookie('token'); res.json({ message: 'Logged out' }); }); // GET /api/auth/me router.get('/me', authMiddleware, async (req: Request, res: Response) => { try { const user = await prisma.user.findUnique({ where: { id: req.user!.userId }, select: { id: true, email: true, name: true, role: true, avatarUrl: true }, }); if (!user) { res.status(404).json({ error: 'User not found' }); return; } res.json({ user }); } catch (err) { console.error('Me error:', err); res.status(500).json({ error: 'Internal server error' }); } }); export default router;