Datenminimierung: Rohdaten, Protokoll und Loeschung
Drei Befunde aus einer Durchsicht der gespeicherten personenbezogenen Daten: Der Import legte bisher jede Spalte der hochgeladenen Datei dauerhaft ab, auch Mailadressen oder Rechnungsdaten, die nie zugeordnet wurden. Nach dem Buchen oder Verwerfen bleiben jetzt nur noch die gemappten Spalten stehen. Ein fehlgeschlagener Login protokollierte den eingetippten Benutzernamen. Wer sein Passwort ins Namensfeld tippt, hatte es damit im Klartext im Audit-Log. Der Name wird nur noch gespeichert, wenn das Konto existiert. Das Loeschen eines Spielers liess Spuren in den Import-Zeilen und in den Details frueherer Audit-Eintraege zurueck. Beides wird jetzt mit abgeraeumt; die Vorgaenge bleiben als Eintrag bestehen, nur ohne Inhalt. Dazu eine Aufbewahrungsfrist fuer das Audit-Log (Standard 12 Monate, ueber AUDIT_RETENTION_DAYS einstellbar). Die Guthaben-Historie ist davon nicht betroffen - sie bleibt die Quelle der Wahrheit fuer bezahlte Ansprueche.
This commit is contained in:
@@ -132,6 +132,12 @@ export async function updatePlayer(playerId: number, input: Partial<PlayerInput>
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loescht einen Spieler samt allem, was an ihm haengt. Spuren in den
|
||||
* Import-Zeilen und in frueheren Audit-Eintraegen werden dabei mit entfernt,
|
||||
* damit nach der Loeschung wirklich nichts Personenbezogenes uebrig bleibt.
|
||||
* Die Audit-Eintraege selbst bleiben als Vorgang bestehen, nur ohne Inhalt.
|
||||
*/
|
||||
export async function deletePlayer(playerId: number, ctx: ActionContext): Promise<void> {
|
||||
const existing = await prisma.player.findUnique({
|
||||
where: { id: playerId },
|
||||
@@ -140,15 +146,37 @@ export async function deletePlayer(playerId: number, ctx: ActionContext): Promis
|
||||
if (!existing) throw notFound('Spieler');
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Rohzeilen der Importe verlieren ihren Personenbezug.
|
||||
await tx.importRow.updateMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ resolvedPlayerId: playerId },
|
||||
...(existing.steamId ? [{ playerKey: existing.steamId }] : []),
|
||||
],
|
||||
},
|
||||
data: {
|
||||
playerKey: null,
|
||||
playerName: null,
|
||||
resolvedPlayerId: null,
|
||||
raw: {},
|
||||
},
|
||||
});
|
||||
|
||||
// Fruehere Audit-Eintraege zu diesem Spieler behalten den Vorgang, nicht den Inhalt.
|
||||
await tx.auditLog.updateMany({
|
||||
where: { targetType: 'player', targetId: String(playerId) },
|
||||
data: { details: { redacted: true } },
|
||||
});
|
||||
|
||||
await tx.player.delete({ where: { id: playerId } });
|
||||
|
||||
await recordAudit(tx, ctx, {
|
||||
action: 'player.delete',
|
||||
targetType: 'player',
|
||||
targetId: playerId,
|
||||
// Bewusst ohne Name und SteamID: der Eintrag soll die Loeschung belegen,
|
||||
// nicht die geloeschten Daten konservieren.
|
||||
details: {
|
||||
displayName: existing.displayName,
|
||||
steamId: existing.steamId,
|
||||
removedCharacters: existing._count.characters,
|
||||
removedLedgerRows: existing._count.ledger,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user