fix: BankID fallback, bank import fixes, account deletion (#216)
* fix: BankID graceful fallback, bank import fixes, and account deletion - BankID: surface service_unavailable state in login/register with password fallback messaging; structured error codes in tic extension; poll failure counter in BankIdAuth avoids infinite retry when TIC API is down. - Transactions: allow deleting unbooked bank-synced and imported transactions (only posted entries remain protected); detect reconnect duplicates by also checking unbooked bank-synced rows in content-based dedup. - Enable Banking: key external_id by account iban/uid instead of connection id so reconnects don't create duplicates. - Bank import: detect SEB privatbanken CSV variant (Bokföringsdatum / Valutadatum headers) via regex. - Banking settings: replace full-screen sync loader with toast notifications. - delete_user_account: raise statement_timeout, pre-clear NO ACTION FK references, and disable audit/immutability triggers during CASCADE. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review — ingest dedup scoping and migration EXCEPTION handler - ingest: split buildExistingTransactionMap into two maps. Booked rows (any source) remain consumed by any incoming raw transaction, but unbooked enable_banking slots are only consumed when the incoming raw transaction is also enable_banking. This preserves reconnect dedup while preventing false positives where a pending bank-synced row silently blocks a legitimately separate CSV row with the same date/amount. - delete_user_account: add EXCEPTION WHEN OTHERS handler that re-enables every legally required enforcement trigger before re-raising. Postgres transactional DDL already rolls back on abort, but the explicit guard makes the intent unambiguous and covers sub-transaction edge cases so enforcement triggers are never left disabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
aa405b9a74
commit
bca00cc5bd
@@ -0,0 +1,116 @@
|
||||
-- Fix delete_user_account: timeout + cascade blockers.
|
||||
--
|
||||
-- Problem 1: ALTER TABLE DDL requires ACCESS EXCLUSIVE locks. With the
|
||||
-- authenticated role's 8s statement_timeout, any concurrent read causes
|
||||
-- error 57014. Fixed via function-level SET statement_timeout = '60s'.
|
||||
--
|
||||
-- Problem 2: Several NO ACTION FKs block the CASCADE chain from
|
||||
-- auth.users → companies → children:
|
||||
-- audit_log.company_id → companies (NO ACTION)
|
||||
-- fiscal_periods.previous_period_id → fiscal_periods (NO ACTION, self-ref)
|
||||
-- fiscal_periods.closing_entry_id → journal_entries (NO ACTION)
|
||||
-- fiscal_periods.opening_balance_entry_id → journal_entries (NO ACTION)
|
||||
-- Fixed by explicitly clearing these before the CASCADE fires.
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.delete_user_account(target_user_id uuid)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
SET statement_timeout = '60s'
|
||||
SET lock_timeout = '10s'
|
||||
AS $$
|
||||
DECLARE
|
||||
v_company_ids uuid[];
|
||||
BEGIN
|
||||
-- Only allow users to delete their own account
|
||||
IF auth.uid() IS DISTINCT FROM target_user_id THEN
|
||||
RAISE EXCEPTION 'Can only delete your own account';
|
||||
END IF;
|
||||
|
||||
-- Collect companies owned by this user (CASCADE will delete these)
|
||||
SELECT array_agg(id) INTO v_company_ids
|
||||
FROM public.companies
|
||||
WHERE created_by = target_user_id;
|
||||
|
||||
-- Clear active_company_id to avoid FK conflicts during CASCADE
|
||||
DELETE FROM public.user_preferences WHERE user_id = target_user_id;
|
||||
|
||||
-- Explicitly delete from extension_data (missing DELETE RLS policy
|
||||
-- causes CASCADE from auth.users to fail even with ON DELETE CASCADE)
|
||||
DELETE FROM public.extension_data WHERE user_id = target_user_id;
|
||||
|
||||
-- Disable BEFORE DELETE triggers that block deletion
|
||||
ALTER TABLE audit_log DISABLE TRIGGER audit_log_no_delete;
|
||||
ALTER TABLE payment_match_log DISABLE TRIGGER payment_match_log_no_delete;
|
||||
ALTER TABLE document_attachments DISABLE TRIGGER block_document_deletion;
|
||||
ALTER TABLE journal_entries DISABLE TRIGGER enforce_journal_entry_immutability;
|
||||
ALTER TABLE journal_entries DISABLE TRIGGER enforce_retention_journal_entries;
|
||||
ALTER TABLE journal_entry_lines DISABLE TRIGGER enforce_journal_entry_line_immutability;
|
||||
|
||||
-- Disable AFTER DELETE audit triggers (they INSERT into audit_log during
|
||||
-- CASCADE, which would create orphaned rows after the user is gone)
|
||||
ALTER TABLE api_keys DISABLE TRIGGER audit_api_keys;
|
||||
ALTER TABLE chart_of_accounts DISABLE TRIGGER audit_chart_of_accounts;
|
||||
ALTER TABLE company_settings DISABLE TRIGGER audit_company_settings;
|
||||
ALTER TABLE document_attachments DISABLE TRIGGER audit_document_attachments;
|
||||
ALTER TABLE extension_data DISABLE TRIGGER audit_extension_data;
|
||||
ALTER TABLE fiscal_periods DISABLE TRIGGER audit_fiscal_periods;
|
||||
ALTER TABLE journal_entries DISABLE TRIGGER audit_journal_entries;
|
||||
ALTER TABLE supplier_invoices DISABLE TRIGGER audit_supplier_invoices;
|
||||
|
||||
-- Clear NO ACTION FK references that block the CASCADE chain
|
||||
IF v_company_ids IS NOT NULL THEN
|
||||
-- audit_log.company_id → companies (NO ACTION)
|
||||
DELETE FROM public.audit_log
|
||||
WHERE company_id = ANY(v_company_ids);
|
||||
|
||||
-- fiscal_periods self-ref and cross-refs to journal_entries (NO ACTION)
|
||||
UPDATE public.fiscal_periods
|
||||
SET previous_period_id = NULL,
|
||||
closing_entry_id = NULL,
|
||||
opening_balance_entry_id = NULL
|
||||
WHERE company_id = ANY(v_company_ids);
|
||||
END IF;
|
||||
|
||||
-- Delete from auth.users — ON DELETE CASCADE handles all public tables
|
||||
DELETE FROM auth.users WHERE id = target_user_id;
|
||||
|
||||
-- Re-enable all triggers
|
||||
ALTER TABLE audit_log ENABLE TRIGGER audit_log_no_delete;
|
||||
ALTER TABLE payment_match_log ENABLE TRIGGER payment_match_log_no_delete;
|
||||
ALTER TABLE document_attachments ENABLE TRIGGER block_document_deletion;
|
||||
ALTER TABLE journal_entries ENABLE TRIGGER enforce_journal_entry_immutability;
|
||||
ALTER TABLE journal_entries ENABLE TRIGGER enforce_retention_journal_entries;
|
||||
ALTER TABLE journal_entry_lines ENABLE TRIGGER enforce_journal_entry_line_immutability;
|
||||
ALTER TABLE api_keys ENABLE TRIGGER audit_api_keys;
|
||||
ALTER TABLE chart_of_accounts ENABLE TRIGGER audit_chart_of_accounts;
|
||||
ALTER TABLE company_settings ENABLE TRIGGER audit_company_settings;
|
||||
ALTER TABLE document_attachments ENABLE TRIGGER audit_document_attachments;
|
||||
ALTER TABLE extension_data ENABLE TRIGGER audit_extension_data;
|
||||
ALTER TABLE fiscal_periods ENABLE TRIGGER audit_fiscal_periods;
|
||||
ALTER TABLE journal_entries ENABLE TRIGGER audit_journal_entries;
|
||||
ALTER TABLE supplier_invoices ENABLE TRIGGER audit_supplier_invoices;
|
||||
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
-- PostgreSQL transactional DDL already rolls back the DISABLE TRIGGER
|
||||
-- statements if the function aborts, but re-enable explicitly as a
|
||||
-- defensive guard against sub-transaction edge cases so enforcement
|
||||
-- triggers are never left disabled on the live tables.
|
||||
BEGIN ALTER TABLE audit_log ENABLE TRIGGER audit_log_no_delete; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE payment_match_log ENABLE TRIGGER payment_match_log_no_delete; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE document_attachments ENABLE TRIGGER block_document_deletion; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER enforce_journal_entry_immutability; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER enforce_retention_journal_entries; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE journal_entry_lines ENABLE TRIGGER enforce_journal_entry_line_immutability; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE api_keys ENABLE TRIGGER audit_api_keys; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE chart_of_accounts ENABLE TRIGGER audit_chart_of_accounts; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE company_settings ENABLE TRIGGER audit_company_settings; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE document_attachments ENABLE TRIGGER audit_document_attachments; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE extension_data ENABLE TRIGGER audit_extension_data; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE fiscal_periods ENABLE TRIGGER audit_fiscal_periods; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE journal_entries ENABLE TRIGGER audit_journal_entries; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
BEGIN ALTER TABLE supplier_invoices ENABLE TRIGGER audit_supplier_invoices; EXCEPTION WHEN OTHERS THEN NULL; END;
|
||||
RAISE;
|
||||
END;
|
||||
$$;
|
||||
Reference in New Issue
Block a user