Refactor the authentication system to separate login and registration forms, implement password hashing, manage JWT tokens, and enable user data persistence in `backend/data/users.json`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 375ec6d3-d5af-4f82-ab81-5c60fd4a86a3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 8d427c3d-aa60-488b-82c4-6cef148ba5d7 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/147e665c-8c0d-48ec-b0ad-fdc89cd4460f/375ec6d3-d5af-4f82-ab81-5c60fd4a86a3/e238nM8 Replit-Helium-Checkpoint-Created: true
187 lines
4.9 KiB
JavaScript
187 lines
4.9 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const User = require('../models/userModel');
|
|
const { validateEmail, validatePassword } = require('../middleware/validationMiddleware');
|
|
|
|
// Generate JWT Token
|
|
const generateToken = (id) => {
|
|
return jwt.sign({ id }, process.env.JWT_SECRET || 'your_secret_key', {
|
|
expiresIn: process.env.JWT_EXPIRE || '7d',
|
|
});
|
|
};
|
|
|
|
// @desc Register user
|
|
// @route POST /api/auth/register
|
|
// @access Public
|
|
exports.register = async (req, res) => {
|
|
try {
|
|
const { firstName, lastName, email, password, passwordConfirm } = req.body;
|
|
const normalizedEmail = email ? email.toLowerCase().trim() : '';
|
|
|
|
// Validation
|
|
if (!firstName || !lastName || !email || !password || !passwordConfirm) {
|
|
return res.status(400).json({ message: 'All fields are required' });
|
|
}
|
|
|
|
if (!validateEmail(normalizedEmail)) {
|
|
return res.status(400).json({ message: 'Invalid email format' });
|
|
}
|
|
|
|
if (!validatePassword(password)) {
|
|
return res.status(400).json({
|
|
message: 'Password must be at least 6 characters',
|
|
});
|
|
}
|
|
|
|
if (password !== passwordConfirm) {
|
|
return res.status(400).json({ message: 'Passwords do not match' });
|
|
}
|
|
|
|
// Check if user already exists
|
|
let user = await User.findOne({ email: normalizedEmail });
|
|
if (user) {
|
|
return res.status(400).json({ message: 'Email already in use' });
|
|
}
|
|
|
|
// Create user
|
|
user = await User.create({
|
|
firstName,
|
|
lastName,
|
|
email: normalizedEmail,
|
|
password,
|
|
});
|
|
|
|
// Create token
|
|
const token = generateToken(user._id);
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'User registered successfully',
|
|
token,
|
|
user: user.getPublicData(),
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
// @desc Login user
|
|
// @route POST /api/auth/login
|
|
// @access Public
|
|
exports.login = async (req, res) => {
|
|
try {
|
|
const { email, password } = req.body;
|
|
const normalizedEmail = email ? email.toLowerCase().trim() : '';
|
|
|
|
// Validation
|
|
if (!email || !password) {
|
|
return res.status(400).json({ message: 'Email and password are required' });
|
|
}
|
|
|
|
if (!validateEmail(normalizedEmail)) {
|
|
return res.status(400).json({ message: 'Invalid email format' });
|
|
}
|
|
|
|
const user = await User.findOne({ email: normalizedEmail });
|
|
if (!user) {
|
|
return res.status(401).json({ message: 'Invalid email or password' });
|
|
}
|
|
|
|
// Check if password matches
|
|
const isPasswordMatch = await user.matchPassword(password);
|
|
if (!isPasswordMatch) {
|
|
return res.status(401).json({ message: 'Invalid email or password' });
|
|
}
|
|
|
|
// Check if user account is active
|
|
if (!user.isActive) {
|
|
return res.status(403).json({ message: 'Your account has been deactivated' });
|
|
}
|
|
|
|
// Update last login
|
|
user.lastLogin = new Date();
|
|
await user.save();
|
|
|
|
// Create token
|
|
const token = generateToken(user._id);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Login successful',
|
|
token,
|
|
user: user.getPublicData(),
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
// @desc Logout user (client-side)
|
|
// @route POST /api/auth/logout
|
|
// @access Private
|
|
exports.logout = async (req, res) => {
|
|
try {
|
|
// Token removal is handled on client-side
|
|
// This endpoint can be used for server-side session cleanup if needed
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Logout successful',
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
// @desc Refresh token
|
|
// @route POST /api/auth/refresh-token
|
|
// @access Private
|
|
exports.refreshToken = async (req, res) => {
|
|
try {
|
|
const { token } = req.body;
|
|
|
|
if (!token) {
|
|
return res.status(400).json({ message: 'Token is required' });
|
|
}
|
|
|
|
// Verify token
|
|
let decoded;
|
|
try {
|
|
decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_secret_key');
|
|
} catch (error) {
|
|
return res.status(401).json({ message: 'Invalid or expired token' });
|
|
}
|
|
|
|
// Get user
|
|
const user = await User.findById(decoded.id);
|
|
if (!user) {
|
|
return res.status(404).json({ message: 'User not found' });
|
|
}
|
|
|
|
// Generate new token
|
|
const newToken = generateToken(user._id);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
message: 'Token refreshed successfully',
|
|
token: newToken,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|
|
|
|
// @desc Get current logged in user
|
|
// @route GET /api/auth/me
|
|
// @access Private
|
|
exports.getMe = async (req, res) => {
|
|
try {
|
|
const user = await User.findById(req.user._id);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
user: user.getPublicData(),
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|