From 174b3644a9b0dcc4609b9261ec3782d0e3c08a3a Mon Sep 17 00:00:00 2001 From: Sagnik Ghosh Date: Mon, 4 May 2026 10:54:14 +0530 Subject: [PATCH] Implement audit log hardening with retention, export limits, and date filtering Generated-By: PostHog Code Task-Id: 724f7f1d-c542-4cce-9332-eda23b8b7849 --- .env.example | 4 + app/(dashboard)/settings/audit-log/page.tsx | 71 +- app/api/internal/audit-logs/route.ts | 84 ++- app/api/internal/suppressions/[id]/route.ts | 13 +- app/api/internal/suppressions/route.ts | 9 + .../suppressions/upload/confirm/route.ts | 14 + app/api/v1/suppressions/[id]/route.ts | 25 +- app/api/v1/suppressions/bulk/route.ts | 57 +- app/api/v1/suppressions/route.ts | 97 +-- app/api/webhooks/resend/route.ts | 32 +- app/api/webhooks/ses/route.ts | 23 +- app/unsubscribe/[token]/page.tsx | 13 + .../migrations/0002_audit_log_hardening.sql | 50 ++ drizzle/migrations/meta/0002_snapshot.json | 681 ++++++++++++++++++ drizzle/migrations/meta/_journal.json | 9 +- lib/audit/index.ts | 10 + lib/queue/index.ts | 3 +- lib/suppressions/index.ts | 9 +- worker.ts | 66 +- 19 files changed, 1156 insertions(+), 114 deletions(-) create mode 100644 drizzle/migrations/0002_audit_log_hardening.sql create mode 100644 drizzle/migrations/meta/0002_snapshot.json diff --git a/.env.example b/.env.example index 1e7be84..02f867c 100644 --- a/.env.example +++ b/.env.example @@ -25,6 +25,10 @@ ENCRYPTION_KEY= # Generate with: openssl rand -hex 32 # Worker WORKER_CONCURRENCY=5 +# Audit log +AUDIT_LOG_RETENTION_DAYS=365 # Daily purge job deletes entries older than this. Set to 0 to disable. +AUDIT_LOG_EXPORT_MAX_ROWS=100000 # Cap for CSV exports; tighten the date range if exceeded. + # Webhooks (optional, for bounce/complaint tracking) # RESEND_WEBHOOK_SECRET= # From Resend dashboard diff --git a/app/(dashboard)/settings/audit-log/page.tsx b/app/(dashboard)/settings/audit-log/page.tsx index 45159e9..9448842 100644 --- a/app/(dashboard)/settings/audit-log/page.tsx +++ b/app/(dashboard)/settings/audit-log/page.tsx @@ -48,17 +48,38 @@ export default function AuditLogPage() { const [actorType, setActorType] = useState("") const [resourceType, setResourceType] = useState("") const [actionFilter, setActionFilter] = useState("") + const [fromDate, setFromDate] = useState("") + const [toDate, setToDate] = useState("") const [page, setPage] = useState(1) + function buildFilterParams(): URLSearchParams { + const params = new URLSearchParams() + if (actorType) params.set("actorType", actorType) + if (resourceType) params.set("resourceType", resourceType) + if (actionFilter) params.set("action", actionFilter) + if (fromDate) params.set("from", new Date(fromDate).toISOString()) + if (toDate) { + const to = new Date(toDate) + to.setHours(23, 59, 59, 999) + params.set("to", to.toISOString()) + } + return params + } + + function handleExport() { + const params = buildFilterParams() + params.set("format", "csv") + window.location.href = `/api/internal/audit-logs?${params.toString()}` + } + useEffect(() => { let cancelled = false async function fetchLogs() { setLoading(true) try { - const params = new URLSearchParams({ page: String(page), limit: "50" }) - if (actorType) params.set("actorType", actorType) - if (resourceType) params.set("resourceType", resourceType) - if (actionFilter) params.set("action", actionFilter) + const params = buildFilterParams() + params.set("page", String(page)) + params.set("limit", "50") const res = await fetch(`/api/internal/audit-logs?${params.toString()}`) if (!res.ok || cancelled) return const json = await res.json() @@ -72,17 +93,23 @@ export default function AuditLogPage() { } fetchLogs() return () => { cancelled = true } - }, [page, actorType, resourceType, actionFilter]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, actorType, resourceType, actionFilter, fromDate, toDate]) const totalPages = Math.max(1, Math.ceil(meta.total / meta.limit)) return (
-
-

Audit Log

-

- Append-only record of every state-changing action. -

+
+
+

Audit Log

+

+ Append-only record of every state-changing action. +

+
+
@@ -123,7 +150,27 @@ export default function AuditLogPage() { className="w-56 h-9" />
- {(actorType || resourceType || actionFilter) && ( +
+ + { setFromDate(e.target.value); setPage(1) }} + className="w-40 h-9" + /> +
+
+ + { setToDate(e.target.value); setPage(1) }} + className="w-40 h-9" + /> +
+ {(actorType || resourceType || actionFilter || fromDate || toDate) && (