import { Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; import prisma from '../lib/prisma.js'; import { AuthRequest, JwtPayload } from '../types/index.js'; import { emit as emitSecurityEvent } from '../services/securityMonitor.service.js'; export async function authenticate( req: AuthRequest, res: Response, next: NextFunction ): Promise { const authHeader = req.headers.authorization; // Token aus Header oder Query-Parameter (für Downloads) let token: string | null = null; if (authHeader && authHeader.startsWith('Bearer ')) { token = authHeader.split(' ')[1]; } else if (req.query.token && typeof req.query.token === 'string') { // Fallback für Downloads: Token als Query-Parameter token = req.query.token; } if (!token) { res.status(401).json({ success: false, error: 'Nicht authentifiziert' }); return; } try { // JWT_SECRET wird beim Server-Start geprüft (Fail-Fast in index.ts) // Algorithmus explizit auf HS256 festlegen (Defense-in-Depth gegen alg-confusion). const decoded = jwt.verify(token, process.env.JWT_SECRET as string, { algorithms: ['HS256'], }) as JwtPayload; // Prüfen ob Token durch Rechteänderung/Passwort-Reset invalidiert wurde if (decoded.userId && decoded.iat) { // Mitarbeiter-Login const user = await prisma.user.findUnique({ where: { id: decoded.userId }, select: { tokenInvalidatedAt: true, isActive: true }, }); if (!user || !user.isActive) { res.status(401).json({ success: false, error: 'Benutzer nicht mehr aktiv' }); return; } if (user.tokenInvalidatedAt) { const tokenIssuedAt = decoded.iat * 1000; if (tokenIssuedAt < user.tokenInvalidatedAt.getTime()) { res.status(401).json({ success: false, error: 'Ihre Berechtigungen wurden geändert. Bitte melden Sie sich erneut an.', }); return; } } } else if (decoded.isCustomerPortal && decoded.customerId && decoded.iat) { // Portal-Kunden-Login: gleiche Prüfung const customer = await prisma.customer.findUnique({ where: { id: decoded.customerId }, select: { portalTokenInvalidatedAt: true, portalEnabled: true }, }); if (!customer || !customer.portalEnabled) { res.status(401).json({ success: false, error: 'Portal-Zugang nicht mehr aktiv' }); return; } if (customer.portalTokenInvalidatedAt) { const tokenIssuedAt = decoded.iat * 1000; if (tokenIssuedAt < customer.portalTokenInvalidatedAt.getTime()) { res.status(401).json({ success: false, error: 'Ihre Sitzung ist ungültig. Bitte melden Sie sich erneut an.', }); return; } } } req.user = decoded; next(); } catch (err) { // JWT-Failures sind interessant: alg=none, manipulierte Signature, // expired Token. Emit SecurityEvent (asynchron, blockt nicht). emitSecurityEvent({ type: 'TOKEN_REJECTED', severity: err instanceof jwt.TokenExpiredError ? 'LOW' : 'HIGH', message: err instanceof Error ? `JWT abgelehnt: ${err.message}` : 'JWT abgelehnt', ipAddress: req.ip || (req.socket as any)?.remoteAddress || 'unknown', endpoint: `${req.method} ${req.path}`, }); res.status(401).json({ success: false, error: 'Ungültiger Token' }); } } export function requirePermission(...requiredPermissions: string[]) { return (req: AuthRequest, res: Response, next: NextFunction): void => { if (!req.user) { res.status(401).json({ success: false, error: 'Nicht authentifiziert' }); return; } const userPermissions = req.user.permissions || []; // Check if user has any of the required permissions const hasPermission = requiredPermissions.some((perm) => userPermissions.includes(perm) ); if (!hasPermission) { res.status(403).json({ success: false, error: 'Keine Berechtigung für diese Aktion', }); return; } next(); }; } // Middleware to check if user can access specific customer data export function requireCustomerAccess( req: AuthRequest, res: Response, next: NextFunction ): void { if (!req.user) { res.status(401).json({ success: false, error: 'Nicht authentifiziert' }); return; } const userPermissions = req.user.permissions || []; // Admins and employees can access all customers if ( userPermissions.includes('customers:read') || userPermissions.includes('customers:update') ) { next(); return; } // Customers can only access their own data + represented customers const customerId = parseInt(req.params.customerId || req.params.id); const allowedIds = [ req.user.customerId, ...((req.user as any).representedCustomerIds || []), ].filter(Boolean); if (allowedIds.includes(customerId)) { next(); return; } res.status(403).json({ success: false, error: 'Kein Zugriff auf diese Kundendaten', }); }