mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-07-26 11:59:14 +00:00
afca6f811d
Replit-Restored-To: d77ce2adbe
194 lines
6.3 KiB
TypeScript
194 lines
6.3 KiB
TypeScript
import { Request, Response, NextFunction } from "express";
|
|
import { db } from "./db";
|
|
import { roles, rolePermissions, users, apiTokens } from "@shared/schema";
|
|
import { eq, or, and, inArray } from "drizzle-orm";
|
|
|
|
// Types for role-based access control
|
|
export type RequireAuthOptions = {
|
|
allowApiToken?: boolean;
|
|
};
|
|
|
|
export type RequirePermissionOptions = RequireAuthOptions & {
|
|
anyOf?: string[];
|
|
allOf?: string[];
|
|
};
|
|
|
|
// Default middleware that verifies a user is authenticated
|
|
export function requireAuth(options: RequireAuthOptions = {}) {
|
|
return async (req: Request, res: Response, next: NextFunction) => {
|
|
// Check for session authentication
|
|
if (req.isAuthenticated()) {
|
|
return next();
|
|
}
|
|
|
|
// Check for API token authentication if allowed
|
|
if (options.allowApiToken) {
|
|
const authHeader = req.headers.authorization;
|
|
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
const token = authHeader.substring(7);
|
|
|
|
try {
|
|
// Lookup the token
|
|
const [apiToken] = await db.select()
|
|
.from(apiTokens)
|
|
.where(eq(apiTokens.token, token))
|
|
.limit(1);
|
|
|
|
if (!apiToken) {
|
|
return res.status(401).json({ message: "Invalid API token" });
|
|
}
|
|
|
|
// Check if token has expired
|
|
if (apiToken.expiresAt && new Date(apiToken.expiresAt) < new Date()) {
|
|
return res.status(401).json({ message: "API token has expired" });
|
|
}
|
|
|
|
// Set token info on the request
|
|
req.user = {
|
|
id: apiToken.userId,
|
|
username: '',
|
|
password: '',
|
|
fullName: null,
|
|
email: null,
|
|
createdAt: null,
|
|
// Add tokenId for identifying which token was used
|
|
tokenId: apiToken.id,
|
|
// Add roleId for permission checking
|
|
roleId: apiToken.roleId,
|
|
// Store custom permissions if available
|
|
customPermissions: (Array.isArray(apiToken.customPermissions) ?
|
|
apiToken.customPermissions :
|
|
(apiToken.customPermissions ? JSON.parse(String(apiToken.customPermissions)) : [])) as string[]
|
|
};
|
|
|
|
return next();
|
|
} catch (error) {
|
|
console.error("API token authentication error:", error);
|
|
return res.status(500).json({ message: "Internal server error" });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not authenticated through any method
|
|
return res.status(401).json({ message: "Not authenticated" });
|
|
};
|
|
}
|
|
|
|
// Middleware for checking specific permissions
|
|
export function requirePermission(permission: string, options: RequirePermissionOptions = {}) {
|
|
return async (req: Request, res: Response, next: NextFunction) => {
|
|
// First ensure the user is authenticated
|
|
const authMiddleware = requireAuth(options);
|
|
|
|
authMiddleware(req, res, async () => {
|
|
try {
|
|
if (!req.user) {
|
|
return res.status(401).json({ message: "Not authenticated" });
|
|
}
|
|
|
|
// Get the permissions for the user's role
|
|
let hasPermission = false;
|
|
|
|
// Check for API token custom permissions first if present
|
|
if (req.user?.tokenId && Array.isArray(req.user?.customPermissions)) {
|
|
// For API tokens with custom permissions
|
|
const customPermissions = req.user.customPermissions;
|
|
if (customPermissions.includes(permission) ||
|
|
options.anyOf?.some(p => customPermissions.includes(p))) {
|
|
hasPermission = true;
|
|
}
|
|
}
|
|
|
|
// If no permission yet, check role-based permissions
|
|
if (!hasPermission && req.user.roleId) {
|
|
// Get permissions assigned to the role
|
|
const rolePerms = await db.select()
|
|
.from(rolePermissions)
|
|
.where(eq(rolePermissions.roleId, req.user.roleId));
|
|
|
|
const userPermissions = rolePerms.map(rp => rp.permission);
|
|
|
|
// Check for the specific permission or any of the optional permissions
|
|
if (userPermissions.includes(permission) ||
|
|
options.anyOf?.some(p => userPermissions.includes(p))) {
|
|
hasPermission = true;
|
|
}
|
|
|
|
// If allOf is specified, ensure all required permissions are present
|
|
if (options.allOf && options.allOf.length > 0) {
|
|
hasPermission = options.allOf.every(p => userPermissions.includes(p));
|
|
}
|
|
}
|
|
|
|
// Check if user has the admin:system permission which grants all access
|
|
if (!hasPermission && req.user.roleId) {
|
|
const adminPerm = await db.select()
|
|
.from(rolePermissions)
|
|
.where(and(
|
|
eq(rolePermissions.roleId, req.user.roleId),
|
|
eq(rolePermissions.permission, "admin:system")
|
|
))
|
|
.limit(1);
|
|
|
|
if (adminPerm.length > 0) {
|
|
hasPermission = true;
|
|
}
|
|
}
|
|
|
|
if (hasPermission) {
|
|
return next();
|
|
}
|
|
|
|
return res.status(403).json({ message: "Insufficient permissions" });
|
|
} catch (error) {
|
|
console.error("Permission check error:", error);
|
|
return res.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
// Convenience middleware for requiring administrative access
|
|
export function requireAdmin() {
|
|
return requirePermission("admin:system");
|
|
}
|
|
|
|
// Get permissions for a user
|
|
export async function getUserPermissions(userId: number): Promise<string[]> {
|
|
const [user] = await db.select()
|
|
.from(users)
|
|
.where(eq(users.id, userId))
|
|
.limit(1);
|
|
|
|
if (!user || !user.roleId) {
|
|
return [];
|
|
}
|
|
|
|
const rolePerms = await db.select()
|
|
.from(rolePermissions)
|
|
.where(eq(rolePermissions.roleId, user.roleId));
|
|
|
|
return rolePerms.map(rp => rp.permission);
|
|
}
|
|
|
|
// Initialize on server startup to ensure default roles
|
|
export async function initializeRBAC() {
|
|
try {
|
|
// Check for existing data before doing anything
|
|
const [adminRole] = await db.select()
|
|
.from(roles)
|
|
.where(eq(roles.name, "admin"))
|
|
.limit(1);
|
|
|
|
if (adminRole) {
|
|
console.log("RBAC already initialized");
|
|
return true;
|
|
}
|
|
|
|
console.log("RBAC initialization skipped - should be handled by updateSchema.ts");
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Failed to initialize RBAC:", error);
|
|
return false;
|
|
}
|
|
} |