feat: add env-based super admin authentication and complete role & permission system
deploy-chabokan / deploy (push) Canceled after 0s

This commit is contained in:
2026-08-18 00:08:04 +03:30
parent 86655f2b69
commit 2b94fdb034
11 changed files with 814 additions and 64 deletions
+6 -6
View File
@@ -2,6 +2,7 @@
const express = require('express')
const userController = require('./userController')
const loginCheck = require('../../middlewares/loginCheck')
const perm = require('../../middlewares/permissionMiddleware')
const router = new express.Router()
@@ -12,18 +13,17 @@ router.post('/login-email', userController.loginUser)
router.post('/logout', userController.logoutUser)
router.use(loginCheck.loginProtect)
// router.use(loginCheck.adminProtect)
router.get('/user/get-self', userController.userGetSelf)
router.get('/', userController.getAllUsers)
router.get('/', perm.requires('getallusers'), userController.getAllUsers)
router.get('/:userId', userController.getUser)
router.get('/:userId', perm.requires('getuser'), userController.getUser)
router.post('/', userController.createUser)
router.post('/', perm.requires('createuser'), userController.createUser)
router.patch('/:userId', userController.updateUser)
router.patch('/:userId', perm.requires('updateuser'), userController.updateUser)
router.delete('/:userId', userController.deleteUser)
router.delete('/:userId', perm.requires('deleteuser'), userController.deleteUser)
module.exports = router
+113 -19
View File
@@ -1,5 +1,6 @@
// userService.js
const User = require('./userModel')
const Role = require('../role/roleModel')
const AppError = require('../../globalErrorHandler/AppError')
const LoginAttempt = require('./loginAttemptModel')
const jwt = require('jsonwebtoken')
@@ -13,19 +14,19 @@ const signToken = (id) => {
}
const signup = async (name, email, password, passwordConfirm) => {
let userExist = await User.findOne({ email }, null, null)
let userExist = await User.findOne({ email: email.toLowerCase().trim() }, null, null)
if (userExist) {
throw new AppError('UserAlreadyExist', 500, {})
throw new AppError('UserAlreadyExist', 400, {})
}
if (password != passwordConfirm) throw new AppError('PasswordIncorrect', 401, {})
if (password !== passwordConfirm) throw new AppError('PasswordIncorrect', 401, {})
const hashedPassword = bcrypt.hashSync(password, 10)
const user = await createUser({
name,
email,
email: email.toLowerCase().trim(),
password: hashedPassword,
passwordConfirm: undefined,
})
@@ -66,7 +67,6 @@ const loginAttempt = async (phrase) => {
return
}
// if user phrase(email or phone) exist return error with expire time left for login
if (attemptRecord.attempts >= limit) {
const limitTime = Math.max(0, Math.ceil((attemptRecord.expiresAt.getTime() - Date.now()) / 1000))
const minutes = (limitTime / 60).toFixed(0)
@@ -85,56 +85,150 @@ const loginUser = async (email, password) => {
throw new AppError('ProvideEmailAndPassword', 400)
}
const user = await User.findOne({ email }, null, null).select('+password')
const inputPhrase = email.toLowerCase().trim()
if (email) {
await loginAttempt(email)
// ─── 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, {})
}
// set user password to undefined before sending data
if (user.isActive === false) {
throw new AppError('UserIsInactive', 403, {})
}
user.password = undefined
return { token: signToken(user._id), user }
}
const getAllUsers = async () => {
const users = await User.find({}, null, null)
return users
return User.find({}).populate('role').sort({ createdAt: -1 })
}
const getUser = async (userId) => {
const user = await User.findById({ _id: userId }, null, null)
const user = await User.findById(userId).populate('role')
if (!user) {
throw AppError('NoUserFound', 404, {})
throw new AppError('NoUserFound', 404, {})
}
return user
}
const createUser = async (data) => {
return User.create(data, null)
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 user = await User.findOneAndUpdate({ _id: userId }, data, { new: true })
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 AppError('NoUserFound', 404, {})
throw new AppError('NoUserFound', 404, {})
}
return user
}
const deleteUser = async (userId) => {
const user = await User.findOneAndDelete({ _id: userId })
const user = await User.findByIdAndUpdate(userId, { isActive: false }, { new: true })
if (!user) {
throw AppError('NoUserFound', 404, {})
throw new AppError('NoUserFound', 404, {})
}
return null