/** * Access-Control-Helper für Portal-Kunden-Isolation. * * Portal-Kunden haben die Permission `contracts:read` / `customers:read`, damit * sie ihre eigenen Daten sehen können. Damit sie aber NICHT fremde Daten über * geratene IDs abrufen (IDOR), muss bei jedem Endpoint der eine sensible * Ressource (Vertrag, Rechnung, Passwort, ...) zurückliefert, der Kunde auf * Besitz/Vollmacht geprüft werden. */ import { Response } from 'express'; import prisma from '../lib/prisma.js'; import * as authorizationService from '../services/authorization.service.js'; import { AuthRequest } from '../types/index.js'; /** * Prüft ob der authentifizierte User auf einen bestimmten Vertrag zugreifen darf. * - Mitarbeiter/Admin mit customers:read / contracts:read: ja, immer * - Portal-Kunde: nur wenn contract.customerId = eigener customerId ODER * wenn er einen Vertreter für diesen Kunden ist MIT gültiger Vollmacht * * @returns true = erlaubt, false = Zugriff verweigert (Response wurde bereits gesendet) */ export async function canAccessContract( req: AuthRequest, res: Response, contractId: number, ): Promise { // Nicht-Portal-User (Mitarbeiter/Admin) kommen hier immer durch, wenn sie die Permission haben if (!req.user?.isCustomerPortal) { return true; } if (!req.user.customerId) { res.status(403).json({ success: false, error: 'Kein Zugriff' }); return false; } // Vertrag laden, Besitzer-ID prüfen const contract = await prisma.contract.findUnique({ where: { id: contractId }, select: { customerId: true }, }); if (!contract) { res.status(404).json({ success: false, error: 'Vertrag nicht gefunden' }); return false; } // Eigene Verträge = immer erlaubt if (contract.customerId === req.user.customerId) { return true; } // Fremde Verträge nur mit aktiver Vollmacht const representedIds: number[] = (req.user as any).representedCustomerIds || []; if (!representedIds.includes(contract.customerId)) { res.status(403).json({ success: false, error: 'Kein Zugriff auf diesen Vertrag' }); return false; } const hasAuth = await authorizationService.hasAuthorization( contract.customerId, req.user.customerId, ); if (!hasAuth) { res.status(403).json({ success: false, error: 'Vollmacht erforderlich' }); return false; } return true; } /** * Prüft Zugriff auf einen Kunden (analog zu canAccessContract). */ export async function canAccessCustomer( req: AuthRequest, res: Response, customerId: number, ): Promise { if (!req.user?.isCustomerPortal) { return true; } if (!req.user.customerId) { res.status(403).json({ success: false, error: 'Kein Zugriff' }); return false; } if (customerId === req.user.customerId) { return true; } const representedIds: number[] = (req.user as any).representedCustomerIds || []; if (!representedIds.includes(customerId)) { res.status(403).json({ success: false, error: 'Kein Zugriff auf diese Kundendaten' }); return false; } const hasAuth = await authorizationService.hasAuthorization(customerId, req.user.customerId); if (!hasAuth) { res.status(403).json({ success: false, error: 'Vollmacht erforderlich' }); return false; } return true; } /** * Generische Zugriffsprüfung: Ressource → customerId → canAccessCustomer. */ async function canAccessResourceByCustomerId( req: AuthRequest, res: Response, customerId: number | null | undefined, resourceLabel: string, ): Promise { if (!req.user?.isCustomerPortal) return true; if (!customerId) { res.status(404).json({ success: false, error: `${resourceLabel} nicht gefunden` }); return false; } return canAccessCustomer(req, res, customerId); } /** * Zugriff auf eine Adresse prüfen (lädt sie aus der DB, prüft customerId). */ export async function canAccessAddress( req: AuthRequest, res: Response, addressId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const addr = await prisma.address.findUnique({ where: { id: addressId }, select: { customerId: true }, }); return canAccessResourceByCustomerId(req, res, addr?.customerId, 'Adresse'); } /** * Zugriff auf eine BankCard prüfen. */ export async function canAccessBankCard( req: AuthRequest, res: Response, bankCardId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const card = await prisma.bankCard.findUnique({ where: { id: bankCardId }, select: { customerId: true }, }); return canAccessResourceByCustomerId(req, res, card?.customerId, 'Bankkarte'); } /** * Zugriff auf ein IdentityDocument prüfen. */ export async function canAccessIdentityDocument( req: AuthRequest, res: Response, documentId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const doc = await prisma.identityDocument.findUnique({ where: { id: documentId }, select: { customerId: true }, }); return canAccessResourceByCustomerId(req, res, doc?.customerId, 'Ausweis'); } /** * Zugriff auf einen Meter prüfen. */ export async function canAccessMeter( req: AuthRequest, res: Response, meterId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const meter = await prisma.meter.findUnique({ where: { id: meterId }, select: { customerId: true }, }); return canAccessResourceByCustomerId(req, res, meter?.customerId, 'Zähler'); } /** * Zugriff auf eine StressfreiEmail prüfen. */ export async function canAccessStressfreiEmail( req: AuthRequest, res: Response, stressfreiEmailId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const sfe = await prisma.stressfreiEmail.findUnique({ where: { id: stressfreiEmailId }, select: { customerId: true }, }); return canAccessResourceByCustomerId(req, res, sfe?.customerId, 'E-Mail-Konto'); } /** * Zugriff auf eine CachedEmail prüfen (StressfreiEmail → customerId). */ export async function canAccessCachedEmail( req: AuthRequest, res: Response, emailId: number, ): Promise { if (!req.user?.isCustomerPortal) return true; const email = await prisma.cachedEmail.findUnique({ where: { id: emailId }, select: { stressfreiEmail: { select: { customerId: true } } }, }); return canAccessResourceByCustomerId( req, res, email?.stressfreiEmail?.customerId, 'E-Mail', ); }