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',
|
cancellationDate: 'Kündigungsdatum', cancellationSentDate: 'Kündigung gesendet am',
|
||||||
identityDocumentId: 'Ausweis', bankCardId: 'Bankverbindung', addressId: 'Adresse',
|
identityDocumentId: 'Ausweis', bankCardId: 'Bankverbindung', addressId: 'Adresse',
|
||||||
commission: 'Provision', notes: 'Notizen',
|
commission: 'Provision', notes: 'Notizen',
|
||||||
|
portalCredentialsNotRequired: 'Portaldaten-Opt-out',
|
||||||
|
stressfreiEmailId: 'Stressfrei-/Corporate-Adresse',
|
||||||
};
|
};
|
||||||
const energyLabels: Record<string, string> = {
|
const energyLabels: Record<string, string> = {
|
||||||
meterId: 'Zähler', maloId: 'MaLo-ID', annualConsumption: 'Jahresverbrauch',
|
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,
|
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.
|
// Response sanitisieren – sonst leakt portalPasswordEncrypted etc.
|
||||||
// (Pentest Runde 15, gleiche Klasse wie 20.3 für Customer).
|
// (Pentest Runde 15, gleiche Klasse wie 20.3 für Customer).
|
||||||
const isPortal = !!req.user?.isCustomerPortal;
|
const isPortal = !!req.user?.isCustomerPortal;
|
||||||
|
|||||||
@@ -97,6 +97,19 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
|||||||
|
|
||||||
## ✅ Erledigt
|
## ✅ 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**
|
- [x] **🔕 Vertrag: „Portaldaten nicht möglich / nicht benötigt"-Opt-out**
|
||||||
- Dritter Radio-Button in der `Zugangsdaten`-Card des Vertragsformulars:
|
- Dritter Radio-Button in der `Zugangsdaten`-Card des Vertragsformulars:
|
||||||
„Nicht möglich zu bekommen / nicht benötigt". Wenn gesetzt, unterdrückt
|
„Nicht möglich zu bekommen / nicht benötigt". Wenn gesetzt, unterdrückt
|
||||||
|
|||||||
Reference in New Issue
Block a user