feat: implement identity and security features
- Added account types and privileged account types constants. - Created admin user controller for updating user security fields. - Developed role assignment controller for managing user roles. - Implemented validation middleware for request schemas. - Defined user role and auth session models for database interactions. - Created services for authentication, email notifications, and OTP handling. - Developed OAuth service for Google and Apple authentication. - Added JWT utility functions for token generation and verification. - Implemented comprehensive tests for authentication, session management, and password policies. - Created migration for updating user schema and adding new tables for auth sessions and user identities.
This commit is contained in:
@@ -1,100 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Niolla
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is proprietary and confidential.
|
||||
* Unauthorized copying, modification, distribution, or use
|
||||
* of this file, via any medium, is strictly prohibited.
|
||||
*/
|
||||
const { ACCOUNT_TYPES } = require("../constants/accountTypes");
|
||||
|
||||
// app/middleware/permission.middleware.js
|
||||
|
||||
const hasPermission = (userPermissions, requiredPermission) => {
|
||||
return userPermissions.some(p => {
|
||||
if (p === requiredPermission) return true;
|
||||
|
||||
// wildcard support
|
||||
if (p.endsWith(".*")) {
|
||||
const prefix = p.slice(0, -2);
|
||||
return requiredPermission.startsWith(prefix);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
const authorizedAccountType = (allowedTypes) => (req, res, next) => {
|
||||
if (!req.user) return res.status(401).json({ success: false, error: { code: "UNAUTHORIZED", message: "Authentication required" } });
|
||||
if (req.user.accountType !== ACCOUNT_TYPES.SUPER_ADMIN && !allowedTypes.includes(req.user.accountType)) return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "Access denied" } });
|
||||
next();
|
||||
};
|
||||
|
||||
const methodToAction = {
|
||||
GET: "view",
|
||||
POST: "create",
|
||||
PUT: "update",
|
||||
PATCH: "update",
|
||||
DELETE: "delete"
|
||||
const checkPermission = (baseOrFull, options = {}) => (req, res, next) => {
|
||||
if (!req.user) return res.status(401).json({ success: false, error: { code: "UNAUTHORIZED", message: "Authentication required" } });
|
||||
if (req.user.accountType === ACCOUNT_TYPES.SUPER_ADMIN) return next();
|
||||
const actions = { GET: "view", POST: "create", PUT: "update", PATCH: "update", DELETE: "delete" };
|
||||
const required = options.custom ? baseOrFull : `${baseOrFull}.${actions[req.method]}`;
|
||||
const permissions = req.user.permissions || [];
|
||||
const allowed = permissions.includes(required) || permissions.some((value) => value.endsWith(".*") && required.startsWith(value.slice(0, -1)));
|
||||
if (!allowed) return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "Access denied" } });
|
||||
next();
|
||||
};
|
||||
|
||||
const checkPermission = (baseOrFull, options = {}) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized"
|
||||
});
|
||||
}
|
||||
|
||||
// admin bypass
|
||||
if (req.user.accountType === "admin") {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (typeof baseOrFull !== "string") {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Permission must be a string"
|
||||
});
|
||||
}
|
||||
|
||||
let requiredPermission;
|
||||
|
||||
if (options.custom) {
|
||||
requiredPermission = baseOrFull;
|
||||
} else {
|
||||
const action = methodToAction[req.method];
|
||||
|
||||
if (!action) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Unknown HTTP method"
|
||||
});
|
||||
}
|
||||
|
||||
requiredPermission = `${baseOrFull}.${action}`;
|
||||
}
|
||||
|
||||
const userPermissions = req.user.permissions || [];
|
||||
|
||||
const hasPermission =
|
||||
userPermissions.includes(requiredPermission) ||
|
||||
userPermissions.includes(`${baseOrFull}.*`);
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: `Forbidden - Missing ${requiredPermission}`
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
const requireOwnership = (param = "id") => (req, res, next) => {
|
||||
if (req.user.accountType === ACCOUNT_TYPES.SUPER_ADMIN || req.user.accountType === ACCOUNT_TYPES.ADMIN || req.user.id === req.params[param]) return next();
|
||||
return res.status(403).json({ success: false, error: { code: "FORBIDDEN", message: "Access denied" } });
|
||||
};
|
||||
|
||||
const authorizedAccountType = (allowedTypes) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || !allowedTypes.includes(req.user.accountType)) {
|
||||
return res
|
||||
.status(403)
|
||||
.json({ success: false, message: "Forbidden" });
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {authorizedAccountType, checkPermission};
|
||||
module.exports = { authorizedAccountType, requireAccountType: authorizedAccountType, checkPermission, requirePermission: checkPermission, requireOwnership };
|
||||
|
||||
Reference in New Issue
Block a user