Audit: Portaldaten-Opt-out als eigenes CRITICAL-Event loggen
Auf Wunsch des Pentesters (R117-Nachtrag). Das Umschalten des portalCredentialsNotRequired-Flags emittiert jetzt zusätzlich zum generischen Contract-Update-Diff ein dediziertes UPDATE-Event unter resourceType 'ContractPassword' – landet damit in derselben CRITICAL-Sensitivity-Reihe wie Klartext-Password-Reads. Motivation: das Setzen des Flags räumt server-seitig portalUsername, portalPasswordEncrypted und stressfreiEmailId auf NULL. Diese Löschung soll unabhängig vom generischen Diff sofort sichtbar sein. Rücknahme wird ebenfalls geloggt. Details enthält alte + neue Flag-Werte plus Bool-Marker, welche Anmeldedaten vor dem Opt-out belegt waren – keine Klartext- Leckage im Log. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -235,6 +235,8 @@ export async function updateContract(req: AuthRequest, res: Response): Promise<v
|
||||
cancellationDate: 'Kündigungsdatum', cancellationSentDate: 'Kündigung gesendet am',
|
||||
identityDocumentId: 'Ausweis', bankCardId: 'Bankverbindung', addressId: 'Adresse',
|
||||
commission: 'Provision', notes: 'Notizen',
|
||||
portalCredentialsNotRequired: 'Portaldaten-Opt-out',
|
||||
stressfreiEmailId: 'Stressfrei-/Corporate-Adresse',
|
||||
};
|
||||
const energyLabels: Record<string, string> = {
|
||||
meterId: 'Zähler', maloId: 'MaLo-ID', annualConsumption: 'Jahresverbrauch',
|
||||
@@ -280,6 +282,39 @@ export async function updateContract(req: AuthRequest, res: Response): Promise<v
|
||||
customerId: before?.customerId,
|
||||
});
|
||||
|
||||
// Zusätzliches CRITICAL-Event für das „Portaldaten nicht benötigt"-Opt-out.
|
||||
// Landet unter resourceType ContractPassword (sensitivity CRITICAL laut
|
||||
// audit.service determineSensitivity) und liegt damit in derselben
|
||||
// DSGVO-Reihe wie Klartext-Password-Reads. Motivation: das Setzen des
|
||||
// Flags räumt server-seitig portalUsername/portalPasswordEncrypted/
|
||||
// stressfreiEmailId auf NULL – diese Löschung soll unabhängig vom
|
||||
// generischen UPDATE-Diff nachweisbar sein. Rücknahme des Flags wird
|
||||
// ebenfalls geloggt, damit die Kette in beide Richtungen konsistent ist.
|
||||
if (
|
||||
before
|
||||
&& 'portalCredentialsNotRequired' in body
|
||||
&& before.portalCredentialsNotRequired !== body.portalCredentialsNotRequired
|
||||
) {
|
||||
const toggledOn = body.portalCredentialsNotRequired === true;
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'ContractPassword',
|
||||
resourceId: contractId.toString(),
|
||||
label: toggledOn
|
||||
? `Vertrag ${before.contractNumber || contractId}: Portal-Zugangsdaten als „nicht erforderlich" markiert (Username/Passwort/Stressfrei-ID auf NULL gesetzt)`
|
||||
: `Vertrag ${before.contractNumber || contractId}: „Portaldaten nicht erforderlich"-Marker zurückgenommen`,
|
||||
details: {
|
||||
vorher: before.portalCredentialsNotRequired,
|
||||
nachher: body.portalCredentialsNotRequired,
|
||||
hatUsernameVorher: !!before.portalUsername,
|
||||
hatPasswortVorher: !!before.portalPasswordEncrypted,
|
||||
hatStressfreiVorher: !!before.stressfreiEmailId,
|
||||
},
|
||||
customerId: before.customerId,
|
||||
});
|
||||
}
|
||||
|
||||
// Response sanitisieren – sonst leakt portalPasswordEncrypted etc.
|
||||
// (Pentest Runde 15, gleiche Klasse wie 20.3 für Customer).
|
||||
const isPortal = !!req.user?.isCustomerPortal;
|
||||
|
||||
@@ -97,6 +97,19 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **📝 DSGVO-Audit: Portaldaten-Opt-out als eigenes CRITICAL-Event**
|
||||
- Auf Wunsch des Pentesters (R117-Nachtrag): das Umschalten des
|
||||
`portalCredentialsNotRequired`-Flags emittiert jetzt zusätzlich zum
|
||||
generischen Contract-Update-Diff ein dediziertes `UPDATE`-Event
|
||||
unter `resourceType: 'ContractPassword'` — landet damit in derselben
|
||||
CRITICAL-Sensitivity-Reihe wie Klartext-Password-Reads.
|
||||
- Motivation: das Setzen des Flags räumt server-seitig
|
||||
`portalUsername`/`portalPasswordEncrypted`/`stressfreiEmailId` auf
|
||||
`NULL`. Diese Löschung sensitiver Daten ist jetzt unabhängig vom
|
||||
generischen Diff nachweisbar. Rücknahme wird ebenfalls geloggt.
|
||||
- `details` enthält alte + neue Flag-Werte plus Bool-Marker, welche
|
||||
Anmeldedaten vor dem Opt-out belegt waren (keine Klartext-Leckage).
|
||||
|
||||
- [x] **🔕 Vertrag: „Portaldaten nicht möglich / nicht benötigt"-Opt-out**
|
||||
- Dritter Radio-Button in der `Zugangsdaten`-Card des Vertragsformulars:
|
||||
„Nicht möglich zu bekommen / nicht benötigt". Wenn gesetzt, unterdrückt
|
||||
|
||||
Reference in New Issue
Block a user