const express = require('express'); const rateLimit = require('express-rate-limit'); const AuthService = require('../services/AuthService'); const { authenticateToken } = require('../middleware/auth'); const router = express.Router(); // Rate limiting for authentication endpoints const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts per window message: { error: 'Too many authentication attempts, please try again later.', code: 'RATE_LIMIT_EXCEEDED' }, standardHeaders: true, legacyHeaders: false, }); // Rate limiting for registration (more restrictive) const registerLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 3, // 3 registration attempts per hour message: { error: 'Too many registration attempts, please try again later.', code: 'REGISTRATION_RATE_LIMIT' }, standardHeaders: true, legacyHeaders: false, }); /** * POST /api/auth/register * Register a new user with email validation and verification */ router.post('/register', registerLimiter, async (req, res) => { try { const { email, password } = req.body; // Validate required fields if (!email || !password) { return res.status(400).json({ error: 'Email and password are required', code: 'MISSING_FIELDS' }); } // Register user const result = await AuthService.register(email, password); if (result.success) { res.status(201).json({ message: result.message, user: result.user }); } else { res.status(400).json({ error: result.message, code: 'REGISTRATION_FAILED' }); } } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'Internal server error during registration', code: 'INTERNAL_ERROR' }); } }); /** * POST /api/auth/login * Login user with credential validation and session creation */ router.post('/login', authLimiter, async (req, res) => { try { const { email, password } = req.body; // Validate required fields if (!email || !password) { return res.status(400).json({ error: 'Email and password are required', code: 'MISSING_CREDENTIALS' }); } // Authenticate user const result = await AuthService.login(email, password); if (result.success) { // Set secure cookie with JWT token const cookieOptions = { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000 // 24 hours }; res.cookie('authToken', result.token, cookieOptions); res.json({ message: result.message, user: result.user }); } else { const statusCode = result.requiresVerification ? 403 : 401; res.status(statusCode).json({ error: result.message, code: result.requiresVerification ? 'EMAIL_NOT_VERIFIED' : 'INVALID_CREDENTIALS' }); } } catch (error) { console.error('Login error:', error); res.status(500).json({ error: 'Internal server error during login', code: 'INTERNAL_ERROR' }); } }); /** * POST /api/auth/logout * Logout user with session cleanup */ router.post('/logout', authenticateToken, async (req, res) => { try { // Clear the authentication cookie res.clearCookie('authToken', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict' }); res.json({ message: 'Logged out successfully' }); } catch (error) { console.error('Logout error:', error); res.status(500).json({ error: 'Internal server error during logout', code: 'INTERNAL_ERROR' }); } }); /** * POST /api/auth/refresh * Refresh JWT token */ router.post('/refresh', authenticateToken, async (req, res) => { try { const currentToken = req.cookies?.authToken || (req.headers.authorization && req.headers.authorization.split(' ')[1]); const result = await AuthService.refreshToken(currentToken); if (result.success) { // Set new cookie with refreshed token const cookieOptions = { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000 // 24 hours }; res.cookie('authToken', result.token, cookieOptions); res.json({ message: 'Token refreshed successfully', user: result.user }); } else { res.status(401).json({ error: result.message, code: 'TOKEN_REFRESH_FAILED' }); } } catch (error) { console.error('Token refresh error:', error); res.status(500).json({ error: 'Internal server error during token refresh', code: 'INTERNAL_ERROR' }); } }); /** * POST /api/auth/forgot-password * Request password reset */ router.post('/forgot-password', authLimiter, async (req, res) => { try { const { email } = req.body; if (!email) { return res.status(400).json({ error: 'Email is required', code: 'MISSING_EMAIL' }); } const result = await AuthService.requestPasswordReset(email); // Always return success for security (don't reveal if email exists) res.json({ message: result.message }); } catch (error) { console.error('Password reset request error:', error); res.status(500).json({ error: 'Internal server error during password reset request', code: 'INTERNAL_ERROR' }); } }); /** * POST /api/auth/reset-password * Reset password with token */ router.post('/reset-password', authLimiter, async (req, res) => { try { const { token, newPassword } = req.body; if (!token || !newPassword) { return res.status(400).json({ error: 'Token and new password are required', code: 'MISSING_FIELDS' }); } const result = await AuthService.resetPassword(token, newPassword); if (result.success) { res.json({ message: result.message }); } else { res.status(400).json({ error: result.message, code: 'PASSWORD_RESET_FAILED' }); } } catch (error) { console.error('Password reset error:', error); res.status(500).json({ error: 'Internal server error during password reset', code: 'INTERNAL_ERROR' }); } }); /** * GET /api/auth/verify/:token * Verify email address */ router.get('/verify/:token', async (req, res) => { try { const { token } = req.params; const result = await AuthService.verifyEmail(token); if (result.success) { // Redirect to success page instead of returning JSON res.redirect(`/email-verified.html?message=${encodeURIComponent(result.message)}`); } else { // Redirect to error page with error message res.redirect(`/verify-email.html?error=${encodeURIComponent(result.message)}`); } } catch (error) { console.error('Email verification error:', error); // Redirect to error page for server errors res.redirect(`/verify-email.html?error=${encodeURIComponent('Verification failed. Please try again.')}`); } }); /** * POST /api/auth/resend-verification * Resend email verification */ router.post('/resend-verification', authLimiter, async (req, res) => { try { const { email } = req.body; if (!email) { return res.status(400).json({ error: 'Email is required', code: 'MISSING_EMAIL' }); } const result = await AuthService.resendVerificationEmail(email); if (result.success) { res.json({ message: result.message }); } else { res.status(400).json({ error: result.message, code: 'RESEND_VERIFICATION_FAILED' }); } } catch (error) { console.error('Resend verification error:', error); res.status(500).json({ error: 'Internal server error during resend verification', code: 'INTERNAL_ERROR' }); } }); module.exports = router;