feat(core-cms): register audit-logs + wire GDPR audit erasure

The audit-logs collection was never registered (record() would throw),
bindAudit/createAuditErasureHook were unused, and DSR cascade-hard never
touched the audit trail (audit finding A6). core-cms now registers the
collection and wires a req-scoped afterDelete erasure hook on users;
bindAllProduction binds the audit log into consent/DSR; cascade-hard
pseudonymizes the subject's audit entries; the action select accepts
the full AuditAction enum so consent/DSR entries pass validation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 18:08:28 +02:00
parent 7b0c2ea590
commit a2be5d5488
13 changed files with 300 additions and 10 deletions

View File

@@ -8,7 +8,11 @@ import {
withRetentionTombstone,
buildRetentionPurgeTask,
} from "@repo/core-shared/payload";
import { users } from "@repo/auth/cms";
import {
auditLogsCollection,
createReqScopedAuditErasureHook,
} from "@repo/core-audit";
import { users as usersBase } from "@repo/auth/cms";
import { articles } from "@repo/blog/cms";
import { media } from "@repo/media/cms";
import { pages, siteSettings } from "@repo/marketing-pages/cms";
@@ -17,10 +21,28 @@ import { header } from "@repo/navigation/cms";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
// GDPR audit erasure (audit finding A6): when a users row is hard-deleted
// (admin expunge, DSR cascade-hard, retention purge), pseudonymize that
// subject's audit-log entries so the trail keeps its shape without PII linkage.
const users = {
...usersBase,
hooks: {
...usersBase.hooks,
afterDelete: [
...(usersBase.hooks?.afterDelete ?? []),
createReqScopedAuditErasureHook(),
],
},
};
// Collections declaring custom.retention.postDeletion get the soft-delete
// tombstone field (`deletedAt`) so the DSR soft delete can stamp rows and the
// retention purge job can grace-purge them (audit finding A2).
const collections = [users, articles, pages, media].map(withRetentionTombstone);
const collections = [
...[users, articles, pages, media].map(withRetentionTombstone),
// Local audit sink (A6) — required for PayloadAuditLog.record() to work.
auditLogsCollection,
];
export default buildConfig({
editor: lexicalEditor(),