60 lines
2.2 KiB
PL/PgSQL
60 lines
2.2 KiB
PL/PgSQL
-- Migration 14: Audit Log
|
|
-- Append-only audit log for all compliance-critical mutations
|
|
|
|
-- =============================================================================
|
|
-- 1. audit_log table
|
|
-- =============================================================================
|
|
CREATE TABLE public.audit_log (
|
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id uuid NOT NULL, -- No FK cascade: survives user deletion
|
|
action text NOT NULL CHECK (action IN (
|
|
'INSERT', 'UPDATE', 'DELETE',
|
|
'COMMIT', 'REVERSE', 'CORRECT',
|
|
'LOCK_PERIOD', 'CLOSE_PERIOD',
|
|
'DOCUMENT_DELETE_BLOCKED', 'RETENTION_BLOCK',
|
|
'SECURITY_EVENT'
|
|
)),
|
|
table_name text,
|
|
record_id uuid,
|
|
actor_id uuid,
|
|
old_state jsonb,
|
|
new_state jsonb,
|
|
description text,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
-- Intentionally NO updated_at: append-only
|
|
);
|
|
|
|
ALTER TABLE public.audit_log ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users can only read their own audit log entries
|
|
CREATE POLICY "audit_log_select" ON public.audit_log
|
|
FOR SELECT USING (auth.uid() = user_id);
|
|
|
|
-- No INSERT policy for normal users -- audit log is written by SECURITY DEFINER triggers
|
|
-- No UPDATE or DELETE policies -- immutability enforced by triggers below
|
|
|
|
CREATE INDEX idx_audit_log_user_id ON public.audit_log (user_id);
|
|
CREATE INDEX idx_audit_log_table_record ON public.audit_log (table_name, record_id);
|
|
CREATE INDEX idx_audit_log_action ON public.audit_log (action);
|
|
CREATE INDEX idx_audit_log_created_at ON public.audit_log (created_at);
|
|
|
|
-- =============================================================================
|
|
-- 2. Immutability triggers: block UPDATE and DELETE on audit_log
|
|
-- =============================================================================
|
|
CREATE OR REPLACE FUNCTION public.audit_log_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION 'Audit log entries cannot be modified or deleted';
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER audit_log_no_update
|
|
BEFORE UPDATE ON public.audit_log
|
|
FOR EACH ROW EXECUTE FUNCTION public.audit_log_immutable();
|
|
|
|
CREATE TRIGGER audit_log_no_delete
|
|
BEFORE DELETE ON public.audit_log
|
|
FOR EACH ROW EXECUTE FUNCTION public.audit_log_immutable();
|