114 lines
3.9 KiB
PL/PgSQL
114 lines
3.9 KiB
PL/PgSQL
-- supabase/migrations/20260709000009_security_fixes.sql
|
|
-- Security fixes from IMS Phase 0 final code review
|
|
|
|
-- =============================================================================
|
|
-- 1. CRITICAL: Drop and recreate users_update_own with WITH CHECK to prevent
|
|
-- role self-escalation (any user setting their own role to admin)
|
|
-- =============================================================================
|
|
DROP POLICY IF EXISTS "users_update_own" ON users;
|
|
|
|
CREATE POLICY "users_update_own" ON users
|
|
FOR UPDATE
|
|
USING (id = auth.uid())
|
|
WITH CHECK (
|
|
id = auth.uid()
|
|
AND role = (SELECT role FROM public.users WHERE id = auth.uid())
|
|
);
|
|
|
|
-- =============================================================================
|
|
-- 2. IMPORTANT: SECURITY DEFINER audit writer function — app code calls this
|
|
-- instead of direct INSERT on audit_log
|
|
-- =============================================================================
|
|
CREATE OR REPLACE FUNCTION public.write_audit_log(
|
|
p_table_name TEXT,
|
|
p_record_id UUID,
|
|
p_action TEXT,
|
|
p_old_value JSONB DEFAULT NULL,
|
|
p_new_value JSONB DEFAULT NULL
|
|
)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
BEGIN
|
|
INSERT INTO public.audit_log (table_name, record_id, action, changed_by, old_value, new_value)
|
|
VALUES (p_table_name, p_record_id, p_action, auth.uid(), p_old_value, p_new_value);
|
|
END;
|
|
$$;
|
|
|
|
-- =============================================================================
|
|
-- 3. IMPORTANT: notifications_log INSERT policy for elevated roles
|
|
-- =============================================================================
|
|
CREATE POLICY "notifications_insert_elevated" ON notifications_log
|
|
FOR INSERT
|
|
WITH CHECK (auth_user_role() IN ('hse', 'admin', 'supervisor'));
|
|
|
|
-- =============================================================================
|
|
-- 4. IMPORTANT: Recreate generate_incident_reference trigger function with
|
|
-- SECURITY DEFINER and SET search_path to prevent search_path injection
|
|
-- =============================================================================
|
|
CREATE OR REPLACE FUNCTION public.generate_incident_reference()
|
|
RETURNS TRIGGER
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
v_site_code TEXT;
|
|
v_month TEXT;
|
|
v_seq INT;
|
|
BEGIN
|
|
SELECT UPPER(REGEXP_REPLACE(SUBSTRING(name, 1, 6), '[^A-Za-z0-9]', '', 'g'))
|
|
INTO v_site_code
|
|
FROM sites
|
|
WHERE id = NEW.site_id;
|
|
|
|
v_month := TO_CHAR(NEW.reported_at, 'YYYYMM');
|
|
|
|
SELECT COUNT(*) + 1
|
|
INTO v_seq
|
|
FROM incidents
|
|
WHERE site_id = NEW.site_id
|
|
AND TO_CHAR(reported_at, 'YYYYMM') = v_month;
|
|
|
|
NEW.reference_no := COALESCE(v_site_code, 'UNK') || '-' || v_month || '-' || LPAD(v_seq::TEXT, 4, '0');
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
-- =============================================================================
|
|
-- 5. IMPORTANT: Recreate helper functions with SET search_path to prevent
|
|
-- search_path hijacking attacks
|
|
-- =============================================================================
|
|
CREATE OR REPLACE FUNCTION public.auth_user_role()
|
|
RETURNS user_role
|
|
LANGUAGE sql
|
|
SECURITY DEFINER
|
|
STABLE
|
|
SET search_path = public
|
|
AS $$ SELECT role FROM public.users WHERE id = auth.uid() $$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.auth_user_site_id()
|
|
RETURNS UUID
|
|
LANGUAGE sql
|
|
SECURITY DEFINER
|
|
STABLE
|
|
SET search_path = public
|
|
AS $$ SELECT site_id FROM public.users WHERE id = auth.uid() $$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.auth_user_department()
|
|
RETURNS TEXT
|
|
LANGUAGE sql
|
|
SECURITY DEFINER
|
|
STABLE
|
|
SET search_path = public
|
|
AS $$ SELECT department FROM public.users WHERE id = auth.uid() $$;
|
|
|
|
-- =============================================================================
|
|
-- 6. IMPORTANT: Add UNIQUE constraint to dosh_reports to prevent duplicate
|
|
-- form submissions for the same incident
|
|
-- =============================================================================
|
|
ALTER TABLE dosh_reports
|
|
ADD CONSTRAINT dosh_reports_incident_form_uq UNIQUE (incident_id, form_type);
|