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
+516
View File
@@ -0,0 +1,516 @@
/**
* System-wide Permission Definitions
* Organized by functional category for the Belonging Manager platform.
*/
const permissionsList = [
// ─── مدیریت کاربران (User Management) ──────────────────────────────────────
{
englishName: 'View All Users',
nativeName: 'مشاهده لیست کاربران',
key: 'getallusers',
category: 'کاربران',
},
{
englishName: 'View User Details',
nativeName: 'مشاهده جزئیات کاربر',
key: 'getuser',
category: 'کاربران',
},
{
englishName: 'Create User',
nativeName: 'ایجاد کاربر جدید',
key: 'createuser',
category: 'کاربران',
},
{
englishName: 'Update User',
nativeName: 'ویرایش کاربر',
key: 'updateuser',
category: 'کاربران',
},
{
englishName: 'Delete User',
nativeName: 'حذف کاربر',
key: 'deleteuser',
category: 'کاربران',
},
// ─── مدیریت نقش‌ها و دسترسی‌ها (Roles & Permissions) ────────────────────────
{
englishName: 'View All Roles',
nativeName: 'مشاهده لیست نقش‌ها',
key: 'getallroles',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'View Role Details',
nativeName: 'مشاهده جزئیات نقش',
key: 'getarole',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Create Role',
nativeName: 'ایجاد نقش جدید',
key: 'createarole',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Update Role',
nativeName: 'ویرایش نقش و دسترسی‌ها',
key: 'updatearole',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Delete Role',
nativeName: 'حذف نقش',
key: 'deletearole',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'View All Permissions',
nativeName: 'مشاهده لیست دسترسی‌ها',
key: 'getallpermissions',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Create Permission',
nativeName: 'ایجاد دسترسی',
key: 'createpermission',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'View Permission',
nativeName: 'مشاهده دسترسی',
key: 'getpermission',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Update Permission',
nativeName: 'ویرایش دسترسی',
key: 'updatepermission',
category: 'نقش‌ها و دسترسی‌ها',
},
{
englishName: 'Delete Permission',
nativeName: 'حذف دسترسی',
key: 'deletepermission',
category: 'نقش‌ها و دسترسی‌ها',
},
// ─── مدیریت املاک و اموال (Property / Belongings) ─────────────────────────
{
englishName: 'View All Properties',
nativeName: 'مشاهده لیست املاک',
key: 'getallproperties',
category: 'املاک و اموال',
},
{
englishName: 'View Property Details',
nativeName: 'مشاهده جزئیات ملک',
key: 'getaproperty',
category: 'املاک و اموال',
},
{
englishName: 'Create Property',
nativeName: 'ایجاد ملک جدید',
key: 'createaproperty',
category: 'املاک و اموال',
},
{
englishName: 'Update Property',
nativeName: 'ویرایش ملک',
key: 'updateaproperty',
category: 'املاک و اموال',
},
{
englishName: 'Delete Property',
nativeName: 'حذف ملک',
key: 'deleteaproperty',
category: 'املاک و اموال',
},
{
englishName: 'Search Properties',
nativeName: 'جستجوی املاک',
key: 'searchproperties',
category: 'املاک و اموال',
},
{
englishName: 'Index Properties',
nativeName: 'ایندکس‌سازی املاک',
key: 'indexproperties',
category: 'املاک و اموال',
},
// Aliases for Belongings
{
englishName: 'View All Belongings',
nativeName: 'مشاهده لیست اموال (سازگاری)',
key: 'getallbelongings',
category: 'املاک و اموال',
},
{
englishName: 'View Belonging Details',
nativeName: 'مشاهده جزئیات مال (سازگاری)',
key: 'getabelonging',
category: 'املاک و اموال',
},
{
englishName: 'Create Belonging',
nativeName: 'ایجاد مال (سازگاری)',
key: 'createabelonging',
category: 'املاک و اموال',
},
{
englishName: 'Update Belonging',
nativeName: 'ویرایش مال (سازگاری)',
key: 'updateabelonging',
category: 'املاک و اموال',
},
{
englishName: 'Delete Belonging',
nativeName: 'حذف مال (سازگاری)',
key: 'deleteabelonging',
category: 'املاک و اموال',
},
{
englishName: 'Search Belongings',
nativeName: 'جستجوی اموال (سازگاری)',
key: 'searchbelongings',
category: 'املاک و اموال',
},
{
englishName: 'Index Belongings',
nativeName: 'ایندکس‌سازی اموال (سازگاری)',
key: 'indexbelongings',
category: 'املاک و اموال',
},
// ─── دسته‌بندی‌ها (Categories) ─────────────────────────────────────────────
{
englishName: 'View All Categories',
nativeName: 'مشاهده لیست دسته‌بندی‌ها',
key: 'getallcategories',
category: 'دسته‌بندی‌ها',
},
{
englishName: 'View Category Tree',
nativeName: 'مشاهده درخت دسته‌بندی‌ها',
key: 'getcategorytree',
category: 'دسته‌بندی‌ها',
},
{
englishName: 'View Category Details',
nativeName: 'مشاهده جزئیات دسته‌بندی',
key: 'getcategory',
category: 'دسته‌بندی‌ها',
},
{
englishName: 'Create Category',
nativeName: 'ایجاد دسته‌بندی جدید',
key: 'createcategory',
category: 'دسته‌بندی‌ها',
},
{
englishName: 'Update Category',
nativeName: 'ویرایش دسته‌بندی',
key: 'updatecategory',
category: 'دسته‌بندی‌ها',
},
{
englishName: 'Delete Category',
nativeName: 'حذف دسته‌بندی',
key: 'deletecategory',
category: 'دسته‌بندی‌ها',
},
// ─── ویژگی‌ها (Features) ──────────────────────────────────────────────────
{
englishName: 'View All Features',
nativeName: 'مشاهده ویژگی‌ها',
key: 'getallfeatures',
category: 'ویژگی‌ها',
},
{
englishName: 'View Feature Details',
nativeName: 'مشاهده جزئیات ویژگی',
key: 'getafeature',
category: 'ویژگی‌ها',
},
{
englishName: 'Create Feature',
nativeName: 'ایجاد ویژگی جدید',
key: 'createfeature',
category: 'ویژگی‌ها',
},
{
englishName: 'Update Feature',
nativeName: 'ویرایش ویژگی',
key: 'updateafeature',
category: 'ویژگی‌ها',
},
{
englishName: 'Delete Feature',
nativeName: 'حذف ویژگی',
key: 'deleteafeature',
category: 'ویژگی‌ها',
},
// ─── وضعیت املاک (Property Statuses) ──────────────────────────────────────
{
englishName: 'View All Property Statuses',
nativeName: 'مشاهده وضعیت‌های ملک',
key: 'getallpropertystatuses',
category: 'وضعیت‌های ملک',
},
{
englishName: 'View Property Status Details',
nativeName: 'مشاهده جزئیات وضعیت ملک',
key: 'getapropertystatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Create Property Status',
nativeName: 'ایجاد وضعیت ملک جدید',
key: 'createapropertystatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Update Property Status',
nativeName: 'ویرایش وضعیت ملک',
key: 'updateapropertystatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Delete Property Status',
nativeName: 'حذف وضعیت ملک',
key: 'deleteapropertystatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'View All Belonging Statuses',
nativeName: 'مشاهده وضعیت‌های اموال (سازگاری)',
key: 'getallbelongingstatuses',
category: 'وضعیت‌های ملک',
},
{
englishName: 'View Belonging Status Details',
nativeName: 'مشاهده جزئیات وضعیت مال (سازگاری)',
key: 'getabelongingstatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Create Belonging Status',
nativeName: 'ایجاد وضعیت مال (سازگاری)',
key: 'createabelongingstatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Update Belonging Status',
nativeName: 'ویرایش وضعیت مال (سازگاری)',
key: 'updateabelongingstatus',
category: 'وضعیت‌های ملک',
},
{
englishName: 'Delete Belonging Status',
nativeName: 'حذف وضعیت مال (سازگاری)',
key: 'deleteabelongingstatus',
category: 'وضعیت‌های ملک',
},
// ─── مدیریت انبار (Warehouse & Inventory) ──────────────────────────────────
{
englishName: 'View All Warehouses',
nativeName: 'مشاهده انبارها',
key: 'getallwarehouses',
category: 'مدیریت انبار',
},
{
englishName: 'View Warehouse Details',
nativeName: 'مشاهده جزئیات انبار',
key: 'getwarehouse',
category: 'مدیریت انبار',
},
{
englishName: 'Create Warehouse',
nativeName: 'ایجاد انبار جدید',
key: 'createwarehouse',
category: 'مدیریت انبار',
},
{
englishName: 'Update Warehouse',
nativeName: 'ویرایش انبار',
key: 'updatewarehouse',
category: 'مدیریت انبار',
},
{
englishName: 'Delete Warehouse',
nativeName: 'حذف انبار',
key: 'deletewarehouse',
category: 'مدیریت انبار',
},
{
englishName: 'View Warehouse Items',
nativeName: 'مشاهده اقلام انبار',
key: 'getallwarehouseitems',
category: 'مدیریت انبار',
},
{
englishName: 'View Warehouse Item Details',
nativeName: 'مشاهده جزئیات قلم انبار',
key: 'getwarehouseitem',
category: 'مدیریت انبار',
},
{
englishName: 'Create Warehouse Item',
nativeName: 'ایجاد قلم انبار جدید',
key: 'createwarehouseitem',
category: 'مدیریت انبار',
},
{
englishName: 'Update Warehouse Item',
nativeName: 'ویرایش قلم انبار',
key: 'updatewarehouseitem',
category: 'مدیریت انبار',
},
{
englishName: 'Delete Warehouse Item',
nativeName: 'حذف قلم انبار',
key: 'deletewarehouseitem',
category: 'مدیریت انبار',
},
{
englishName: 'View Warehouse Transactions',
nativeName: 'مشاهده تراکنش‌های انبار',
key: 'getallwarehousetransactions',
category: 'مدیریت انبار',
},
{
englishName: 'Create Warehouse Transaction',
nativeName: 'ثبت تراکنش انبار',
key: 'createwarehousetransaction',
category: 'مدیریت انبار',
},
{
englishName: 'Delete Warehouse Transaction',
nativeName: 'حذف تراکنش انبار',
key: 'deletewarehousetransaction',
category: 'مدیریت انبار',
},
{
englishName: 'View Warehouse Stock',
nativeName: 'مشاهده موجودی انبار',
key: 'getwarehousestock',
category: 'مدیریت انبار',
},
// ─── مدیریت فایل‌ها (Files) ────────────────────────────────────────────────
{
englishName: 'View All Files',
nativeName: 'مشاهده لیست فایل‌ها',
key: 'getallfiles',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'View File Details',
nativeName: 'مشاهده جزئیات فایل',
key: 'getfile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Create File Record',
nativeName: 'ایجاد رکورد فایل',
key: 'createfile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Update File',
nativeName: 'ویرایش فایل',
key: 'updatefile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Delete File',
nativeName: 'حذف فایل',
key: 'deletefile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Upload Single File',
nativeName: 'آپلود فایل تکی',
key: 'uploadsinglefile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Upload Multiple Files',
nativeName: 'آپلود چندتایی فایل',
key: 'uploadmultifile',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Get Bucket Files',
nativeName: 'مشاهده فایل‌های ذخیره‌سازی ابری',
key: 'getbucketfiles',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Get Images',
nativeName: 'مشاهده تصاویر',
key: 'getimages',
category: 'مدیریت فایل‌ها',
},
{
englishName: 'Delete Bucket Files',
nativeName: 'حذف فایل از فضای ابری',
key: 'deletebucketfiles',
category: 'مدیریت فایل‌ها',
},
// ─── مدیریت تراکنش‌ها (Transactions) ────────────────────────────────────────
{
englishName: 'View All Transactions',
nativeName: 'مشاهده تراکنش‌ها',
key: 'getalltransactions',
category: 'تراکنش‌ها',
},
{
englishName: 'View Transaction Details',
nativeName: 'مشاهده جزئیات تراکنش',
key: 'gettransaction',
category: 'تراکنش‌ها',
},
{
englishName: 'Create Transaction',
nativeName: 'ثبت تراکنش جدید',
key: 'createtransaction',
category: 'تراکنش‌ها',
},
{
englishName: 'Update Transaction',
nativeName: 'ویرایش تراکنش',
key: 'updatetransaction',
category: 'تراکنش‌ها',
},
{
englishName: 'Delete Transaction',
nativeName: 'حذف تراکنش',
key: 'deletetransaction',
category: 'تراکنش‌ها',
},
// ─── سیستم و داشبورد (Dashboard & System) ──────────────────────────────────
{
englishName: 'View Dashboard',
nativeName: 'مشاهده داشبورد',
key: 'viewdashboard',
category: 'سیستم و داشبورد',
},
{
englishName: 'Admin Dashboard Access',
nativeName: 'دسترسی مدیریت داشبورد',
key: 'admindashboard',
category: 'سیستم و داشبورد',
},
]
module.exports = permissionsList
+1 -1
View File
@@ -25,7 +25,7 @@ const permissionSchema = new mongoose.Schema({
})
permissionSchema.pre(/^find/, function (next) {
this.find({ active: { $ne: false } })
this.find({ isActive: { $ne: false } })
next()
})
+6 -2
View File
@@ -1,12 +1,16 @@
const Permission = require('./permissionModel')
const permissionsList = require('./permissionList')
const chalk = require('chalk')
const runSeeder = async () => {
const permissions = []
await Permission.deleteMany({})
console.log(chalk.red('Deleted all permission records.'))
const permissions = permissionsList.map((p) => ({
...p,
isActive: true,
}))
await Permission.insertMany(permissions, null)
console.log(
chalk.green.bold(
+45 -1
View File
@@ -1,8 +1,52 @@
const Permission = require('./permissionModel')
const permissionsList = require('./permissionList')
const AppError = require('../../globalErrorHandler/AppError')
module.exports.syncPermissions = async () => {
try {
let createdCount = 0
let updatedCount = 0
for (const item of permissionsList) {
const existing = await Permission.findOne({ key: item.key })
if (!existing) {
await Permission.create({
englishName: item.englishName,
nativeName: item.nativeName,
key: item.key,
category: item.category,
isActive: true,
})
createdCount++
} else {
let changed = false
if (existing.englishName !== item.englishName) {
existing.englishName = item.englishName
changed = true
}
if (existing.nativeName !== item.nativeName) {
existing.nativeName = item.nativeName
changed = true
}
if (existing.category !== item.category) {
existing.category = item.category
changed = true
}
if (changed) {
await existing.save()
updatedCount++
}
}
}
console.log(`[Permissions] Sync complete: ${createdCount} created, ${updatedCount} updated.`)
} catch (error) {
console.error('[Permissions] Error during sync:', error.message)
}
}
module.exports.getAllPermissions = async () => {
return Permission.find({})
return Permission.find({}).sort({ category: 1, englishName: 1 })
}
module.exports.createPermission = async (data) => {
+5 -4
View File
@@ -2,15 +2,16 @@ const Role = require('./roleModel')
const AppError = require('../../globalErrorHandler/AppError')
module.exports.getAllRoles = async () => {
return Role.find({}, null, null)
return Role.find({}).populate('permissions').sort({ createdAt: -1 })
}
module.exports.createRole = async (data) => {
return await Role.create(data)
const role = await Role.create(data)
return Role.findById(role._id).populate('permissions')
}
module.exports.getRole = async (roleId) => {
const role = await Role.findById(roleId)
const role = await Role.findById(roleId).populate('permissions')
if (!role) {
throw new AppError('NoRoleFound', 404, {})
@@ -20,7 +21,7 @@ module.exports.getRole = async (roleId) => {
}
module.exports.updateRole = async (roleId, data) => {
const role = await Role.findOneAndUpdate({ _id: roleId }, data, { new: true })
const role = await Role.findOneAndUpdate({ _id: roleId }, data, { new: true }).populate('permissions')
if (!role) {
throw new AppError('NoRoleFound', 404, {})
+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
+3
View File
@@ -4,6 +4,9 @@ PORT=3000
JWT_SECRET=Y@RJ$WT!@K0NS@MPL!
LOGIN_ATTEMPT_LIMIT=900
SUPER_ADMIN_ROLE_ID=66a2da5b9b989a8909948848
SUPER_ADMIN_ENABLED=true
SUPER_ADMIN_EMAIL=superadmin@mbmanager.ir
SUPER_ADMIN_PASSWORD=SuperAdmin@123456!
TYPE_SENSE_URL=localhost:8109
TYPE_SENSE_PROTOCOL=http
TYPE_SENSE_API_KEY=vYQq7pTgl6ACoRy
+40
View File
@@ -28,5 +28,45 @@
"code": "6",
"english": "You need to log in first.",
"persian": "لطفا ابتدا وارد شوید."
},
"UserAlreadyExist": {
"code": "7",
"english": "A user with this email already exists.",
"persian": "کاربری با این آدرس ایمیل قبلاً ثبت شده است."
},
"NoPermissionAccess": {
"code": "8",
"english": "You do not have permission to perform this action.",
"persian": "شما دسترسی لازم برای انجام این عملیات را ندارید."
},
"ProvideEmailPasswordRole": {
"code": "9",
"english": "Please provide email, password, and role.",
"persian": "لطفاً ایمیل، رمز عبور و نقش کاربر را مشخص کنید."
},
"ProvideEmailAndPassword": {
"code": "10",
"english": "Please provide email and password.",
"persian": "لطفاً ایمیل و رمز عبور را وارد کنید."
},
"IncorrectEmailOrPassword": {
"code": "11",
"english": "Incorrect email or password.",
"persian": "ایمیل یا رمز عبور اشتباه است."
},
"UserIsInactive": {
"code": "12",
"english": "Your account has been deactivated.",
"persian": "حساب کاربری شما غیرفعال شده است."
},
"NoRoleFound": {
"code": "13",
"english": "No role found with that ID.",
"persian": "نقش مورد نظر یافت نشد."
},
"NoPermissionFound": {
"code": "14",
"english": "No permission found with that ID.",
"persian": "دسترسی مورد نظر یافت نشد."
}
}
+71 -27
View File
@@ -1,30 +1,73 @@
const Permission = require('../components/permission/permissionModel')
const Role = require('../components/role/roleModel')
const AppError = require('../globalErrorHandler/AppError')
const catchAsync = require('../globalErrorHandler/catchAsync')
const User = require('../components/user/userModel')
const ErrorLocalization = require('../globalErrorHandler/errorLocalization')
const el = new ErrorLocalization()
let checker = (arr, target) => arr.includes(target)
let checker = (arr, target) => {
if (!Array.isArray(arr)) return false
return arr.some((item) => {
const id = (item?._id || item)?.toString()
const key = item?.key
return id === target?.toString() || (key && key === target)
})
}
const requires = (...permissions) => {
return async (req, res, next) => {
const role = await Role.findById(req.user.role)
const perm = await Permission.findOne({ key: permissions[0] })
const rolePerm = role?.permissions || []
const userPerm = req.user.extraPermissions || []
try {
if (!req.user) {
return next(new AppError('NotLoggedIn', 401, {}))
}
// some features: permission denied when all roles are not same --> we want to access user with either roles
const superAdminRoleId = process.env.SUPER_ADMIN_ROLE_ID || '66a2da5b9b989a8909948848'
// Super Admin bypass: full access to all endpoints
if (
!checker(rolePerm, perm?._id) &&
!checker(userPerm, perm?._id) &&
role._id.toString() !== process.env.SUPER_ADMIN_ROLE_ID
req.user.isAdmin === true ||
req.user.role?.englishName === 'SuperAdmin' ||
req.user.role?._id?.toString() === superAdminRoleId ||
req.user.role?.toString() === superAdminRoleId
) {
throw new AppError('NoPermissionAccess', 403, {})
return next()
}
// Retrieve role and its permissions
let role = req.user.role
if (role && (typeof role === 'string' || !role.permissions || !role.englishName)) {
role = await Role.findById(role._id || role).populate('permissions')
}
const rolePermissions = role?.permissions || []
const userExtraPermissions = req.user.extraPermissions || []
// Find target permissions from DB
const targetPerms = await Permission.find({ key: { $in: permissions } })
const targetIds = targetPerms.map((p) => p._id.toString())
const targetKeys = permissions
// Check if any required permission is present in role or user extra permissions
const hasPermission =
rolePermissions.some((rp) => {
const rpId = (rp._id || rp).toString()
const rpKey = rp.key
return targetIds.includes(rpId) || targetKeys.includes(rpKey)
}) ||
userExtraPermissions.some((up) => {
const upId = (up._id || up).toString()
const upKey = up.key
return targetIds.includes(upId) || targetKeys.includes(upKey)
})
if (!hasPermission) {
return next(new AppError('NoPermissionAccess', 403, {}))
}
next()
} catch (err) {
next(err)
}
}
}
@@ -36,23 +79,26 @@ const adminCheck = () => {
}).populate('role')
if (!user) {
throw new AppError('UserDoesNotExist', 403, {})
return next(new AppError('UserDoesNotExist', 403, {}))
}
const perm = await Permission.findOne(
{ key: 'admindashboard' },
null,
null
)
const superAdminRoleId = process.env.SUPER_ADMIN_ROLE_ID || '66a2da5b9b989a8909948848'
if (
user.isAdmin === true ||
user.role?.englishName === 'SuperAdmin' ||
user.role?._id?.toString() === superAdminRoleId ||
user.role?.toString() === superAdminRoleId
) {
return next()
}
const perm = await Permission.findOne({ key: 'admindashboard' })
let rolePerm = user.role?.permissions || []
let userPerm = user.extraPermissions || []
// some features: permission denied when all roles are not same --> we want to access user with either roles
if (
!checker(rolePerm, perm?._id) &&
!checker(userPerm, perm?._id) &&
user.role?._id.toString() !== process.env.SUPER_ADMIN_ROLE_ID
) {
throw new AppError('NoPermissionAccess', 403, {})
if (!checker(rolePerm, perm?._id) && !checker(userPerm, perm?._id)) {
return next(new AppError('NoPermissionAccess', 403, {}))
}
next()
@@ -61,9 +107,7 @@ const adminCheck = () => {
err.message = el.getErrorText(err.name, err.parameters, 'persian')
res.status(500).json({
status: 'error',
message:
err.message ||
'An error occurred while checking permissions.',
message: err.message || 'An error occurred while checking permissions.',
})
}
}
+4
View File
@@ -19,6 +19,10 @@ module.exports = (app) => {
// Create a document to force MongoDB to actually create the DB and collection
await MyModel.create({ name: 'test' })
// Auto-sync permissions defined in permissionList.js
const { syncPermissions } = require('../components/permission/permissionService')
await syncPermissions()
app.listen(port, function () {
console.log('Server Started On Port ' + port)
})