fix(gdpr): explicit radering av ai_corrections/scan_jobs + S3-bilder vid DELETE /v1/me
- DELETE /v1/me soft-deletar users, så users-cascade fyrar aldrig. - Samlar distinkta bildnycklar från ai_corrections, ai_training_bank och scan_jobs och raderar dem via storage.deleteObject innan DB-radering. - S3-fel loggas och avbryter inte raderingen. - Raderar explicit ai_corrections (ai_training_bank cascadar) och scan_jobs. - Lägger till deleteObject i StorageService (mock + AWS/DeleteObjectCommand). - Uppdaterar docs/28-lärande-loop.md med faktisk mekanism och retention. - Tester: verifierar noll rader kvar och storage.deleteObject-anrop. Relaterat: skiva-1-fixrunda, blockerande GDPR-hål.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import fp from "fastify-plugin";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { createHmac, randomUUID } from "node:crypto";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
/**
|
||||
@@ -19,6 +19,7 @@ import path from "node:path";
|
||||
* meal-scans/, receipts/, product-images/, recipe-images/, temporary/.
|
||||
*/
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
HeadBucketCommand,
|
||||
PutObjectCommand,
|
||||
@@ -40,6 +41,8 @@ export interface StorageService {
|
||||
getReadUrl(key: string): Promise<string>;
|
||||
putObject(key: string, data: Buffer, contentType: string): Promise<void>;
|
||||
getObject(key: string): Promise<Buffer | null>;
|
||||
/** GDPR / rättning: radera ett objekt. Får inte kasta om nyckeln saknas. */
|
||||
deleteObject(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
declare module "fastify" {
|
||||
@@ -91,6 +94,11 @@ class MockStorage implements StorageService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteObject(key: string): Promise<void> {
|
||||
// GDPR-radering får inte avbrytas av saknade filer; rm med force swallowar felet.
|
||||
await rm(path.join(MOCK_ROOT, key), { force: true }).catch(() => {});
|
||||
}
|
||||
|
||||
verifySignature(key: string, sig: string): boolean {
|
||||
return this.sign(key) === sig;
|
||||
}
|
||||
@@ -157,6 +165,18 @@ export class AwsStorage implements StorageService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteObject(key: string): Promise<void> {
|
||||
try {
|
||||
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
|
||||
} catch (err) {
|
||||
const code = (err as { Code?: string }).Code;
|
||||
// NoSuchKey ska inte avbryta GDPR-radering; andra fel loggas.
|
||||
if (code !== "NoSuchKey" && code !== "NotFound") {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const storagePlugin = fp(async (app: FastifyInstance) => {
|
||||
|
||||
Reference in New Issue
Block a user