Files
mbmanager-api/components/user/userService.js
T
2026-08-18 00:08:04 +03:30

246 lines
7.5 KiB
JavaScript

// userService.js
const User = require('./userModel')
const Role = require('../role/roleModel')
const AppError = require('../../globalErrorHandler/AppError')
const LoginAttempt = require('./loginAttemptModel')
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const maxAge = 60 * 60 * 24 * 90
const signToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: maxAge,
})
}
const signup = async (name, email, password, passwordConfirm) => {
let userExist = await User.findOne({ email: email.toLowerCase().trim() }, null, null)
if (userExist) {
throw new AppError('UserAlreadyExist', 400, {})
}
if (password !== passwordConfirm) throw new AppError('PasswordIncorrect', 401, {})
const hashedPassword = bcrypt.hashSync(password, 10)
const user = await createUser({
name,
email: email.toLowerCase().trim(),
password: hashedPassword,
passwordConfirm: undefined,
})
const token = signToken(user._id)
const cookieOptions = {
expires: new Date(Date.now() + maxAge * 24 * 60 * 60 * 1000),
httpOnly: true,
}
if (process.env.NODE_ENV === 'production') cookieOptions.secure = true
// set user password to undefined before sending data
user.password = undefined
return { token, user, cookieOptions }
}
const loginAttempt = async (phrase) => {
const key = phrase + '#attempts'
const limit = parseInt(process.env.LOGIN_ATTEMPT_LIMIT) || 10
const ttlSeconds = 3600
const now = new Date()
let attemptRecord = await LoginAttempt.findOne({ key })
if (attemptRecord && attemptRecord.expiresAt <= now) {
await LoginAttempt.deleteOne({ _id: attemptRecord._id })
attemptRecord = null
}
if (!attemptRecord) {
const expiresAt = new Date(Date.now() + ttlSeconds * 1000)
await LoginAttempt.create({
key,
attempts: 0,
expiresAt,
})
return
}
if (attemptRecord.attempts >= limit) {
const limitTime = Math.max(0, Math.ceil((attemptRecord.expiresAt.getTime() - Date.now()) / 1000))
const minutes = (limitTime / 60).toFixed(0)
throw new AppError('MaximumLoginReached', 500, {
minutes,
})
}
attemptRecord.attempts += 1
attemptRecord.expiresAt = new Date(Date.now() + ttlSeconds * 1000)
await attemptRecord.save()
}
const loginUser = async (email, password) => {
if (!email || !password) {
throw new AppError('ProvideEmailAndPassword', 400)
}
const inputPhrase = email.toLowerCase().trim()
// ─── 1) Environment-based Super Admin Authentication ────────────────────
const superAdminEnabled = process.env.SUPER_ADMIN_ENABLED !== 'false'
const superAdminEmail = (process.env.SUPER_ADMIN_EMAIL || process.env.SUPER_ADMIN_USERNAME || 'superadmin@mbmanager.ir').toLowerCase().trim()
const superAdminUsername = (process.env.SUPER_ADMIN_USERNAME || 'superadmin').toLowerCase().trim()
const superAdminPassword = process.env.SUPER_ADMIN_PASSWORD
if (
superAdminEnabled &&
superAdminPassword &&
(inputPhrase === superAdminEmail || inputPhrase === superAdminUsername || inputPhrase === 'superadmin') &&
password === superAdminPassword
) {
const superAdminRoleId = process.env.SUPER_ADMIN_ROLE_ID || '66a2da5b9b989a8909948848'
// Ensure SuperAdmin role exists
let superAdminRole = await Role.findById(superAdminRoleId)
if (!superAdminRole) {
superAdminRole = await Role.findOne({ englishName: 'SuperAdmin' })
}
if (!superAdminRole) {
superAdminRole = await Role.create({
_id: superAdminRoleId,
englishName: 'SuperAdmin',
nativeName: 'ادمین اصلی',
permissions: [],
isActive: true,
})
}
// Ensure SuperAdmin user exists in DB
let superAdminUser = await User.findOne({
$or: [{ email: superAdminEmail }, { _id: '66ddb52302f30f105de39e61' }],
})
if (!superAdminUser) {
superAdminUser = await User.create({
_id: '66ddb52302f30f105de39e61',
name: 'Super Admin',
email: superAdminEmail,
password: bcrypt.hashSync(superAdminPassword, 10),
role: superAdminRole._id,
isAdmin: true,
isActive: true,
})
} else {
superAdminUser.isAdmin = true
superAdminUser.role = superAdminRole._id
superAdminUser.isActive = true
await superAdminUser.save()
}
const populatedUser = await User.findById(superAdminUser._id).populate('role')
return { token: signToken(populatedUser._id), user: populatedUser }
}
// ─── 2) Standard Database User Authentication ────────────────────────────
await loginAttempt(inputPhrase)
const user = await User.findOne({ email: inputPhrase }, null, null).select('+password').populate('role')
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new AppError('IncorrectEmailOrPassword', 401, {})
}
if (user.isActive === false) {
throw new AppError('UserIsInactive', 403, {})
}
user.password = undefined
return { token: signToken(user._id), user }
}
const getAllUsers = async () => {
return User.find({}).populate('role').sort({ createdAt: -1 })
}
const getUser = async (userId) => {
const user = await User.findById(userId).populate('role')
if (!user) {
throw new AppError('NoUserFound', 404, {})
}
return user
}
const createUser = async (data) => {
if (!data.email || !data.password || !data.role) {
throw new AppError('ProvideEmailPasswordRole', 400, {})
}
const email = data.email.toLowerCase().trim()
const existing = await User.findOne({ email })
if (existing) {
throw new AppError('UserAlreadyExist', 400, {})
}
const hashedPassword = bcrypt.hashSync(data.password, 10)
const user = await User.create({
name: data.name,
email,
password: hashedPassword,
role: data.role,
isAdmin: data.isAdmin || false,
isActive: data.isActive !== undefined ? data.isActive : true,
})
return User.findById(user._id).populate('role')
}
const updateUser = async (userId, data) => {
const updateData = { ...data }
if (updateData.email) {
updateData.email = updateData.email.toLowerCase().trim()
const existing = await User.findOne({ email: updateData.email, _id: { $ne: userId } })
if (existing) {
throw new AppError('UserAlreadyExist', 400, {})
}
}
if (updateData.password && updateData.password.trim().length > 0) {
updateData.password = bcrypt.hashSync(updateData.password, 10)
} else {
delete updateData.password
}
const user = await User.findOneAndUpdate({ _id: userId }, updateData, { new: true }).populate('role')
if (!user) {
throw new AppError('NoUserFound', 404, {})
}
return user
}
const deleteUser = async (userId) => {
const user = await User.findByIdAndUpdate(userId, { isActive: false }, { new: true })
if (!user) {
throw new AppError('NoUserFound', 404, {})
}
return null
}
module.exports = {
loginUser,
getUser,
getAllUsers,
deleteUser,
updateUser,
createUser,
signup,
}