mirror of
https://github.com/freedbygrace/ActiveDirectoryManager.git
synced 2026-08-12 03:38:23 +00:00
Update API documentation and UI to improve clarity and add role-based access control.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 705f2157-ef97-4fbd-89e4-8c7f2ecaea90 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7ed01c5f-a82d-405a-b728-b2e3d127c60c/994ee92d-6a6d-4ca4-99c0-fe0dbc7f160b.jpg
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user