Aufraeumrunde vor Etappe 2: Fehlerhygiene, Admin-Kriterium, Portal-Riegel
Drei Nachrangpunkte aus dem Pentest, vor dem Schneiden des Rechtekatalogs.
1. R192-01 projektweit. Das Muster `error instanceof Error ? error.message`
stand 124-mal in 26 Controllern und konnte ueberall Serverpfade,
Spaltennamen und Bibliotheksinterna ausliefern. Zentral geloest statt
124-mal einzeln - das waere die Falle aus R186-01 und R188 gewesen:
utils/fehlerAntwort.ts mit antworteAufFehler(), jetzt 123 Aufrufe in 26
Dateien und eine Regel.
Die Unterscheidung laeuft ueber die Fehlerklasse. Neue Basisklasse
FachlicherFehler fuer alles, dessen Wortlaut fuer den Aufrufer bestimmt
ist; ApiError, RechteEskalationError, RollenSperrError,
UngueltigeEingabeError, FilterFehler und ReferralError stammen davon ab.
Ein blankes Error gilt weiter als absichtlich. Alles andere - TypeError,
Prisma, JWT-Bibliothek - wird 500 mit allgemeiner Auskunft, Einzelheiten
ins Protokoll.
Mit gefunden: Sechs Stellen in cachedEmail.controller interpolierten die
interne Meldung in den Antworttext; das haette kein Filter erwischt, der
nur das Feld ersetzt. Und POST /auth/refresh gab den Wortlaut der
JWT-Bibliothek zurueck ("jwt malformed", "invalid signature") - der sagt
einem Angreifer, woran sein Token gescheitert ist.
2. Admin-Heuristik. Bisher galt "wer users:delete hat, ist Admin" - ein
Zufallsmerkmal. Bewusst NICHT auf den Rollennamen umgestellt, wie
vorgeschlagen: Eine selbst gebaute Rolle mit users:update verwaltet
tatsaechlich, ein Namenskriterium wuerde sie uebersehen, und dann liesse
sich der letzte Admin loeschen, obwohl die Faehigkeit erhalten bliebe.
Geschuetzt wird jetzt die Faehigkeit selbst: users:update und
roles:manage. Wer der letzte Traeger ist, kann sie nicht verlieren - durch
Rollenwechsel, Deaktivierung oder Loeschung. Die Meldung nennt die
Faehigkeit beim Namen. Nebenbei der letzte Cost-10-Rest im
Kennwort-Zuruecksetzen.
3. Portal-Kunden. Korrektur meiner eigenen Einordnung: Das war kein
Migrationsrueckstand, sondern eine gewollte Trennung. Kunden bekommen
niemals operative Rechte; die Portalansicht ist dafuer nicht gebaut und
prueft es nicht. Die zwei Kopien des festen Arrays sind jetzt eine
Konstante PORTAL_RECHTE mit einem Kommentar, der die Absicht benennt.
Dazu ein harter Riegel im Gate: requirePermission schneidet die Rechte
eines Portal-Zugangs auf PORTAL_RECHTE zu, unabhaengig davon, was sein
Token behauptet. Heute wirkungslos, morgen die Sicherung - bisher haette
eine unbedachte Zeile in der Token-Erzeugung gereicht. Und eine Startwache,
die jedes Nicht-Lese-Recht in dieser Liste meldet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -291,3 +291,53 @@ export function istSystemrollenName(name: string): boolean {
|
||||
const normalisiert = name.trim().toLocaleLowerCase('de-DE');
|
||||
return SYSTEMROLLEN_NAMEN.some((n) => n.toLocaleLowerCase('de-DE') === normalisiert);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Kundenportal - bewusst NICHT Teil des Rollensystems
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Die Rechte eines angemeldeten Portal-Kunden. Fest, kurz, abschliessend.
|
||||
*
|
||||
* DIESE TRENNUNG IST ABSICHT UND SOLL BLEIBEN.
|
||||
*
|
||||
* Kunden im Portal ziehen ihre Rechte NICHT aus Rollen, sondern aus dieser
|
||||
* Liste. Das ist kein Altbestand, den man noch migrieren muesste - es ist
|
||||
* die Sicherung dagegen, dass eine Aenderung am Rollenmodell versehentlich
|
||||
* auf die Kundenseite durchschlaegt. Ein Kunde, der ueber eine Rolle
|
||||
* ploetzlich `contracts:create` haelt, koennte in fremdem Namen Vertraege
|
||||
* anlegen; die Portalansicht ist fuer so etwas nicht gebaut und pruefte es
|
||||
* nicht.
|
||||
*
|
||||
* Wer hier etwas hinzufuegen will, aendert nicht eine Liste, sondern eine
|
||||
* Vertrauensgrenze. `pruefePortalRechte()` weist beim Start alles ab, was
|
||||
* nicht Lesen ist - absichtlich als Alarm und nicht als stiller Default.
|
||||
*
|
||||
* Die Durchsetzung liegt zusaetzlich im Gate (`requirePermission`): Ein
|
||||
* Portal-Zugang bekommt dort nur, was auch hier steht, unabhaengig davon,
|
||||
* was in seinem Token behauptet wird.
|
||||
*/
|
||||
export const PORTAL_RECHTE: readonly string[] = [
|
||||
'contracts:read', // eigene Vertraege lesen
|
||||
'customers:read', // eigene Kundendaten lesen
|
||||
] as const;
|
||||
|
||||
const PORTAL_ERLAUBTE_AKTIONEN = ['read'];
|
||||
|
||||
/**
|
||||
* Wacht darueber, dass die Portal-Liste eine Leseliste bleibt.
|
||||
*
|
||||
* Wird beim Start aufgerufen. Ein schreibendes Recht hier waere kein
|
||||
* Schoenheitsfehler, sondern eine stille Ausweitung der Kundenrechte - und
|
||||
* genau die soll nicht durch eine unbedachte Zeile passieren koennen.
|
||||
*/
|
||||
export function pruefePortalRechte(): string[] {
|
||||
const beanstandet: string[] = [];
|
||||
for (const recht of PORTAL_RECHTE) {
|
||||
const [, aktion] = recht.split(':');
|
||||
if (!PORTAL_ERLAUBTE_AKTIONEN.includes(aktion)) {
|
||||
beanstandet.push(recht);
|
||||
}
|
||||
}
|
||||
return beanstandet;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as appSettingService from '../services/appSetting.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
@@ -81,10 +82,7 @@ export async function updateSetting(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
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);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern der Einstellung');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,9 +144,6 @@ export async function updateSettings(req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
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);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern der Einstellungen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { FachlicherFehler } from '../utils/apiError.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as auditService from '../services/audit.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
@@ -18,7 +20,11 @@ import { emit as emitSecurityEvent, contextFromRequest } from '../services/secur
|
||||
* sprengt die Query erst in der Datenbank) und fuer Zahlen (`parseInt('x')`
|
||||
* ergibt NaN).
|
||||
*/
|
||||
class FilterFehler extends Error {}
|
||||
class FilterFehler extends FachlicherFehler {
|
||||
constructor(nachricht: string) {
|
||||
super(nachricht, 400);
|
||||
}
|
||||
}
|
||||
|
||||
const AUDIT_ACTIONS = Object.values(AuditAction) as string[];
|
||||
const AUDIT_SENSITIVITIES = Object.values(AuditSensitivity) as string[];
|
||||
@@ -537,10 +543,7 @@ export async function sealBacklog(req: AuthRequest, res: Response) {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Versiegeln des Altbestands:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Versiegeln',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Versiegeln');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response, CookieOptions } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as authService from '../services/auth.service.js';
|
||||
import { AuthRequest, ApiResponse } from '../types/index.js';
|
||||
import prisma from '../lib/prisma.js';
|
||||
@@ -281,10 +282,7 @@ export async function confirmPasswordReset(req: Request, res: Response): Promise
|
||||
ipAddress: ctx.ipAddress,
|
||||
endpoint: ctx.endpoint,
|
||||
});
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Passwort-Reset fehlgeschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Passwort-Reset fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,10 +398,12 @@ export async function refresh(req: Request, res: Response): Promise<void> {
|
||||
endpoint: ctx.endpoint,
|
||||
});
|
||||
}
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: msg,
|
||||
} as ApiResponse);
|
||||
// `msg` geht weiterhin in den Alarmkanal (oben), aber nicht mehr an den
|
||||
// Aufrufer: Bei einem abgelehnten Refresh stammt der Wortlaut aus der
|
||||
// JWT-Bibliothek ("jwt malformed", "invalid signature") und sagt einem
|
||||
// Angreifer, WORAN sein Token gescheitert ist. Fuer den berechtigten
|
||||
// Nutzer aendert das nichts - er muss sich so oder so neu anmelden.
|
||||
antworteAufFehler(res, error, 'Refresh fehlgeschlagen', 401);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,9 +525,6 @@ export async function changeInitialPortalPassword(req: AuthRequest, res: Respons
|
||||
clearRefreshCookie(res);
|
||||
res.json({ success: true, message: 'Passwort geändert' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Passwort konnte nicht geändert werden',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Passwort konnte nicht geändert werden', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { isValidIBAN } from 'ibantools';
|
||||
import * as blzData from '../services/blzData.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
@@ -47,9 +48,6 @@ export async function lookupIban(req: AuthRequest, res: Response): Promise<void>
|
||||
},
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim IBAN-Nachschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim IBAN-Nachschlagen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as birthdayService from '../services/birthday.service.js';
|
||||
import { sendEmail, SmtpCredentials } from '../services/smtpService.js';
|
||||
@@ -89,10 +90,7 @@ export async function resetBirthdayGreeting(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Zurücksetzen:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Zurücksetzen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +178,6 @@ export async function sendBirthdayGreeting(req: AuthRequest, res: Response) {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden des Geburtstagsgrußes:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Senden',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Senden', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import * as blzData from '../services/blzData.service.js';
|
||||
@@ -9,10 +10,7 @@ export async function getStatus(req: AuthRequest, res: Response): Promise<void>
|
||||
const status = await blzData.getStatus();
|
||||
res.json({ success: true, data: status } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Laden des BLZ-Status',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Laden des BLZ-Status', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +30,6 @@ export async function updateNow(req: AuthRequest, res: Response): Promise<void>
|
||||
const status = await blzData.getStatus();
|
||||
res.json({ success: true, data: { result, status } } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(502).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'BLZ-Aktualisierung fehlgeschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'BLZ-Aktualisierung fehlgeschlagen', 502);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ==================== CACHED EMAIL CONTROLLER ====================
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as cachedEmailService from '../services/cachedEmail.service.js';
|
||||
import * as stressfreiEmailService from '../services/stressfreiEmail.service.js';
|
||||
import * as invoiceService from '../services/invoice.service.js';
|
||||
@@ -272,10 +273,7 @@ export async function assignToContract(req: AuthRequest, res: Response): Promise
|
||||
res.json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('assignToContract error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Zuordnen der E-Mail',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Zuordnen der E-Mail');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,10 +288,7 @@ export async function unassignFromContract(req: AuthRequest, res: Response): Pro
|
||||
res.json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('unassignFromContract error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aufheben der Zuordnung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aufheben der Zuordnung');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,12 +1655,7 @@ export async function saveAttachmentTo(req: AuthRequest, res: Response): Promise
|
||||
} catch (error) {
|
||||
console.error('saveAttachmentTo error:', error);
|
||||
// Detailliertere Fehlermeldung für Debugging
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: `Fehler beim Speichern des Anhangs: ${errorMessage}`,
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern des Anhangs', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1909,11 +1899,7 @@ export async function saveEmailAsPdf(req: AuthRequest, res: Response): Promise<v
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('saveEmailAsPdf error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: `Fehler beim Erstellen der PDF: ${errorMessage}`,
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der PDF', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2036,11 +2022,7 @@ export async function saveEmailAsInvoice(req: AuthRequest, res: Response): Promi
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('saveEmailAsInvoice error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: `Fehler beim Erstellen der Rechnung: ${errorMessage}`,
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Rechnung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2145,9 +2127,7 @@ export async function saveEmailAsContractDocument(req: AuthRequest, res: Respons
|
||||
console.error('saveEmailAsContractDocument error:', error);
|
||||
// Pentest 64.1: ApiError mit eigenem statusCode (z.B. 400 vom Race-
|
||||
// Lock) statt pauschal 500.
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(status).json({ success: false, error: `Fehler beim Speichern: ${errorMessage}` } as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2332,12 +2312,7 @@ export async function saveAttachmentAsInvoice(req: AuthRequest, res: Response):
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('saveAttachmentAsInvoice error:', error);
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: `Fehler beim Erstellen der Rechnung: ${errorMessage}`,
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Rechnung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2512,11 +2487,6 @@ export async function saveAttachmentAsContractDocument(req: AuthRequest, res: Re
|
||||
} catch (error) {
|
||||
console.error('saveAttachmentAsContractDocument error:', error);
|
||||
// Pentest 64.1: ApiError mit eigenem statusCode statt pauschal 500.
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: `Fehler beim Speichern: ${errorMessage}`,
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as cancellationPeriodService from '../services/cancellation-period.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -46,10 +47,7 @@ export async function createCancellationPeriod(req: Request, res: Response): Pro
|
||||
});
|
||||
res.status(201).json({ success: true, data: period } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Kündigungsfrist',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Kündigungsfrist');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,10 +62,7 @@ export async function updateCancellationPeriod(req: Request, res: Response): Pro
|
||||
});
|
||||
res.json({ success: true, data: period } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Kündigungsfrist',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Kündigungsfrist');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +78,6 @@ export async function deleteCancellationPeriod(req: Request, res: Response): Pro
|
||||
});
|
||||
res.json({ success: true, message: 'Kündigungsfrist gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Kündigungsfrist',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Kündigungsfrist');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as contractDurationService from '../services/contract-duration.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -46,10 +47,7 @@ export async function createContractDuration(req: Request, res: Response): Promi
|
||||
});
|
||||
res.status(201).json({ success: true, data: duration } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Laufzeit',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Laufzeit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,10 +62,7 @@ export async function updateContractDuration(req: Request, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, data: duration } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Laufzeit',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Laufzeit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +78,6 @@ export async function deleteContractDuration(req: Request, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, message: 'Laufzeit gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Laufzeit',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Laufzeit');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import fs from 'fs';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as contractService from '../services/contract.service.js';
|
||||
@@ -211,10 +212,7 @@ export async function createContract(req: AuthRequest, res: Response): Promise<v
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Vertrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Vertrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,10 +372,7 @@ export async function updateContract(req: AuthRequest, res: Response): Promise<v
|
||||
: sanitizeContract(contract as any);
|
||||
res.json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Vertrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Vertrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,10 +391,7 @@ export async function deleteContract(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, message: 'Vertrag gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Vertrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Vertrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,10 +439,7 @@ export async function createFollowUp(req: AuthRequest, res: Response): Promise<v
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Folgevertrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Folgevertrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,10 +491,7 @@ export async function createRenewal(req: AuthRequest, res: Response): Promise<vo
|
||||
const sanitized = isPortal ? sanitizeContractStrict(contract as any) : sanitizeContract(contract as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der VVL',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der VVL');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,10 +779,7 @@ export async function addSuccessorMeter(req: AuthRequest, res: Response): Promis
|
||||
|
||||
res.json({ success: true, data: contractMeter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Folgezählers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hinzufügen des Folgezählers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,10 +796,7 @@ export async function removeContractMeter(req: AuthRequest, res: Response): Prom
|
||||
});
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Entfernen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Entfernen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,13 +885,9 @@ export async function uploadContractDocument(req: AuthRequest, res: Response): P
|
||||
// Pentest 64.1: ApiError mit eigenem statusCode honorieren (z.B. 400
|
||||
// vom Race-Lock); fallback bleibt 400 für sonstige ContractDocument-
|
||||
// Schreibfehler.
|
||||
const status = error instanceof ApiError ? error.statusCode : 400;
|
||||
// Multer hat die Datei schon geschrieben – bei Reject räumen.
|
||||
if (req.file?.path) try { fs.unlinkSync(req.file.path); } catch { /* ignore */ }
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hochladen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hochladen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,10 +924,7 @@ export async function deleteContractDocument(req: AuthRequest, res: Response): P
|
||||
|
||||
res.json({ success: true, message: 'Dokument gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as contractCategoryService from '../services/contractCategory.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -46,10 +47,7 @@ export async function createContractCategory(req: Request, res: Response): Promi
|
||||
});
|
||||
res.status(201).json({ success: true, data: category } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Vertragskategorie',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Vertragskategorie');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,10 +62,7 @@ export async function updateContractCategory(req: Request, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, data: category } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Vertragskategorie',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Vertragskategorie');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +78,6 @@ export async function deleteContractCategory(req: Request, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, message: 'Vertragskategorie gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Vertragskategorie',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Vertragskategorie');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as contractHistoryService from '../services/contractHistory.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
@@ -47,10 +48,7 @@ export async function createHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
|
||||
res.status(201).json({ success: true, data: entry } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Eintrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Eintrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +72,7 @@ export async function updateHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
|
||||
res.json({ success: true, data: entry } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Eintrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Eintrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +92,6 @@ export async function deleteHistoryEntry(req: AuthRequest, res: Response): Promi
|
||||
|
||||
res.json({ success: true, message: 'Eintrag gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Eintrags',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Eintrags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as contractTaskService from '../services/contractTask.service.js';
|
||||
import * as contractService from '../services/contract.service.js';
|
||||
import * as customerService from '../services/customer.service.js';
|
||||
@@ -146,10 +147,7 @@ export async function createGeneralTask(req: AuthRequest, res: Response): Promis
|
||||
});
|
||||
res.status(201).json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,10 +185,7 @@ export async function createTask(req: AuthRequest, res: Response): Promise<void>
|
||||
|
||||
res.status(201).json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,10 +234,7 @@ export async function createSupportTicket(req: AuthRequest, res: Response): Prom
|
||||
|
||||
res.status(201).json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Support-Anfrage',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Support-Anfrage');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,10 +257,7 @@ export async function updateTask(req: AuthRequest, res: Response): Promise<void>
|
||||
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,10 +272,7 @@ export async function completeTask(req: AuthRequest, res: Response): Promise<voi
|
||||
});
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Abschließen der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Abschließen der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,10 +287,7 @@ export async function reopenTask(req: AuthRequest, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, data: task } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Wiedereröffnen der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Wiedereröffnen der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,10 +302,7 @@ export async function deleteTask(req: AuthRequest, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, message: 'Aufgabe gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Aufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Aufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,10 +337,7 @@ export async function createSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
|
||||
res.status(201).json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Unteraufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Unteraufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,10 +416,7 @@ export async function createCustomerReply(req: AuthRequest, res: Response): Prom
|
||||
|
||||
res.status(201).json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Antwort',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Antwort');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,10 +441,7 @@ export async function updateSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Unteraufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Unteraufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,10 +456,7 @@ export async function completeSubtask(req: AuthRequest, res: Response): Promise<
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Abschließen der Unteraufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Abschließen der Unteraufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,10 +471,7 @@ export async function reopenSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
res.json({ success: true, data: subtask } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Wiedereröffnen der Unteraufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Wiedereröffnen der Unteraufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,9 +486,6 @@ export async function deleteSubtask(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
res.json({ success: true, message: 'Unteraufgabe gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Unteraufgabe',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Unteraufgabe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiError } from '../utils/apiError.js';
|
||||
@@ -36,11 +37,9 @@ function idParam(req: AuthRequest, res: Response, name: string): number | null {
|
||||
}
|
||||
|
||||
function handleError(res: Response, error: unknown, fallback: string) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : fallback,
|
||||
} as ApiResponse);
|
||||
// Eigene Huelle beibehalten, damit die Aufrufer unveraendert bleiben - die
|
||||
// Entscheidung, was nach draussen geht, faellt jetzt zentral.
|
||||
antworteAufFehler(res, error, fallback, 500);
|
||||
}
|
||||
|
||||
// ---- Gesamtübersicht (Hauptmenü) ----
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as customerService from '../services/customer.service.js';
|
||||
import * as authService from '../services/auth.service.js';
|
||||
@@ -155,10 +156,7 @@ export async function createCustomer(req: Request, res: Response): Promise<void>
|
||||
: sanitizeCustomerStrict(customer as any);
|
||||
res.status(201).json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Kunden',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Kunden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,10 +274,7 @@ export async function updateCustomer(req: Request, res: Response): Promise<void>
|
||||
res.json({ success: true, data: sanitized } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('Update customer error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Kunden',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Kunden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,10 +291,7 @@ export async function deleteCustomer(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, message: 'Kunde gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Kunden',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Kunden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,10 +320,7 @@ export async function createAddress(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
res.status(201).json({ success: true, data: address } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Adresse');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,10 +381,7 @@ export async function updateAddress(req: AuthRequest, res: Response): Promise<vo
|
||||
|
||||
res.json({ success: true, data: address } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Adresse');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,10 +400,7 @@ export async function deleteAddress(req: AuthRequest, res: Response): Promise<vo
|
||||
});
|
||||
res.json({ success: true, message: 'Adresse gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Adresse');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,10 +431,7 @@ export async function createBankCard(req: AuthRequest, res: Response): Promise<v
|
||||
});
|
||||
res.status(201).json({ success: true, data: card } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Bankkarte',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Bankkarte');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,10 +490,7 @@ export async function updateBankCard(req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
res.json({ success: true, data: card } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Bankkarte',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Bankkarte');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,10 +509,7 @@ export async function deleteBankCard(req: AuthRequest, res: Response): Promise<v
|
||||
});
|
||||
res.json({ success: true, message: 'Bankkarte gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Bankkarte',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Bankkarte');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,10 +539,7 @@ export async function createDocument(req: AuthRequest, res: Response): Promise<v
|
||||
});
|
||||
res.status(201).json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Ausweises',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Ausweises');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,10 +601,7 @@ export async function updateDocument(req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
res.json({ success: true, data: doc } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Ausweises',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Ausweises');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,10 +620,7 @@ export async function deleteDocument(req: AuthRequest, res: Response): Promise<v
|
||||
});
|
||||
res.json({ success: true, message: 'Ausweis gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Ausweises',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Ausweises');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,10 +653,7 @@ export async function createMeter(req: AuthRequest, res: Response): Promise<void
|
||||
});
|
||||
res.status(201).json({ success: true, data: meter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Zählers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Zählers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,10 +708,7 @@ export async function updateMeter(req: AuthRequest, res: Response): Promise<void
|
||||
|
||||
res.json({ success: true, data: meter } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Zählers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,10 +724,7 @@ export async function deleteMeter(req: AuthRequest, res: Response): Promise<void
|
||||
});
|
||||
res.json({ success: true, message: 'Zähler gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Zählers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,10 +770,7 @@ export async function addMeterReading(req: AuthRequest, res: Response): Promise<
|
||||
|
||||
res.status(201).json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Zählerstands',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hinzufügen des Zählerstands');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,10 +798,7 @@ export async function updateMeterReading(req: AuthRequest, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählerstands',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Zählerstands');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,10 +815,7 @@ export async function deleteMeterReading(req: AuthRequest, res: Response): Promi
|
||||
});
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählerstands',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Zählerstands');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -923,10 +870,7 @@ export async function reportMeterReading(req: AuthRequest, res: Response): Promi
|
||||
|
||||
res.status(201).json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Melden des Zählerstands',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Melden des Zählerstands');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -978,10 +922,7 @@ export async function markReadingTransferred(req: AuthRequest, res: Response): P
|
||||
|
||||
res.json({ success: true, data: reading } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1091,10 +1032,7 @@ export async function updatePortalSettings(req: Request, res: Response): Promise
|
||||
|
||||
res.json({ success: true, data: settings } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Portal-Einstellungen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Portal-Einstellungen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,10 +1046,7 @@ export async function generatePortalPassword(req: Request, res: Response): Promi
|
||||
const password = generateSecurePassword({ length: 16 });
|
||||
res.json({ success: true, data: { password } } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Generieren des Passworts',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Generieren des Passworts', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,10 +1134,7 @@ export async function sendPortalCredentials(req: AuthRequest, res: Response): Pr
|
||||
|
||||
res.json({ success: true, message: `Zugangsdaten an ${targetEmail} versendet (Einmalpasswort)` } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Versenden der Zugangsdaten',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Versenden der Zugangsdaten', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1228,10 +1160,7 @@ export async function setPortalPassword(req: Request, res: Response): Promise<vo
|
||||
});
|
||||
res.json({ success: true, message: 'Passwort gesetzt' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Setzen des Passworts',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Setzen des Passworts');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,10 +1233,7 @@ export async function addRepresentative(req: AuthRequest, res: Response): Promis
|
||||
});
|
||||
res.status(201).json({ success: true, data: representative } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Vertreters',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hinzufügen des Vertreters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1326,10 +1252,7 @@ export async function removeRepresentative(req: AuthRequest, res: Response): Pro
|
||||
});
|
||||
res.json({ success: true, message: 'Vertreter entfernt' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Entfernen des Vertreters',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Entfernen des Vertreters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,11 +1277,7 @@ export async function getSalutationPreference(req: AuthRequest, res: Response):
|
||||
} catch (error) {
|
||||
// Pentest R104.1: ApiError-Statuscodes durchreichen (404 statt 500
|
||||
// bei nicht-existierendem Kunden).
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Laden der Anrede-Präferenz',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Laden der Anrede-Präferenz', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1387,11 +1306,7 @@ export async function setSalutationPreference(req: AuthRequest, res: Response):
|
||||
const result = await customerService.getSalutationPreference(userId, customerId);
|
||||
res.json({ success: true, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Speichern der Anrede-Präferenz',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Speichern der Anrede-Präferenz', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1412,11 +1327,7 @@ export async function clearSalutationPreference(req: AuthRequest, res: Response)
|
||||
const result = await customerService.getSalutationPreference(userId, customerId);
|
||||
res.json({ success: true, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen der Anrede-Präferenz',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Zurücksetzen der Anrede-Präferenz', 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import * as referralService from '../services/customerReferral.service.js';
|
||||
@@ -98,11 +99,7 @@ export async function createReferral(req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
res.status(201).json({ success: true, data: created } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = (error as referralService.ReferralError)?.status ?? 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Anlegen der Werbe-Beziehung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Anlegen der Werbe-Beziehung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +150,7 @@ export async function updateReferral(req: AuthRequest, res: Response): Promise<v
|
||||
|
||||
res.json({ success: true, data: updated } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = (error as referralService.ReferralError)?.status ?? 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Ändern der Werbe-Beziehung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Ändern der Werbe-Beziehung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// ==================== EMAIL PROVIDER CONTROLLER ====================
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as emailProviderService from '../services/emailProvider/emailProviderService.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -60,10 +61,7 @@ export async function createProviderConfig(req: Request, res: Response): Promise
|
||||
});
|
||||
res.status(201).json({ success: true, data: config } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Email-Providers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Email-Providers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +78,7 @@ export async function updateProviderConfig(req: Request, res: Response): Promise
|
||||
});
|
||||
res.json({ success: true, data: config } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Email-Providers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Email-Providers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,10 +94,7 @@ export async function deleteProviderConfig(req: Request, res: Response): Promise
|
||||
});
|
||||
res.json({ success: true, message: 'Email-Provider gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Email-Providers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Email-Providers');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,10 +150,7 @@ export async function testConnection(req: Request, res: Response): Promise<void>
|
||||
const result = await emailProviderService.testProviderConnection({ id, testData });
|
||||
res.json({ success: result.success, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Verbindungstest fehlgeschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Verbindungstest fehlgeschlagen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,10 +331,7 @@ export async function testMailAccess(req: Request, res: Response): Promise<void>
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('testMailAccess error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Test',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Test', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,10 +341,7 @@ export async function checkEmailExists(req: Request, res: Response): Promise<voi
|
||||
const result = await emailProviderService.checkEmailExists(localPart);
|
||||
res.json({ success: true, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler bei der E-Mail-Prüfung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler bei der E-Mail-Prüfung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,10 +360,7 @@ export async function provisionEmail(req: Request, res: Response): Promise<void>
|
||||
const result = await emailProviderService.provisionEmail(localPart, customerEmail);
|
||||
res.json({ success: result.success, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler bei der E-Mail-Provisionierung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler bei der E-Mail-Provisionierung', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,10 +370,7 @@ export async function deprovisionEmail(req: Request, res: Response): Promise<voi
|
||||
const result = await emailProviderService.deprovisionEmail(localPart);
|
||||
res.json({ success: result.success, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der E-Mail',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der E-Mail', 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as factoryDefaultsService from '../services/factoryDefaults.service.js';
|
||||
import { createAuditLog } from '../services/audit.service.js';
|
||||
@@ -31,10 +32,7 @@ export async function exportFactoryDefaults(req: AuthRequest, res: Response) {
|
||||
res.send(buffer);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Factory-Defaults-Export:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Export',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Export', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,9 +91,6 @@ export async function importFactoryDefaults(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Factory-Defaults-Import:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Import',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Import');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as gdprService from '../services/gdpr.service.js';
|
||||
import * as consentService from '../services/consent.service.js';
|
||||
@@ -54,10 +55,7 @@ export async function exportCustomerData(req: AuthRequest, res: Response) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Datenexport:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Datenexport',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Datenexport', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +96,7 @@ export async function createDeletionRequest(req: AuthRequest, res: Response) {
|
||||
res.status(201).json({ success: true, data: request });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Löschanfrage:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,10 +178,7 @@ export async function processDeletionRequest(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Bearbeiten der Löschanfrage:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Bearbeiten',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Bearbeiten');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,10 +338,7 @@ export async function updateCustomerConsent(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: consent });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Einwilligung:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,10 +686,7 @@ export async function sendConsentLink(req: AuthRequest, res: Response) {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden des Consent-Links:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Senden',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Senden', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,10 +805,7 @@ export async function sendAuthorizationRequest(req: AuthRequest, res: Response)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Senden der Vollmacht-Anfrage:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Senden',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Senden', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,10 +858,7 @@ export async function grantAuthorization(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: auth });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erteilen der Vollmacht:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erteilen der Vollmacht',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Erteilen der Vollmacht');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,10 +884,7 @@ export async function withdrawAuthorization(req: AuthRequest, res: Response) {
|
||||
res.json({ success: true, data: auth });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Widerrufen der Vollmacht:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Widerrufen',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Widerrufen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,10 +989,7 @@ export async function uploadAuthorizationDocument(req: AuthRequest, res: Respons
|
||||
res.json({ success: true, data: auth });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Upload des Vollmacht-Dokuments:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Upload',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Upload');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1039,10 +1013,7 @@ export async function deleteAuthorizationDocument(req: AuthRequest, res: Respons
|
||||
res.json({ success: true, data: auth });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Vollmacht-Dokuments:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as invoiceService from '../services/invoice.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
@@ -101,10 +102,7 @@ export async function addInvoice(req: AuthRequest, res: Response): Promise<void>
|
||||
res.status(201).json({ success: true, data: invoice } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('addInvoice error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen der Rechnung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hinzufügen der Rechnung');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +134,7 @@ export async function updateInvoice(req: AuthRequest, res: Response): Promise<vo
|
||||
res.json({ success: true, data: invoice } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('updateInvoice error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Rechnung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Rechnung');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,10 +158,7 @@ export async function deleteInvoice(req: AuthRequest, res: Response): Promise<vo
|
||||
res.json({ success: true, data: null } as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('deleteInvoice error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Rechnung',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Rechnung');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +200,6 @@ export async function addInvoiceByContract(req: AuthRequest, res: Response): Pro
|
||||
});
|
||||
res.status(201).json({ success: true, data: invoice } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Hinzufügen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { AuthRequest, ApiResponse } from '../types/index.js';
|
||||
import * as appSettingService from '../services/appSetting.service.js';
|
||||
@@ -141,10 +142,7 @@ export async function testAlert(_req: AuthRequest, res: Response): Promise<void>
|
||||
res.status(500).json({ success: false, error: result.error || 'Versand fehlgeschlagen' } as ApiResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Test-Alert fehlgeschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Test-Alert fehlgeschlagen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +199,6 @@ export async function runDigestNow(_req: AuthRequest, res: Response): Promise<vo
|
||||
const result = await sendDigest({ force: true });
|
||||
res.json({ success: true, data: result } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Digest fehlgeschlagen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Digest fehlgeschlagen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import { AuthRequest } from '../types/index.js';
|
||||
import * as pdfTemplateService from '../services/pdfTemplate.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
@@ -57,10 +58,7 @@ export async function createTemplate(req: AuthRequest, res: Response) {
|
||||
|
||||
res.status(201).json({ success: true, data: { ...template, pdfFields } });
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +85,7 @@ export async function updateTemplate(req: AuthRequest, res: Response) {
|
||||
|
||||
res.json({ success: true, data: template });
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +114,7 @@ export async function extractFields(req: AuthRequest, res: Response) {
|
||||
const result = await pdfTemplateService.extractPdfFields(template.templatePath);
|
||||
res.json({ success: true, data: result.fields, totalPages: result.totalPages });
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Auslesen der PDF-Felder',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Auslesen der PDF-Felder');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +129,7 @@ export async function getAnnotatedPreview(req: AuthRequest, res: Response) {
|
||||
res.setHeader('Content-Disposition', 'inline; filename="preview.pdf"');
|
||||
res.send(pdfBuffer);
|
||||
} catch (error) {
|
||||
res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler' });
|
||||
antworteAufFehler(res, error, 'Fehler', 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +146,7 @@ export async function getRequiredInputs(req: AuthRequest, res: Response) {
|
||||
const inputs = await pdfTemplateService.getRequiredInputs(templateId, contractId);
|
||||
res.json({ success: true, data: inputs });
|
||||
} catch (error) {
|
||||
res.status(400).json({ success: false, error: error instanceof Error ? error.message : 'Fehler' });
|
||||
antworteAufFehler(res, error, 'Fehler', 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +187,6 @@ export async function generatePdf(req: AuthRequest, res: Response) {
|
||||
res.send(pdfBuffer);
|
||||
} catch (error) {
|
||||
console.error('PDF generate error:', error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Generieren',
|
||||
});
|
||||
antworteAufFehler(res, error, 'Fehler beim Generieren');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as platformService from '../services/platform.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -46,10 +47,7 @@ export async function createPlatform(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.status(201).json({ success: true, data: platform } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Vertriebsplattform',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Vertriebsplattform');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,10 +62,7 @@ export async function updatePlatform(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, data: platform } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Vertriebsplattform',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Vertriebsplattform');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,9 +78,6 @@ export async function deletePlatform(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, message: 'Vertriebsplattform gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Vertriebsplattform',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Vertriebsplattform');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as providerService from '../services/provider.service.js';
|
||||
@@ -99,10 +100,7 @@ export async function createProvider(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.status(201).json({ success: true, data: provider } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Anbieters',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Anbieters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +134,7 @@ export async function updateProvider(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, data: provider } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Anbieters',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Anbieters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +150,6 @@ export async function deleteProvider(req: Request, res: Response): Promise<void>
|
||||
});
|
||||
res.json({ success: true, message: 'Anbieter gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Anbieters',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Anbieters');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as stressfreiEmailService from '../services/stressfreiEmail.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse, AuthRequest } from '../types/index.js';
|
||||
@@ -93,11 +94,7 @@ export async function createEmail(req: Request, res: Response): Promise<void> {
|
||||
});
|
||||
res.status(201).json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 400;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Stressfrei-Wechseln Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen der Stressfrei-Wechseln Adresse', 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +116,7 @@ export async function updateEmail(req: AuthRequest, res: Response): Promise<void
|
||||
});
|
||||
res.json({ success: true, data: email } as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 400;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Stressfrei-Wechseln Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Stressfrei-Wechseln Adresse', 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,10 +133,7 @@ export async function deleteEmail(req: AuthRequest, res: Response): Promise<void
|
||||
});
|
||||
res.json({ success: true, message: 'Stressfrei-Wechseln Adresse gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen der Stressfrei-Wechseln Adresse',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen der Stressfrei-Wechseln Adresse');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +170,7 @@ export async function syncForwarding(req: AuthRequest, res: Response): Promise<v
|
||||
message: 'Weiterleitungen aktualisiert',
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Synchronisieren der Weiterleitungen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Synchronisieren der Weiterleitungen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,11 +215,7 @@ export async function updateAdditionalForwards(req: AuthRequest, res: Response):
|
||||
message: 'Weiterleitungen aktualisiert',
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
const status = error instanceof ApiError ? error.statusCode : 500;
|
||||
res.status(status).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Weiterleitungen',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren der Weiterleitungen', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,9 +238,6 @@ export async function resetPassword(req: AuthRequest, res: Response): Promise<vo
|
||||
message: 'Passwort wurde zurückgesetzt',
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen des Passworts',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Zurücksetzen des Passworts', 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import * as tariffService from '../services/tariff.service.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
@@ -48,10 +49,7 @@ export async function createTariff(req: Request, res: Response): Promise<void> {
|
||||
});
|
||||
res.status(201).json({ success: true, data: tariff } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Tarifs',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Erstellen des Tarifs');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +64,7 @@ export async function updateTariff(req: Request, res: Response): Promise<void> {
|
||||
});
|
||||
res.json({ success: true, data: tariff } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Tarifs',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Aktualisieren des Tarifs');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +80,6 @@ export async function deleteTariff(req: Request, res: Response): Promise<void> {
|
||||
});
|
||||
res.json({ success: true, message: 'Tarif gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Tarifs',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Tarifs');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { antworteAufFehler } from '../utils/fehlerAntwort.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import * as userService from '../services/user.service.js';
|
||||
@@ -520,10 +521,7 @@ export async function setUserPassword(req: Request, res: Response): Promise<void
|
||||
});
|
||||
res.json({ success: true, message: 'Passwort gesetzt' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Setzen des Passworts',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Setzen des Passworts');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,10 +537,7 @@ export async function deleteUser(req: Request, res: Response): Promise<void> {
|
||||
});
|
||||
res.json({ success: true, message: 'Benutzer gelöscht' } as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Löschen des Benutzers',
|
||||
} as ApiResponse);
|
||||
antworteAufFehler(res, error, 'Fehler beim Löschen des Benutzers');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { PORTAL_RECHTE } from '../config/rechte-katalog.js';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { AuthRequest, JwtPayload } from '../types/index.js';
|
||||
@@ -128,7 +129,18 @@ export function requirePermission(...requiredPermissions: string[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userPermissions = req.user.permissions || [];
|
||||
// Portal-Zugaenge bekommen hier nur, was in PORTAL_RECHTE steht -
|
||||
// unabhaengig davon, was ihr Token behauptet.
|
||||
//
|
||||
// Das ist ein Riegel, kein Filter: Die Trennung zwischen Kundenportal und
|
||||
// Rollensystem ist Absicht (siehe config/rechte-katalog.ts). Ein Kunde
|
||||
// darf niemals operative Rechte bekommen - die Portalansicht ist dafuer
|
||||
// nicht gebaut und prueft es nicht. Bisher haette eine einzige unbedachte
|
||||
// Zeile in der Token-Erzeugung gereicht, um das zu kippen; ab hier
|
||||
// braeuchte es zwei, an zwei Stellen, in Kenntnis dieses Kommentars.
|
||||
const userPermissions = req.user.isCustomerPortal
|
||||
? (req.user.permissions || []).filter((p) => PORTAL_RECHTE.includes(p))
|
||||
: req.user.permissions || [];
|
||||
|
||||
// Check if user has any of the required permissions
|
||||
const hasPermission = requiredPermissions.some((perm) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { PORTAL_RECHTE } from '../config/rechte-katalog.js';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import crypto from 'crypto';
|
||||
@@ -420,10 +421,9 @@ export async function customerLogin(email: string, password: string) {
|
||||
const representedCustomerIds = grantedRepresentingFor.map((rep) => rep.customer.id);
|
||||
|
||||
// Kundenportal-Berechtigungen (eingeschränkt)
|
||||
const customerPermissions = [
|
||||
'contracts:read', // Eigene Verträge lesen
|
||||
'customers:read', // Eigene Kundendaten lesen
|
||||
];
|
||||
// Feste Portal-Rechte aus config/rechte-katalog.ts. Bewusst getrennt vom
|
||||
// Rollensystem - siehe den Kommentar dort.
|
||||
const customerPermissions = [...PORTAL_RECHTE];
|
||||
|
||||
const payload: JwtPayload = {
|
||||
email: customer.portalEmail!,
|
||||
@@ -791,10 +791,7 @@ export async function getCustomerPortalUser(customerId: number) {
|
||||
|
||||
if (!customer || !customer.portalEnabled) return null;
|
||||
|
||||
const customerPermissions = [
|
||||
'contracts:read',
|
||||
'customers:read',
|
||||
];
|
||||
const customerPermissions = [...PORTAL_RECHTE];
|
||||
|
||||
// Selbe Live-Vollmacht-Filterung wie in customerLogin (Pentest Runde 10):
|
||||
// ohne sie zeigt /me dem Vertreter weiterhin widerrufene Beziehungen.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// "Geworben / angeworben": wer hat wen an Board geholt.
|
||||
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { FachlicherFehler } from '../utils/apiError.js';
|
||||
|
||||
// Erlaubte Beziehungsarten (Whitelist). Server vertraut dem Frontend-Dropdown
|
||||
// nicht – ein handgebauter Request mit beliebigem String wird abgewiesen.
|
||||
@@ -64,10 +65,11 @@ export async function getReferralsForCustomer(customerId: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export class ReferralError extends Error {
|
||||
status: number;
|
||||
export class ReferralError extends FachlicherFehler {
|
||||
/** Beibehalten, weil bestehende Aufrufer `.status` lesen. */
|
||||
readonly status: number;
|
||||
constructor(message: string, status = 400) {
|
||||
super(message);
|
||||
super(message, status);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { SYSTEMROLLEN } from '../config/rechte-katalog.js';
|
||||
import { SYSTEMROLLEN, pruefePortalRechte } from '../config/rechte-katalog.js';
|
||||
|
||||
/**
|
||||
* Prueft beim Start, ob die gesetzlich gebundenen Rechte ueberhaupt jemand
|
||||
@@ -59,6 +59,27 @@ export async function pruefePflichtrechte(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Wache ueber die Portal-Trennung.
|
||||
//
|
||||
// Ein schreibendes Recht in PORTAL_RECHTE waere kein Schoenheitsfehler,
|
||||
// sondern eine stille Ausweitung der Kundenrechte - jeder Portal-Kunde
|
||||
// haette es sofort. Diese Meldung soll den Tag ueberleben, an dem jemand
|
||||
// "nur kurz" etwas hinzufuegt.
|
||||
const portalBeanstandet = pruefePortalRechte();
|
||||
if (portalBeanstandet.length > 0) {
|
||||
console.warn(
|
||||
'\n' +
|
||||
'========================================================================\n' +
|
||||
' ACHTUNG: Das Kundenportal traegt Rechte, die nicht Lesen sind:\n' +
|
||||
portalBeanstandet.map((r) => ` – ${r}\n`).join('') +
|
||||
'\n' +
|
||||
' Kunden sollen im Portal NIEMALS operative Rechte haben. Die Ansicht\n' +
|
||||
' ist dafuer nicht gebaut und prueft es nicht. Bitte PORTAL_RECHTE in\n' +
|
||||
' config/rechte-katalog.ts pruefen.\n' +
|
||||
'========================================================================\n',
|
||||
);
|
||||
}
|
||||
|
||||
// Zweite Wache: Stimmen die Systemrollen noch?
|
||||
//
|
||||
// Die versteckten Rollen werden ueber ihren NAMEN gefunden
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
import prisma from '../lib/prisma.js';
|
||||
import { FachlicherFehler } from '../utils/apiError.js';
|
||||
|
||||
/** Groesste Zahl, die in eine INT-Spalte passt. Darueber gibt es keine ID. */
|
||||
const INT_MAX = 2147483647;
|
||||
@@ -35,13 +36,13 @@ import {
|
||||
} from '../config/rechte-katalog.js';
|
||||
|
||||
/** Der Handelnde wollte Rechte vergeben, die er selbst nicht besitzt. */
|
||||
export class RechteEskalationError extends Error {
|
||||
export class RechteEskalationError extends FachlicherFehler {
|
||||
constructor(public readonly fehlend: string[]) {
|
||||
super(
|
||||
'Sie können nur Rechte vergeben, die Sie selbst besitzen. ' +
|
||||
`Nicht vergeben werden können: ${fehlend.join(', ')}`,
|
||||
403,
|
||||
);
|
||||
this.name = 'RechteEskalationError';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,18 +51,16 @@ export class RechteEskalationError extends Error {
|
||||
* Rollen-ID. Eigene Klasse, damit daraus ein 400 wird und nicht ein 500 aus
|
||||
* einem Fremdschluesselfehler tief in Prisma.
|
||||
*/
|
||||
export class UngueltigeEingabeError extends Error {
|
||||
export class UngueltigeEingabeError extends FachlicherFehler {
|
||||
constructor(nachricht: string) {
|
||||
super(nachricht);
|
||||
this.name = 'UngueltigeEingabeError';
|
||||
super(nachricht, 400);
|
||||
}
|
||||
}
|
||||
|
||||
/** Der Vorgang zielte auf eine Systemrolle, die von der Anwendung gepflegt wird. */
|
||||
export class RollenSperrError extends Error {
|
||||
export class RollenSperrError extends FachlicherFehler {
|
||||
constructor(nachricht: string) {
|
||||
super(nachricht);
|
||||
this.name = 'RollenSperrError';
|
||||
super(nachricht, 403);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -169,6 +169,79 @@ export async function getUserById(id: number) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Faehigkeiten, ohne die sich das System nicht mehr verwalten laesst.
|
||||
*
|
||||
* Bis 09/2026 galt hier "wer `users:delete` hat, ist Admin". Das war ein
|
||||
* Zufallsmerkmal: Wer Konten anlegen und bearbeiten darf, aber nicht loeschen,
|
||||
* verwaltet genauso - zaehlte aber nicht. Umgekehrt zaehlte jemand mit nur
|
||||
* `users:delete` als Admin, obwohl er kein Konto anlegen kann. Ein
|
||||
* Sicherheitsnetz an einem Merkmal, das mit der geschuetzten Eigenschaft nur
|
||||
* lose zusammenhing (Pentest R193, Nachrangpunkt 2).
|
||||
*
|
||||
* Bewusst NICHT auf die Rolle "Admin" umgestellt, wie zunaechst vorgeschlagen:
|
||||
* Eine selbst gebaute Rolle mit `users:update` verwaltet tatsaechlich, und ein
|
||||
* Namenskriterium wuerde sie uebersehen - man koennte dann den letzten
|
||||
* Admin loeschen, obwohl die Verwaltungsfaehigkeit erhalten bliebe. Geschuetzt
|
||||
* wird die FAEHIGKEIT, nicht ihr ueblicher Traeger.
|
||||
*
|
||||
* `users:update` ist das Minimum, um Konten wieder in Ordnung zu bringen
|
||||
* (Rollen zuweisen). `roles:manage` ist das Minimum, um Rollen zu definieren.
|
||||
* Faellt eines davon ersatzlos weg, hilft nur noch die Kommandozeile.
|
||||
*/
|
||||
const UNVERZICHTBARE_FAEHIGKEITEN = [
|
||||
{ recht: 'users:update', wofuer: 'Benutzer verwalten' },
|
||||
{ recht: 'roles:manage', wofuer: 'Rollen und Rechte pflegen' },
|
||||
] as const;
|
||||
|
||||
/** Die Rechte eines Kontos aus seinen geladenen Rollen. */
|
||||
function rechteAusRollen(
|
||||
rollen: Array<{ role: { permissions: Array<{ permission: { resource: string; action: string } }> } }>,
|
||||
): Set<string> {
|
||||
const rechte = new Set<string>();
|
||||
for (const ur of rollen) {
|
||||
for (const rp of ur.role.permissions) {
|
||||
rechte.add(`${rp.permission.resource}:${rp.permission.action}`);
|
||||
}
|
||||
}
|
||||
return rechte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verhindert, dass die letzte Person mit einer unverzichtbaren Faehigkeit
|
||||
* sie verliert - durch Rollenwechsel, Deaktivierung oder Loeschung.
|
||||
*
|
||||
* `kuenftigeRechte === null` heisst: Das Konto faellt ganz weg.
|
||||
*/
|
||||
async function pruefeLetzterVerwalter(
|
||||
zielId: number,
|
||||
bisherigeRechte: Set<string>,
|
||||
kuenftigeRechte: Set<string> | null,
|
||||
): Promise<void> {
|
||||
for (const { recht, wofuer } of UNVERZICHTBARE_FAEHIGKEITEN) {
|
||||
if (!bisherigeRechte.has(recht)) continue; // hatte es nie
|
||||
if (kuenftigeRechte?.has(recht)) continue; // behaelt es
|
||||
|
||||
const [resource, action] = recht.split(':');
|
||||
const andere = await prisma.user.count({
|
||||
where: {
|
||||
id: { not: zielId },
|
||||
isActive: true,
|
||||
roles: { some: { role: { permissions: { some: { permission: { resource, action } } } } } },
|
||||
},
|
||||
});
|
||||
|
||||
if (andere === 0) {
|
||||
throw new Error(
|
||||
`Dieses Konto ist das letzte, das „${wofuer}" kann (${recht}). ` +
|
||||
'Ohne es liesse sich das System nur noch über die Kommandozeile ' +
|
||||
'wieder in Ordnung bringen. Bitte zuerst einem anderen aktiven Konto ' +
|
||||
'die entsprechende Rolle geben.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function createUser(data: {
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -324,74 +397,31 @@ export async function updateUser(
|
||||
},
|
||||
});
|
||||
|
||||
const isCurrentlyAdmin = currentUser?.roles.some((ur) =>
|
||||
ur.role.permissions.some(
|
||||
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
||||
)
|
||||
);
|
||||
// Geschuetzt wird die FAEHIGKEIT, nicht ihr ueblicher Traeger -
|
||||
// siehe UNVERZICHTBARE_FAEHIGKEITEN.
|
||||
const bisherigeRechte = rechteAusRollen(currentUser?.roles ?? []);
|
||||
|
||||
if (isCurrentlyAdmin) {
|
||||
// Check if user will still be admin after role change
|
||||
let willStillBeAdmin = false;
|
||||
if (rolesAreBeingChanged) {
|
||||
const newRoles = await prisma.role.findMany({
|
||||
where: { id: { in: gepruefteRollenIds! } },
|
||||
include: {
|
||||
permissions: {
|
||||
include: { permission: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
willStillBeAdmin = newRoles.some((role) =>
|
||||
role.permissions.some(
|
||||
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
willStillBeAdmin = true; // Roles not being changed
|
||||
}
|
||||
|
||||
// If user is losing admin status or being deactivated, check for other admins
|
||||
if (!willStillBeAdmin || isBeingDeactivated) {
|
||||
const otherAdminCount = await prisma.user.count({
|
||||
where: {
|
||||
id: { not: id },
|
||||
isActive: true,
|
||||
roles: {
|
||||
some: {
|
||||
role: {
|
||||
permissions: {
|
||||
some: {
|
||||
permission: {
|
||||
resource: 'users',
|
||||
action: 'delete',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (otherAdminCount === 0) {
|
||||
if (isBeingDeactivated) {
|
||||
throw new Error(
|
||||
'Dieser Benutzer ist der letzte Administrator und kann nicht deaktiviert werden'
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Die Admin-Rolle kann nicht entfernt werden, da dies der letzte Administrator ist'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let kuenftigeRechte: Set<string> | null;
|
||||
if (isBeingDeactivated) {
|
||||
// Ein deaktiviertes Konto kann nichts mehr, egal welche Rollen formal
|
||||
// drankleben.
|
||||
kuenftigeRechte = null;
|
||||
} else if (rolesAreBeingChanged) {
|
||||
const neueRollen = await prisma.role.findMany({
|
||||
where: { id: { in: gepruefteRollenIds! } },
|
||||
include: { permissions: { include: { permission: true } } },
|
||||
});
|
||||
kuenftigeRechte = rechteAusRollen(neueRollen.map((role) => ({ role })));
|
||||
} else {
|
||||
kuenftigeRechte = bisherigeRechte;
|
||||
}
|
||||
|
||||
await pruefeLetzterVerwalter(id, bisherigeRechte, kuenftigeRechte);
|
||||
}
|
||||
|
||||
// Hash password if provided
|
||||
if (password) {
|
||||
(userData as Record<string, unknown>).password = await bcrypt.hash(password, 10);
|
||||
(userData as Record<string, unknown>).password = await bcrypt.hash(password, 12);
|
||||
}
|
||||
|
||||
// Prüfen ob Rollen geändert werden (für Zwangslogout)
|
||||
@@ -549,42 +579,9 @@ export async function deleteUser(id: number) {
|
||||
throw new Error('Benutzer nicht gefunden');
|
||||
}
|
||||
|
||||
// Check if user has admin permissions (users:delete means admin)
|
||||
const isAdmin = user.roles.some((ur) =>
|
||||
ur.role.permissions.some(
|
||||
(rp) => rp.permission.resource === 'users' && rp.permission.action === 'delete'
|
||||
)
|
||||
);
|
||||
|
||||
if (isAdmin) {
|
||||
// Count other admins (users with users:delete permission)
|
||||
const adminCount = await prisma.user.count({
|
||||
where: {
|
||||
id: { not: id },
|
||||
isActive: true,
|
||||
roles: {
|
||||
some: {
|
||||
role: {
|
||||
permissions: {
|
||||
some: {
|
||||
permission: {
|
||||
resource: 'users',
|
||||
action: 'delete',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (adminCount === 0) {
|
||||
throw new Error(
|
||||
'Dieser Benutzer ist der letzte Administrator und kann nicht gelöscht werden'
|
||||
);
|
||||
}
|
||||
}
|
||||
// Dasselbe Kriterium wie beim Bearbeiten: Geschuetzt wird die Faehigkeit,
|
||||
// das System zu verwalten - nicht die Rolle, die sie ueblicherweise traegt.
|
||||
await pruefeLetzterVerwalter(id, rechteAusRollen(user.roles), null);
|
||||
|
||||
return prisma.user.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -8,11 +8,33 @@
|
||||
* obwohl die Fehlermeldung "Dokument vor wenigen Sekunden bereits
|
||||
* angelegt" eindeutig eine 400-Class-Situation ist.
|
||||
*/
|
||||
export class ApiError extends Error {
|
||||
/**
|
||||
* Basisklasse fuer Fehler, deren Wortlaut fuer den AUFRUFER bestimmt ist.
|
||||
*
|
||||
* Der Unterschied ist der Kern von R190-01 und R192-01: Bis 09/2026 reichten
|
||||
* die Controller jede `error.message` durch. Damit landete erst der
|
||||
* vollstaendige Prisma-Aufruf samt Serverpfad beim Client und dann der
|
||||
* Wortlaut eines TypeError. Beide Male dieselbe Ursache - eine Meldung, die
|
||||
* fuer Entwickler geschrieben ist, an einen Empfaenger, fuer den sie nicht
|
||||
* gedacht war.
|
||||
*
|
||||
* Ab hier gilt: Was von dieser Klasse abstammt (oder ein blankes `Error` ist,
|
||||
* das wir absichtlich geworfen haben), geht nach draussen. Alles andere -
|
||||
* TypeError, Prisma, was auch immer - wird protokolliert und durch eine
|
||||
* allgemeine Auskunft ersetzt.
|
||||
*/
|
||||
export class FachlicherFehler extends Error {
|
||||
readonly statusCode: number;
|
||||
constructor(statusCode: number, message: string) {
|
||||
constructor(message: string, statusCode = 400) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
this.name = new.target.name;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
export class ApiError extends FachlicherFehler {
|
||||
constructor(statusCode: number, message: string) {
|
||||
super(message, statusCode);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Eine Stelle, an der entschieden wird, was ein Fehler dem Aufrufer sagt.
|
||||
*
|
||||
* Vorher stand in 26 Controllern 124-mal dieselbe Zeile:
|
||||
*
|
||||
* error: error instanceof Error ? error.message : 'Irgendein Fallback'
|
||||
*
|
||||
* Das reicht JEDE Fehlermeldung durch - auch die, die fuer Entwickler
|
||||
* geschrieben sind. Konkret gefunden wurden ein vollstaendiger Prisma-Aufruf
|
||||
* samt Serverpfad (R190-01) und der Wortlaut eines TypeError (R192-01). Kein
|
||||
* Loch fuer sich genommen, aber eine Landkarte: Pfade, Spaltennamen,
|
||||
* eingesetzte Bibliotheken.
|
||||
*
|
||||
* 124 Einzelkorrekturen waeren die falsche Antwort gewesen - das ist die
|
||||
* Falle aus R186-01 und R188, wo dieselbe Regel in mehreren Kopien lebte und
|
||||
* auseinanderlief. Deshalb eine Funktion.
|
||||
*
|
||||
* Die Unterscheidung laeuft ueber die Fehlerklasse, nicht ueber eine
|
||||
* Heuristik auf dem Text:
|
||||
*
|
||||
* - `FachlicherFehler` (und Abkoemmlinge wie `ApiError`) - absichtlich
|
||||
* geworfen, Wortlaut fuer den Aufrufer, eigener Statuscode.
|
||||
* - blankes `Error` - ebenfalls absichtlich geworfen, historisch die
|
||||
* ueblichste Form ("Der letzte Admin kann nicht..."). Geht mit dem
|
||||
* uebergebenen Status hinaus.
|
||||
* - alles andere (TypeError, RangeError, Prisma...) - Programmierfehler oder
|
||||
* Fremdfehler. Geht als 500 mit der allgemeinen Auskunft hinaus, die
|
||||
* Einzelheiten ins Protokoll. 500 und nicht 400, weil ein
|
||||
* Eingabefehler-Code hier die naechste Meldung waere, die sich falsch
|
||||
* ausgibt.
|
||||
*/
|
||||
|
||||
import { Response } from 'express';
|
||||
import { FachlicherFehler } from './apiError.js';
|
||||
|
||||
export function antworteAufFehler(
|
||||
res: Response,
|
||||
error: unknown,
|
||||
fallback: string,
|
||||
status = 400,
|
||||
): void {
|
||||
if (error instanceof FachlicherFehler) {
|
||||
res.status(error.statusCode).json({ success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.constructor === Error) {
|
||||
res.status(status).json({ success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[${fallback}] unerwarteter Fehler:`, error);
|
||||
res.status(500).json({ success: false, error: fallback });
|
||||
}
|
||||
Reference in New Issue
Block a user