feat: add env-based super admin authentication and complete role & permission system
deploy-chabokan / deploy (push) Canceled after 0s
deploy-chabokan / deploy (push) Canceled after 0s
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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, {})
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": "دسترسی مورد نظر یافت نشد."
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
if (
|
||||
!checker(rolePerm, perm?._id) &&
|
||||
!checker(userPerm, perm?._id) &&
|
||||
role._id.toString() !== process.env.SUPER_ADMIN_ROLE_ID
|
||||
) {
|
||||
throw new AppError('NoPermissionAccess', 403, {})
|
||||
const superAdminRoleId = process.env.SUPER_ADMIN_ROLE_ID || '66a2da5b9b989a8909948848'
|
||||
|
||||
// Super Admin bypass: full access to all endpoints
|
||||
if (
|
||||
req.user.isAdmin === true ||
|
||||
req.user.role?.englishName === 'SuperAdmin' ||
|
||||
req.user.role?._id?.toString() === superAdminRoleId ||
|
||||
req.user.role?.toString() === superAdminRoleId
|
||||
) {
|
||||
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)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user