const bcrypt = require('bcryptjs'); const dayjs = require('dayjs'); const jwt = require('jsonwebtoken'); const { body } = require('express-validator'); const { User } = require('../models'); const env = require('../config/env'); const validate = require('../utils/validation'); const PASSWORD_ROTATION_ROLES = new Set(['Admin', 'HR', 'Safety_Officer']); const PASSWORD_ROTATION_DAYS = 90; const loginValidators = [ body('email').isEmail().withMessage('Valid email is required'), body('password').isLength({ min: 6 }).withMessage('Password is required'), validate ]; const changePasswordValidators = [ body('current_password').isLength({ min: 6 }).withMessage('current_password is required'), body('new_password').isLength({ min: 8 }).withMessage('new_password must be at least 8 characters'), validate ]; function getPasswordDueDate(role, passwordChangedAt) { if (!PASSWORD_ROTATION_ROLES.has(role)) return null; return dayjs(passwordChangedAt).add(PASSWORD_ROTATION_DAYS, 'day').format('DD/MM/YYYY'); } function isPasswordRotationExpired(role, passwordChangedAt) { if (!PASSWORD_ROTATION_ROLES.has(role)) return false; if (!passwordChangedAt) return true; return dayjs().startOf('day').diff(dayjs(passwordChangedAt).startOf('day'), 'day') >= PASSWORD_ROTATION_DAYS; } async function login(req, res, next) { try { const { email, password } = req.body; const user = await User.findOne({ email: String(email).toLowerCase() }).lean(); if (!user || !user.is_active) { return res.status(401).json({ message: 'Invalid credentials' }); } const isMatch = await bcrypt.compare(password, user.password_hash); if (!isMatch) { return res.status(401).json({ message: 'Invalid credentials' }); } const mustResetPassword = Boolean(user.must_reset_password); const passwordRotationExpired = isPasswordRotationExpired(user.role, user.password_changed_at); const mustChangePassword = mustResetPassword || passwordRotationExpired; const token = jwt.sign( { id: String(user._id), full_name: user.full_name, email: user.email, role: user.role, vendor_id: user.vendor_id ? String(user.vendor_id) : null }, env.jwtSecret, { expiresIn: '12h' } ); return res.json({ token, user: { id: user.id, full_name: user.full_name, email: user.email, role: user.role, vendor_id: user.vendor_id ? String(user.vendor_id) : null, must_reset_password: mustResetPassword, must_change_password: mustChangePassword, password_due_date: getPasswordDueDate(user.role, user.password_changed_at) } }); } catch (error) { return next(error); } } async function changePassword(req, res, next) { try { const { current_password, new_password } = req.body; if (current_password === new_password) { return res.status(400).json({ message: 'New password must be different from current password' }); } const user = await User.findOne({ _id: req.user.id, is_active: true }); if (!user) { return res.status(404).json({ message: 'User not found' }); } const isMatch = await bcrypt.compare(current_password, user.password_hash); if (!isMatch) { return res.status(401).json({ message: 'Current password is incorrect' }); } user.password_hash = await bcrypt.hash(new_password, 10); user.must_reset_password = false; user.password_changed_at = new Date(); await user.save(); return res.json({ message: 'Password changed successfully', user: { must_reset_password: false, must_change_password: false, password_due_date: getPasswordDueDate(user.role, new Date()) } }); } catch (error) { return next(error); } } module.exports = { loginValidators, changePasswordValidators, login, changePassword };