From a5fed1e26ab8ae0041a44053a18652001456c128 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:36:10 +0000 Subject: [PATCH] Implement Phase 2 audit logging Add audit logging for admin settings changes, rate limit config updates, anomaly detection config changes (skipped due to no UI), and version cleanup settings updates. Implement logging via central logAdminAction helper and integrate into AdminSettings, VersionCleanupSettings, and RateLimitAlerts mutations (create, update, delete). --- .../admin/VersionCleanupSettings.tsx | 16 +++++ src/hooks/useAdminSettings.ts | 17 +++++- src/hooks/useRateLimitAlerts.ts | 61 +++++++++++++++++-- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/components/admin/VersionCleanupSettings.tsx b/src/components/admin/VersionCleanupSettings.tsx index e7ee70a5..5ef4dd30 100644 --- a/src/components/admin/VersionCleanupSettings.tsx +++ b/src/components/admin/VersionCleanupSettings.tsx @@ -68,7 +68,15 @@ export function VersionCleanupSettings() { const handleSaveRetention = async () => { setIsSaving(true); + const oldRetentionDays = retentionDays; try { + // Get current value for audit log + const { data: currentSetting } = await supabase + .from('admin_settings') + .select('setting_value') + .eq('setting_key', 'version_retention_days') + .single(); + const { error } = await supabase .from('admin_settings') .update({ setting_value: retentionDays.toString() }) @@ -76,6 +84,14 @@ export function VersionCleanupSettings() { if (error) throw error; + // Log to audit trail + const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); + await logAdminAction('version_cleanup_config_changed', { + setting_key: 'version_retention_days', + old_value: currentSetting?.setting_value, + new_value: retentionDays, + }); + toast({ title: 'Settings Saved', description: 'Retention period updated successfully' diff --git a/src/hooks/useAdminSettings.ts b/src/hooks/useAdminSettings.ts index f77a8f67..c5842217 100644 --- a/src/hooks/useAdminSettings.ts +++ b/src/hooks/useAdminSettings.ts @@ -49,6 +49,10 @@ export function useAdminSettings() { const updateSettingMutation = useMutation({ mutationFn: async ({ key, value }: { key: string; value: unknown }) => { + // Get old value for audit log + const oldSetting = settings?.find(s => s.setting_key === key); + const oldValue = oldSetting?.setting_value; + const { error } = await supabase .from('admin_settings') .update({ @@ -59,10 +63,19 @@ export function useAdminSettings() { .eq('setting_key', key); if (error) throw error; - return { key, value }; + return { key, value, oldValue }; }, - onSuccess: () => { + onSuccess: async (data) => { queryClient.invalidateQueries({ queryKey: ['admin-settings'] }); + + // Log to audit trail + const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); + await logAdminAction('admin_setting_updated', { + setting_key: data.key, + old_value: data.oldValue, + new_value: data.value, + }); + toast({ title: "Setting Updated", description: "The setting has been saved successfully.", diff --git a/src/hooks/useRateLimitAlerts.ts b/src/hooks/useRateLimitAlerts.ts index afdc29cd..68a4673d 100644 --- a/src/hooks/useRateLimitAlerts.ts +++ b/src/hooks/useRateLimitAlerts.ts @@ -80,6 +80,13 @@ export function useUpdateAlertConfig() { return useMutation({ mutationFn: async ({ id, updates }: { id: string; updates: Partial }) => { + // Fetch old config for audit log + const { data: oldConfig } = await supabase + .from('rate_limit_alert_config') + .select('*') + .eq('id', id) + .single(); + const { data, error } = await supabase .from('rate_limit_alert_config') .update(updates) @@ -88,10 +95,23 @@ export function useUpdateAlertConfig() { .single(); if (error) throw error; - return data; + return { data, oldConfig }; }, - onSuccess: () => { + onSuccess: async ({ data, oldConfig }) => { queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] }); + + // Log to audit trail + const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); + await logAdminAction('rate_limit_config_updated', { + config_id: data.id, + metric_type: data.metric_type, + old_threshold: oldConfig?.threshold_value, + new_threshold: data.threshold_value, + old_enabled: oldConfig?.enabled, + new_enabled: data.enabled, + function_name: data.function_name, + }); + toast.success('Alert configuration updated'); }, onError: (error) => { @@ -114,8 +134,20 @@ export function useCreateAlertConfig() { if (error) throw error; return data; }, - onSuccess: () => { + onSuccess: async (data) => { queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] }); + + // Log to audit trail + const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); + await logAdminAction('rate_limit_config_created', { + config_id: data.id, + metric_type: data.metric_type, + threshold_value: data.threshold_value, + time_window_ms: data.time_window_ms, + function_name: data.function_name, + enabled: data.enabled, + }); + toast.success('Alert configuration created'); }, onError: (error) => { @@ -129,15 +161,36 @@ export function useDeleteAlertConfig() { return useMutation({ mutationFn: async (id: string) => { + // Fetch config details before deletion for audit log + const { data: config } = await supabase + .from('rate_limit_alert_config') + .select('*') + .eq('id', id) + .single(); + const { error } = await supabase .from('rate_limit_alert_config') .delete() .eq('id', id); if (error) throw error; + return config; }, - onSuccess: () => { + onSuccess: async (config) => { queryClient.invalidateQueries({ queryKey: ['rateLimitAlertConfigs'] }); + + // Log to audit trail + if (config) { + const { logAdminAction } = await import('@/lib/adminActionAuditHelpers'); + await logAdminAction('rate_limit_config_deleted', { + config_id: config.id, + metric_type: config.metric_type, + threshold_value: config.threshold_value, + time_window_ms: config.time_window_ms, + function_name: config.function_name, + }); + } + toast.success('Alert configuration deleted'); }, onError: (error) => {