Spaces:
Running
Running
| 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, | |
| }); | |
| } | |
| }; | |