const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const User = require('../models/userModel'); const { sendWelcomeEmail, sendPasswordChangeConfirmationEmail, } = require('../utils/emailService'); // Helper function to create JWT const signToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN, }); }; // SIGNUP exports.signup = async (req, res) => { try { const { name, email, phone, password } = req.body; const existingUser = await User.findOne({ email }); if (existingUser) { return res.status(400).json({ message: 'Email already registered' }); } // Check if phone number already exists const existingPhone = await User.findOne({ phone }); if (existingPhone) { return res .status(400) .json({ message: 'Phone number already registered' }); } // Create user - the pre-save hook in userModel will hash the password const newUser = await User.create({ name, email, phone, password, // Will be hashed by pre-save hook }); const token = signToken(newUser._id); // Send welcome email (non-blocking - don't wait for it) sendWelcomeEmail(newUser).catch(() => { }); res.status(201).json({ status: 'success', token, user: { id: newUser._id, name: newUser.name, email: newUser.email, phone: newUser.phone, role: newUser.role, }, }); } catch (err) { res.status(500).json({ message: 'Signup failed', error: err.message }); } }; // LOGIN exports.login = async (req, res) => { try { const { email, password } = req.body; // 1) Check if email and password exist if (!email || !password) { return res .status(400) .json({ message: 'Please provide email and password' }); } // 2) Check if user exists && password is correct const user = await User.findOne({ email }) .select('+password') .populate('provider'); if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 'Invalid email or password' }); } // 3) Check if user account is active if (!user.isActive) { return res.status(403).json({ message: 'Your account has been deactivated. Please contact support.', }); } // 4) Check if vendor's provider is active if (user.role === 'vendor' && user.provider) { if (!user.provider.isActive) { return res.status(403).json({ message: 'Your vendor account has been deactivated. Please contact support.', }); } } // 3) If everything ok, send token const token = signToken(user._id); res.status(200).json({ status: 'success', token, user: { id: user._id, name: user.name, email: user.email, phone: user.phone, role: user.role, permissions: user.permissions || [], provider: user.role === 'vendor' ? user.provider : undefined, }, }); } catch (err) { res.status(500).json({ message: 'Login failed', error: err.message }); } }; // ADMIN PORTAL LOGIN - Only for admin, employee, and vendor roles exports.adminLogin = async (req, res) => { try { const { email, password } = req.body; // 1) Check if email and password exist if (!email || !password) { return res .status(400) .json({ message: 'Please provide email and password' }); } // 2) Check if user exists && password is correct const user = await User.findOne({ email }) .select('+password') .populate('provider'); if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 'Invalid email or password' }); } // 3) Check if user has admin portal access (admin, employee, or vendor) const allowedRoles = ['admin', 'employee', 'vendor']; if (!allowedRoles.includes(user.role)) { return res.status(403).json({ message: 'Access denied. This portal is only for administrators, employees, and vendors.', }); } // 4) Check if user account is active if (!user.isActive) { return res.status(403).json({ message: 'Your account has been deactivated. Please contact support.', }); } // 5) Check if vendor's provider is active if (user.role === 'vendor' && user.provider) { if (!user.provider.isActive) { return res.status(403).json({ message: 'Your vendor account has been deactivated. Please contact support.', }); } } // 6) If everything ok, send token const token = signToken(user._id); res.status(200).json({ status: 'success', token, user: { id: user._id, name: user.name, email: user.email, phone: user.phone, role: user.role, permissions: user.permissions || [], provider: user.role === 'vendor' ? user.provider : undefined, }, }); } catch (err) { res.status(500).json({ message: 'Admin login failed', error: err.message }); } }; exports.getMe = async (req, res) => { try { const user = await User.findById(req.user.id).populate('provider'); if (!user) { return res.status(404).json({ message: 'User not found' }); } res.status(200).json({ status: 'success', user: { id: user._id, name: user.name, email: user.email, phone: user.phone, role: user.role, permissions: user.permissions || [], provider: user.role === 'vendor' ? user.provider : undefined, }, }); } catch (err) { res .status(500) .json({ message: 'Failed to get user details', error: err.message }); } }; exports.protect = async (req, res, next) => { try { let token; // 1) Check if token exists in headers if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } if (!token) { return res.status(401).json({ message: 'You are not logged in!' }); } // 2) Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // 3) Check if user still exists const currentUser = await User.findById(decoded.id); if (!currentUser) { return res.status(401).json({ message: 'User no longer exists.' }); } // 4) Check if user is active if (!currentUser.isActive) { return res.status(403).json({ message: 'Your account has been deactivated. Please contact support.', }); } // 5) Grant access req.user = currentUser; next(); } catch (err) { res .status(401) .json({ message: 'Invalid or expired token', error: err.message }); } }; // Optional authentication - sets req.user if token is valid, but doesn't fail if no token exports.optionalAuth = async (req, res, next) => { try { let token; // 1) Check if token exists in headers if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } // If no token, just continue without setting req.user if (!token) { return next(); } // 2) Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // 3) Check if user still exists const currentUser = await User.findById(decoded.id); if (currentUser) { req.user = currentUser; } next(); } catch (err) { // If token is invalid, just continue without setting req.user next(); } }; exports.logout = (req, res) => { res .status(200) .json({ status: 'success', message: 'Logged out successfully' }); }; exports.deleteAccount = async (req, res) => { try { const { currentPassword } = req.body; // 1) Check if password is provided if (!currentPassword) { return res .status(400) .json({ message: 'Password is required to delete account' }); } // 2) Get user with password const user = await User.findById(req.user._id).select('+password'); // 3) Verify password const isMatch = await bcrypt.compare(currentPassword, user.password); if (!isMatch) { return res.status(401).json({ message: 'Incorrect password' }); } // 4) Delete the account await User.findByIdAndDelete(req.user._id); res.status(200).json({ status: 'success', message: 'Account deleted successfully', }); } catch (err) { res .status(500) .json({ message: 'Failed to delete account', error: err.message }); } }; exports.updateMe = async (req, res) => { try { const { name, email, phone, city, currentPassword } = req.body; // 1) لازم يكتب الباسورد القديمة if (!currentPassword) { return res .status(400) .json({ message: 'You must enter your current password' }); } // 2) نجيب المستخدم ونقارن الباسورد const user = await User.findById(req.user._id).select('+password'); const isMatch = await bcrypt.compare(currentPassword, user.password); if (!isMatch) { return res.status(401).json({ message: 'Current password is incorrect' }); } // 3) نعمل update للبيانات user.name = name || user.name; user.email = email || user.email; user.phone = phone || user.phone; user.city = city || user.city; await user.save(); res.status(200).json({ status: 'success', user: { id: user._id, name: user.name, email: user.email, phone: user.phone, city: user.city, }, }); } catch (err) { res.status(500).json({ status: 'fail', message: 'Update failed', error: err.message, }); } }; exports.changePassword = async (req, res) => { try { const { currentPassword, newPassword, confirmNewPassword } = req.body; // 1) Check all fields exist if (!currentPassword || !newPassword || !confirmNewPassword) { return res.status(400).json({ message: 'All fields are required' }); } // 2) Check new passwords match if (newPassword !== confirmNewPassword) { return res.status(400).json({ message: 'New passwords do not match' }); } // 3) Get current user with password const user = await User.findById(req.user._id).select('+password'); // 4) Check currentPassword is correct const isMatch = await bcrypt.compare(currentPassword, user.password); if (!isMatch) { return res.status(401).json({ message: 'Current password is incorrect' }); } // 5) Set new password (will be hashed by pre-save hook) user.password = newPassword; // 6) Save user await user.save(); // 7) Send password change confirmation email sendPasswordChangeConfirmationEmail(user).catch((err) => console.error( 'Failed to send password change confirmation email:', err.message, ), ); res.status(200).json({ status: 'success', message: 'Password updated successfully', }); } catch (err) { res .status(500) .json({ message: 'Failed to update password', error: err.message }); } }; // FORGOT PASSWORD - sends reset token to email exports.forgotPassword = async (req, res) => { try { const { email } = req.body; if (!email) { return res .status(400) .json({ message: 'Please provide an email address' }); } const user = await User.findOne({ email }); if (!user) { return res .status(404) .json({ message: 'No user found with that email address' }); } const resetToken = user.createPasswordResetToken(); await user.save({ validateBeforeSave: false }); const { sendPasswordResetEmail } = require('../utils/emailService'); try { await sendPasswordResetEmail(user, resetToken); res.status(200).json({ status: 'success', message: 'Password reset link sent to email', }); } catch (err) { user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save({ validateBeforeSave: false }); return res.status(500).json({ message: 'Failed to send password reset email. Please try again later.', error: err.message, }); } } catch (err) { res.status(500).json({ message: 'Failed to process forgot password request', error: err.message, }); } }; // RESET PASSWORD - validates token and sets new password exports.resetPassword = async (req, res) => { try { const crypto = require('crypto'); const { password, confirmPassword } = req.body; const { token } = req.params; if (!password || !confirmPassword) { return res .status(400) .json({ message: 'Please provide password and confirm password' }); } if (password !== confirmPassword) { return res.status(400).json({ message: 'Passwords do not match' }); } if (password.length < 6) { return res .status(400) .json({ message: 'Password must be at least 6 characters' }); } const hashedToken = crypto.createHash('sha256').update(token).digest('hex'); const user = await User.findOne({ passwordResetToken: hashedToken, passwordResetExpires: { $gt: Date.now() }, }); if (!user) { return res.status(400).json({ message: 'Invalid or expired reset token. Please request a new password reset.', }); } user.password = password; user.passwordResetToken = undefined; user.passwordResetExpires = undefined; await user.save(); const jwtToken = signToken(user._id); res.status(200).json({ status: 'success', message: 'Password reset successful', token: jwtToken, }); } catch (err) { res.status(500).json({ message: 'Failed to reset password', error: err.message, }); } };