This commit is contained in:
2025-07-20 20:43:06 +02:00
parent 0abee5b794
commit 29592c7fc8
93 changed files with 23400 additions and 131 deletions

313
backend/src/routes/auth.js Normal file
View File

@ -0,0 +1,313 @@
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;