fix(compliance): port DSR/consent/audit/retention audit fixes
Ports the upstream compliance-core audit fixes onto the kept core-dsr, core-consent, core-audit, core-cms and core-shared packages (pristine template state here, so taken to the fixed end-state): - core-dsr: scope DSR operations to the caller's own subject (A11); include the subject's audit trail in exports; resolve the per-request binding from ctx instead of a throwing singleton proxy. - core-consent: build the consent router from the shared superjson transformer (A10); merge per-category on persist instead of replacing; validate migrated categories against an allow-list. - core-audit: keyed 128-bit pseudonyms + salted DSR certificate; add the audit-logs collection and the req-scoped GDPR audit-erasure afterDelete hook (A6). - core-shared: grace-purge soft-deleted rows via a retention-purge task + tombstone field and boot registration (A2/A3); add the require-authenticated tRPC helper; derive clientIp + resolve the session user in createTrpcContext (B2/A11). - core-cms: register audit-logs, wire the audit-erasure hook and retention-purge tasks; adapted to our collection set (users, workspaces). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { CollectionAfterDeleteHook } from "payload";
|
||||
import type { IAuditLog } from "../audit-log.interface";
|
||||
import { PayloadAuditLog } from "../payload-audit-log";
|
||||
|
||||
export type AuditErasureHookOpts = {
|
||||
/** The audit log impl that will perform the erasure. */
|
||||
@@ -36,3 +37,37 @@ export function createAuditErasureHook(
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type ReqScopedAuditErasureHookOpts = {
|
||||
/** Erasure mode — see AuditErasureHookOpts. Defaults to "pseudonymize". */
|
||||
mode?: "pseudonymize" | "delete";
|
||||
};
|
||||
|
||||
/**
|
||||
* Variant of `createAuditErasureHook` for config-composition time (audit
|
||||
* finding A6): a Payload collection config is built before any `IAuditLog`
|
||||
* can exist (binding the audit log needs the built config), so this hook
|
||||
* constructs a `PayloadAuditLog` lazily from the running instance on
|
||||
* `req.payload` when the delete fires. No-ops when the `audit-logs`
|
||||
* collection is not registered.
|
||||
*/
|
||||
export function createReqScopedAuditErasureHook(
|
||||
opts: ReqScopedAuditErasureHookOpts = {},
|
||||
): CollectionAfterDeleteHook {
|
||||
const mode = opts.mode ?? "pseudonymize";
|
||||
return async ({ doc, req }) => {
|
||||
if (typeof doc.id !== "string" && typeof doc.id !== "number") return;
|
||||
const payload = req.payload;
|
||||
// `slug as string`: apps with generated CollectionSlug types narrow slug
|
||||
// to their registered union, which need not include "audit-logs".
|
||||
const hasAuditCollection = payload.config.collections?.some(
|
||||
(c) => (c.slug as string) === "audit-logs",
|
||||
);
|
||||
if (!hasAuditCollection) return;
|
||||
const auditLog = new PayloadAuditLog(
|
||||
payload.config,
|
||||
async () => payload as never,
|
||||
);
|
||||
await auditLog.eraseSubject(String(doc.id), mode);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user