Files
ActiveDirectoryManager/server/authorization.ts
T

199 lines
6.6 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) => {
try {
// Check for session authentication - just check if req.user exists
if (req.user) {
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" });
} catch (error) {
console.error("Authentication error:", error);
return res.status(500).json({ message: "Internal server error during authentication" });
}
};
}
// 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;
}
}