import { Router } from 'express'; import { prisma } from '../config/database.js'; import { hashPassword, comparePassword } from '../utils/password.js'; import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from '../utils/jwt.js'; import { validate } from '../middleware/validate.js'; import { authenticate } from '../middleware/auth.js'; import { registerSchema, loginSchema } from '../validators/auth.js'; import { AppError } from '../middleware/errorHandler.js'; const router = Router(); router.post('/register', validate(registerSchema), async (req, res, next) => { try { const { fullName, email, password } = req.body; const existing = await prisma.user.findUnique({ where: { email } }); if (existing) throw new AppError(409, 'Email already registered'); const passwordHash = await hashPassword(password); const user = await prisma.user.create({ data: { fullName, email, passwordHash }, select: { id: true, email: true, fullName: true, nickname: true, avatar: true, phone: true, location: true, bio: true, rating: true, showEmail: true, showPhone: true, showLocation: true, createdAt: true }, }); const accessToken = generateAccessToken(user.id); const refreshToken = generateRefreshToken(user.id); await prisma.session.create({ data: { userId: user.id, refreshToken, userAgent: req.headers['user-agent'] || null, ipAddress: req.ip || null, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }, }); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env['NODE_ENV'] === 'production', sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, }); res.status(201).json({ user, accessToken }); } catch (error) { next(error); } }); router.post('/login', validate(loginSchema), async (req, res, next) => { try { const { email, password } = req.body; const user = await prisma.user.findUnique({ where: { email } }); if (!user) throw new AppError(401, 'Invalid email or password'); if (!user.isActive) throw new AppError(403, 'Account is disabled'); const valid = await comparePassword(password, user.passwordHash); if (!valid) throw new AppError(401, 'Invalid email or password'); const accessToken = generateAccessToken(user.id); const refreshToken = generateRefreshToken(user.id); await prisma.session.create({ data: { userId: user.id, refreshToken, userAgent: req.headers['user-agent'] || null, ipAddress: req.ip || null, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }, }); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env['NODE_ENV'] === 'production', sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, }); const { passwordHash: _, ...userData } = user; res.json({ user: userData, accessToken }); } catch (error) { next(error); } }); router.post('/refresh', async (req, res, next) => { try { const token = req.cookies?.refreshToken; if (!token) throw new AppError(401, 'No refresh token'); const payload = verifyRefreshToken(token); const session = await prisma.session.findUnique({ where: { refreshToken: token } }); if (!session || session.expiresAt < new Date()) { if (session) await prisma.session.delete({ where: { id: session.id } }); throw new AppError(401, 'Invalid refresh token'); } const accessToken = generateAccessToken(payload.userId); const newRefreshToken = generateRefreshToken(payload.userId); await prisma.session.update({ where: { id: session.id }, data: { refreshToken: newRefreshToken, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) }, }); res.cookie('refreshToken', newRefreshToken, { httpOnly: true, secure: process.env['NODE_ENV'] === 'production', sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000, }); res.json({ accessToken }); } catch (error) { next(error); } }); router.get('/me', authenticate, async (req, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.userId }, select: { id: true, email: true, fullName: true, nickname: true, avatar: true, phone: true, location: true, bio: true, rating: true, showEmail: true, showPhone: true, showLocation: true, createdAt: true }, }); if (!user) throw new AppError(404, 'User not found'); res.json({ user }); } catch (error) { next(error); } }); router.post('/logout', authenticate, async (req, res, next) => { try { const token = req.cookies?.refreshToken; if (token) { await prisma.session.deleteMany({ where: { refreshToken: token } }); } res.clearCookie('refreshToken'); res.json({ message: 'Logged out' }); } catch (error) { next(error); } }); export default router;