import { Response } from 'express'; import prisma from '../lib/prisma.js'; import * as appSettingService from '../services/appSetting.service.js'; import { logChange } from '../services/audit.service.js'; import { ApiResponse, AuthRequest } from '../types/index.js'; export async function getAllSettings(req: AuthRequest, res: Response): Promise { try { const settings = await appSettingService.getAllSettings(); res.json({ success: true, data: settings } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden der Einstellungen', } as ApiResponse); } } export async function getPublicSettings(req: AuthRequest, res: Response): Promise { try { const settings = await appSettingService.getPublicSettings(); res.json({ success: true, data: settings } as ApiResponse); } catch (error) { res.status(500).json({ success: false, error: 'Fehler beim Laden der Einstellungen', } as ApiResponse); } } export async function updateSetting(req: AuthRequest, res: Response): Promise { try { const { key } = req.params; const { value } = req.body; if (value === undefined) { res.status(400).json({ success: false, error: 'Wert ist erforderlich', } as ApiResponse); return; } // Whitelist-Check (Pentest Runde 11, M1) if (!appSettingService.isAllowedSettingKey(key)) { res.status(400).json({ success: false, error: `Unbekannter Setting-Key: ${key}`, } as ApiResponse); return; } // Vorherigen Stand laden für Audit const before = await prisma.appSetting.findUnique({ where: { key } }); const oldValue = before?.value ?? '-'; // HTML-Tags aus Plain-Text-Keys strippen, bevor sie in der DB landen. // Pentest 2026-05-19, MEDIUM: companyName="" landete // sonst ungefiltert in E-Mail-Templates / PDFs. const stripped = appSettingService.sanitizeSettingValue(key, String(value)); // Schema-spezifische Validierung (URL/Email/Int/Bool). Pentest // 2026-05-28, LOW 34.5: portalLoginUrl nahm `/relative/path` und // `http://192.168.1.1` ungefiltert entgegen → Open-Redirect / // SSRF in der versendeten Mail. const validation = appSettingService.validateSettingValue(key, stripped); if (!validation.ok) { res.status(400).json({ success: false, error: validation.error } as ApiResponse); return; } const newValue = validation.value; await appSettingService.setSetting(key, newValue); const label = oldValue !== newValue ? `Einstellung "${key}" geändert: ${oldValue} → ${newValue}` : `Einstellung "${key}" geändert`; await logChange({ req, action: 'UPDATE', resourceType: 'AppSetting', resourceId: key, label, details: oldValue !== newValue ? { [key]: { von: oldValue, nach: newValue } } : undefined, }); res.json({ success: true, message: 'Einstellung gespeichert' } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Speichern der Einstellung', } as ApiResponse); } } export async function updateSettings(req: AuthRequest, res: Response): Promise { try { const settings = req.body; if (!settings || typeof settings !== 'object') { res.status(400).json({ success: false, error: 'Einstellungen sind erforderlich', } as ApiResponse); return; } // Whitelist-Check für jeden Key (Pentest Runde 11, M1: Mass Assignment) const unknownKeys = Object.keys(settings).filter( (k) => !appSettingService.isAllowedSettingKey(k), ); if (unknownKeys.length > 0) { res.status(400).json({ success: false, error: `Unbekannte Setting-Keys: ${unknownKeys.join(', ')}`, } as ApiResponse); return; } // Vorherige Werte laden für Audit. Validierung erfolgt vor dem // ersten Schreibzugriff, damit ein Bulk-PUT mit einem ungültigen // Wert nicht die anderen Werte halb-committed liegen lässt. const changes: Record = {}; const sanitizedEntries: Array<{ key: string; oldValue: string; newValue: string }> = []; for (const [key, value] of Object.entries(settings)) { const before = await prisma.appSetting.findUnique({ where: { key } }); const oldValue = before?.value ?? '-'; const stripped = appSettingService.sanitizeSettingValue(key, String(value)); const validation = appSettingService.validateSettingValue(key, stripped); if (!validation.ok) { res.status(400).json({ success: false, error: `${key}: ${validation.error}` } as ApiResponse); return; } sanitizedEntries.push({ key, oldValue, newValue: validation.value }); } for (const { key, oldValue, newValue } of sanitizedEntries) { if (oldValue !== newValue) { changes[key] = { von: oldValue, nach: newValue }; } await appSettingService.setSetting(key, newValue); } const changeList = Object.entries(changes).map(([k, c]) => `${k}: ${c.von} → ${c.nach}`).join(', '); await logChange({ req, action: 'UPDATE', resourceType: 'AppSetting', label: changeList ? `Einstellungen aktualisiert: ${changeList}` : `Einstellungen aktualisiert (${Object.keys(settings).join(', ')})`, details: Object.keys(changes).length > 0 ? changes : undefined, }); res.json({ success: true, message: 'Einstellungen gespeichert' } as ApiResponse); } catch (error) { res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler beim Speichern der Einstellungen', } as ApiResponse); } }