apply security audit
This commit is contained in:
@@ -3,36 +3,6 @@ import tokenService from '../services/token.service';
|
||||
import userService from '../services/user.service';
|
||||
import logger from '../utils/logger';
|
||||
import { JwtPayload } from '../types/auth.types';
|
||||
import { auditPermissionDenied } from './audit.middleware';
|
||||
import { AuditResourceType } from '../services/audit.service';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Application roles — extend as needed when Authentik group mapping is added
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type AppRole = 'admin' | 'member' | 'viewer';
|
||||
|
||||
export const Permission = {
|
||||
ADMIN_ACCESS: 'admin:access',
|
||||
MEMBER_WRITE: 'member:write',
|
||||
MEMBER_READ: 'member:read',
|
||||
INCIDENT_WRITE:'incident:write',
|
||||
INCIDENT_READ: 'incident:read',
|
||||
EXPORT: 'export',
|
||||
} as const;
|
||||
|
||||
export type Permission = typeof Permission[keyof typeof Permission];
|
||||
|
||||
// Simple permission → required role mapping.
|
||||
// Adjust once Authentik group sync is implemented.
|
||||
const PERMISSION_ROLES: Record<Permission, AppRole[]> = {
|
||||
'admin:access': ['admin'],
|
||||
'member:write': ['admin', 'member'],
|
||||
'member:read': ['admin', 'member', 'viewer'],
|
||||
'incident:write': ['admin', 'member'],
|
||||
'incident:read': ['admin', 'member', 'viewer'],
|
||||
'export': ['admin'],
|
||||
};
|
||||
|
||||
// Extend Express Request type to include user
|
||||
declare global {
|
||||
@@ -42,7 +12,7 @@ declare global {
|
||||
id: string; // UUID
|
||||
email: string;
|
||||
authentikSub: string;
|
||||
role?: AppRole; // populated when role is stored in DB / JWT
|
||||
role?: string; // populated when role is stored in DB / JWT
|
||||
groups?: string[];
|
||||
};
|
||||
}
|
||||
@@ -122,6 +92,7 @@ export const authenticate = async (
|
||||
email: decoded.email,
|
||||
authentikSub: decoded.authentikSub,
|
||||
groups: decoded.groups ?? [],
|
||||
role: decoded.role,
|
||||
};
|
||||
|
||||
logger.debug('User authenticated successfully', {
|
||||
@@ -139,60 +110,6 @@ export const authenticate = async (
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Role-based access control middleware
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* requirePermission — factory that returns Express middleware enforcing a
|
||||
* specific permission. Must be placed after `authenticate` in the chain.
|
||||
*
|
||||
* Usage:
|
||||
* router.get('/admin/audit-log', authenticate, requirePermission('admin:access'), handler);
|
||||
*
|
||||
* When access is denied, a PERMISSION_DENIED audit entry is written before
|
||||
* the 403 response is sent.
|
||||
*
|
||||
* NOTE: Until Authentik group → role mapping is persisted to the users table
|
||||
* or JWT, this middleware checks req.user.role. Temporary workaround:
|
||||
* hard-code specific admin user IDs via the ADMIN_USER_IDS env variable, OR
|
||||
* add a `role` column to the users table (recommended).
|
||||
*/
|
||||
export const requirePermission = (permission: Permission) => {
|
||||
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||
if (!req.user) {
|
||||
res.status(401).json({ success: false, message: 'Not authenticated' });
|
||||
return;
|
||||
}
|
||||
|
||||
const userRole: AppRole = req.user.role ?? 'viewer';
|
||||
const allowedRoles = PERMISSION_ROLES[permission];
|
||||
|
||||
if (!allowedRoles.includes(userRole)) {
|
||||
logger.warn('Permission denied', {
|
||||
userId: req.user.id,
|
||||
permission,
|
||||
userRole,
|
||||
path: req.path,
|
||||
});
|
||||
|
||||
// Audit the denied access — fire-and-forget
|
||||
auditPermissionDenied(req, AuditResourceType.SYSTEM, undefined, {
|
||||
required_permission: permission,
|
||||
user_role: userRole,
|
||||
});
|
||||
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: 'Insufficient permissions',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Optional authentication middleware
|
||||
* Attaches user if token is valid, but doesn't require it
|
||||
|
||||
@@ -48,6 +48,19 @@ const PERMISSION_ROLE_MIN: Record<string, AppRole> = {
|
||||
'admin:access': 'admin',
|
||||
'audit:read': 'admin',
|
||||
'audit:export': 'admin',
|
||||
'members:read': 'mitglied',
|
||||
'members:write': 'kommandant',
|
||||
'vehicles:write': 'kommandant',
|
||||
'vehicles:status': 'gruppenfuehrer',
|
||||
'vehicles:delete': 'admin',
|
||||
'equipment:write': 'gruppenfuehrer',
|
||||
'equipment:delete': 'admin',
|
||||
'events:write': 'gruppenfuehrer',
|
||||
'events:categories': 'gruppenfuehrer',
|
||||
'atemschutz:write': 'gruppenfuehrer',
|
||||
'atemschutz:delete': 'kommandant',
|
||||
'bookings:write': 'gruppenfuehrer',
|
||||
'bookings:delete': 'admin',
|
||||
};
|
||||
|
||||
function hasPermission(role: AppRole, permission: string): boolean {
|
||||
@@ -103,7 +116,9 @@ export function requirePermission(permission: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const role = await getUserRole(req.user.id);
|
||||
const role = (req.user as any).role
|
||||
? (req.user as any).role as AppRole
|
||||
: await getUserRole(req.user.id);
|
||||
|
||||
// Attach role to request for downstream use (e.g., bericht_text redaction)
|
||||
(req as Request & { userRole?: AppRole }).userRole = role;
|
||||
@@ -149,7 +164,9 @@ export function requireGroups(requiredGroups: string[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userGroups: string[] = (req.user as any).groups ?? [];
|
||||
logger.warn('DEPRECATED: requireGroups() — migrate to requirePermission()', { requiredGroups });
|
||||
|
||||
const userGroups: string[] = req.user?.groups ?? [];
|
||||
const hasAccess = requiredGroups.some(g => userGroups.includes(g));
|
||||
|
||||
if (!hasAccess) {
|
||||
|
||||
Reference in New Issue
Block a user