Files
accounted/supabase/migrations/20260506150000_protect_document_journal_link.sql
T
Mattsson ce3af4d17e Fix/multiple domain issue (#401)
* feat: enhance invoice management and immutability checks

- Update InvoiceDetailPage to prevent deletion of drafts with assigned invoice numbers, providing user feedback.
- Modify the invoice conversion API to ensure invoice number allocation occurs only after successful item insertion and proforma cancellation.
- Implement structured error responses for invoice deletion, ensuring only drafts without assigned numbers can be deleted.
- Add comprehensive tests for invoice deletion and conversion scenarios, including edge cases for draft invoices.
- Introduce immutability checks in the document management system to prevent unauthorized changes to linked documents.
- Create SQL migration to enforce document metadata immutability, ensuring compliance with accounting regulations.

* fix(invoice): prevent invoice number consumption on PDF render failure

* feat: add document journal entry immutability enforcement for delete_last_voucher RPC

* fix(invoice): implement rollback for orphan invoices on proforma cancel failure

* fix(document): extend immutability trigger to protect journal entry links
2026-05-06 14:08:38 +02:00

65 lines
2.8 KiB
PL/PgSQL

-- Closes BFL 7 kap 2§ retention bypass on document_attachments.
--
-- Problem: enforce_document_metadata_immutability protected file identity
-- columns but not journal_entry_id / journal_entry_line_id. block_document_deletion
-- only fires when OLD.journal_entry_id IS NOT NULL. An attacker could therefore
-- run UPDATE document_attachments SET journal_entry_id = NULL WHERE id = X,
-- followed by DELETE FROM document_attachments WHERE id = X, severing
-- räkenskapsinformation from the audit trail in violation of BFL 7 kap 2§.
--
-- Fix: extend the existing immutability trigger to also reject changes to
-- journal_entry_id and journal_entry_line_id when the document is currently
-- linked to a posted or reversed journal entry. Honour the existing
-- gnubok.allow_delete transaction-local bypass that delete_last_voucher uses
-- when intentionally tearing down a voucher.
CREATE OR REPLACE FUNCTION public.enforce_document_metadata_immutability()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
v_entry_status text;
BEGIN
IF current_setting('gnubok.allow_delete', true) = 'true' THEN
RETURN NEW;
END IF;
IF OLD.journal_entry_id IS NULL THEN
RETURN NEW;
END IF;
SELECT status INTO v_entry_status
FROM public.journal_entries
WHERE id = OLD.journal_entry_id;
IF v_entry_status IS NULL OR v_entry_status NOT IN ('posted', 'reversed') THEN
RETURN NEW;
END IF;
IF NEW.file_name IS DISTINCT FROM OLD.file_name
OR NEW.storage_path IS DISTINCT FROM OLD.storage_path
OR NEW.file_size_bytes IS DISTINCT FROM OLD.file_size_bytes
OR NEW.mime_type IS DISTINCT FROM OLD.mime_type
OR NEW.sha256_hash IS DISTINCT FROM OLD.sha256_hash
OR NEW.upload_source IS DISTINCT FROM OLD.upload_source
OR NEW.digitization_date IS DISTINCT FROM OLD.digitization_date
OR NEW.uploaded_by IS DISTINCT FROM OLD.uploaded_by
OR NEW.version IS DISTINCT FROM OLD.version
OR NEW.original_id IS DISTINCT FROM OLD.original_id
OR NEW.is_current_version IS DISTINCT FROM OLD.is_current_version
OR NEW.journal_entry_id IS DISTINCT FROM OLD.journal_entry_id
OR NEW.journal_entry_line_id IS DISTINCT FROM OLD.journal_entry_line_id
THEN
INSERT INTO public.audit_log (user_id, company_id, action, table_name, record_id, description)
VALUES (OLD.user_id, OLD.company_id, 'SECURITY_EVENT', 'document_attachments', OLD.id,
'Blocked metadata or link modification of document linked to ' || v_entry_status || ' entry ' || OLD.journal_entry_id);
RAISE EXCEPTION 'Cannot modify metadata or journal entry link of document linked to a % journal entry (BFL 7 kap)', v_entry_status;
END IF;
RETURN NEW;
END;
$function$;