add features

This commit is contained in:
Matthias Hochmeister
2026-02-27 19:47:20 +01:00
parent 44e22a9fc6
commit c5e8337a69
11 changed files with 1554 additions and 194 deletions

View File

@@ -3,15 +3,46 @@ 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 {
namespace Express {
interface Request {
user?: {
id: string; // UUID
email: string;
id: string; // UUID
email: string;
authentikSub: string;
role?: AppRole; // populated when role is stored in DB / JWT
};
}
}
@@ -106,6 +137,60 @@ 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