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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user