samoulla-backend / controllers /authController.js
Samoulla Sync Bot
Auto-deploy Samoulla Backend: b68e45770de26ed39feb4b1c0925e5345eb3a61d
634b9bb
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,
});
}
};