313 lines
8.9 KiB
JavaScript
313 lines
8.9 KiB
JavaScript
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; |