feat(gdpr): fullständig raderingsflöde med pseudonym-retention, residual-test och hushållshantering
- Implementerar eraseUser() i packages/database/src/gdpr-erasure.ts som raderar/anonymiserar/pseudonymiserar alla personliga tabeller. - DELETE /v1/me orkestrerar nu plan + transaktion + lagringsrensning. - Enpersonshushåll raderas automatiskt; publika recept anonymiseras; draft/private-recept raderas. - Finansiella poster (subscriptions/store_transactions/subscription_events) behålls under retention med slumpmässig pseudonym från gdpr_retention_pseudonyms. - audit_logs/domain_events anonymiseras; kvitton i kvarvarande hushåll strippas på bild och OCR-text. - Lägger till permanent residual-test (me.residual.test.ts) med schema-diff-assertion mot information_schema. - Uppdaterar docs/23 och docs/29 med implementerad mekanism. - Migration 0021 för gdpr_retention_pseudonyms och nullable cooking_sessions.started_by_user_id.
This commit is contained in:
@@ -43,6 +43,12 @@ export interface StorageService {
|
||||
getObject(key: string): Promise<Buffer | null>;
|
||||
/** GDPR / rättning: radera ett objekt. Får inte kasta om nyckeln saknas. */
|
||||
deleteObject(key: string): Promise<void>;
|
||||
/**
|
||||
* Försök extrahera lagringsnyckeln ur en läs-URL. Returnerar null om URL:en
|
||||
* inte känns igen (t.ex. extern bild). Används vid GDPR-radering så att vi
|
||||
* kan radera objekt även när endast URL:en är sparad.
|
||||
*/
|
||||
extractKeyFromUrl(url: string): string | null;
|
||||
}
|
||||
|
||||
declare module "fastify" {
|
||||
@@ -99,6 +105,17 @@ class MockStorage implements StorageService {
|
||||
await rm(path.join(MOCK_ROOT, key), { force: true }).catch(() => {});
|
||||
}
|
||||
|
||||
extractKeyFromUrl(url: string): string | null {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const prefix = "/v1/mock-s3/";
|
||||
if (!parsed.pathname.startsWith(prefix)) return null;
|
||||
return decodeURIComponent(parsed.pathname.slice(prefix.length));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
verifySignature(key: string, sig: string): boolean {
|
||||
return this.sign(key) === sig;
|
||||
}
|
||||
@@ -177,6 +194,22 @@ export class AwsStorage implements StorageService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractKeyFromUrl(url: string): string | null {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
if (parsed.hostname.startsWith(`${this.bucket}.`)) {
|
||||
return decodeURIComponent(parsed.pathname.replace(/^\//, ""));
|
||||
}
|
||||
const pathPrefix = `/${this.bucket}/`;
|
||||
if (parsed.pathname.startsWith(pathPrefix)) {
|
||||
return decodeURIComponent(parsed.pathname.slice(pathPrefix.length));
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const storagePlugin = fp(async (app: FastifyInstance) => {
|
||||
|
||||
Reference in New Issue
Block a user