WIP
This commit is contained in:
313
backend/src/routes/auth.js
Normal file
313
backend/src/routes/auth.js
Normal 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;
|
||||
Reference in New Issue
Block a user