diff --git a/ddl/99-rls-erp-modules.sql b/ddl/99-rls-erp-modules.sql new file mode 100644 index 0000000..a176088 --- /dev/null +++ b/ddl/99-rls-erp-modules.sql @@ -0,0 +1,295 @@ +-- ============================================================= +-- ARCHIVO: 99-rls-erp-modules.sql +-- DESCRIPCION: Row Level Security (RLS) Policies para modulos ERP +-- VERSION: 1.0.0 +-- PROYECTO: ERP-Core V2 +-- FECHA: 2026-01-24 +-- CREADO POR: TASK-028 Remediacion RLS +-- ============================================================= + +-- ===================================================== +-- PRODUCTS MODULE - RLS Policies +-- ===================================================== + +-- product_categories +ALTER TABLE products.product_categories ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_product_categories ON products.product_categories; +CREATE POLICY tenant_isolation_product_categories ON products.product_categories + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- products +ALTER TABLE products.products ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_products ON products.products; +CREATE POLICY tenant_isolation_products ON products.products + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- INVENTORY MODULE - RLS Policies +-- ===================================================== + +-- warehouses +ALTER TABLE inventory.warehouses ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_warehouses ON inventory.warehouses; +CREATE POLICY tenant_isolation_warehouses ON inventory.warehouses + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- stock_levels +ALTER TABLE inventory.stock_levels ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_stock_levels ON inventory.stock_levels; +CREATE POLICY tenant_isolation_stock_levels ON inventory.stock_levels + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- stock_movements +ALTER TABLE inventory.stock_movements ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_stock_movements ON inventory.stock_movements; +CREATE POLICY tenant_isolation_stock_movements ON inventory.stock_movements + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- inventory_counts +ALTER TABLE inventory.inventory_counts ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_inventory_counts ON inventory.inventory_counts; +CREATE POLICY tenant_isolation_inventory_counts ON inventory.inventory_counts + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- transfer_orders +ALTER TABLE inventory.transfer_orders ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_transfer_orders ON inventory.transfer_orders; +CREATE POLICY tenant_isolation_transfer_orders ON inventory.transfer_orders + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- lots +ALTER TABLE inventory.lots ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_lots ON inventory.lots; +CREATE POLICY tenant_isolation_lots ON inventory.lots + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- pickings +ALTER TABLE inventory.pickings ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_pickings ON inventory.pickings; +CREATE POLICY tenant_isolation_pickings ON inventory.pickings + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- PARTNERS MODULE - RLS Policies +-- ===================================================== + +-- partners +ALTER TABLE partners.partners ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_partners ON partners.partners; +CREATE POLICY tenant_isolation_partners ON partners.partners + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- partner_segments +ALTER TABLE partners.partner_segments ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_partner_segments ON partners.partner_segments; +CREATE POLICY tenant_isolation_partner_segments ON partners.partner_segments + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- SALES MODULE - RLS Policies +-- ===================================================== + +-- quotations +ALTER TABLE sales.quotations ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_quotations ON sales.quotations; +CREATE POLICY tenant_isolation_quotations ON sales.quotations + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- sales_orders +ALTER TABLE sales.sales_orders ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_sales_orders ON sales.sales_orders; +CREATE POLICY tenant_isolation_sales_orders ON sales.sales_orders + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- PURCHASES MODULE - RLS Policies +-- ===================================================== + +-- purchase_orders +ALTER TABLE purchases.purchase_orders ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_purchase_orders ON purchases.purchase_orders; +CREATE POLICY tenant_isolation_purchase_orders ON purchases.purchase_orders + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- purchase_receipts +ALTER TABLE purchases.purchase_receipts ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_purchase_receipts ON purchases.purchase_receipts; +CREATE POLICY tenant_isolation_purchase_receipts ON purchases.purchase_receipts + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- BILLING MODULE - RLS Policies +-- ===================================================== + +-- invoices (billing schema) +ALTER TABLE billing.invoices ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_billing_invoices ON billing.invoices; +CREATE POLICY tenant_isolation_billing_invoices ON billing.invoices + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- payments (billing schema) +ALTER TABLE billing.payments ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_billing_payments ON billing.payments; +CREATE POLICY tenant_isolation_billing_payments ON billing.payments + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- FINANCIAL MODULE - RLS Policies +-- ===================================================== + +-- accounts +ALTER TABLE financial.accounts ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_accounts ON financial.accounts; +CREATE POLICY tenant_isolation_accounts ON financial.accounts + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- account_mappings +ALTER TABLE financial.account_mappings ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_account_mappings ON financial.account_mappings; +CREATE POLICY tenant_isolation_account_mappings ON financial.account_mappings + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- fiscal_years +ALTER TABLE financial.fiscal_years ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_fiscal_years ON financial.fiscal_years; +CREATE POLICY tenant_isolation_fiscal_years ON financial.fiscal_years + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- fiscal_periods +ALTER TABLE financial.fiscal_periods ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_fiscal_periods ON financial.fiscal_periods; +CREATE POLICY tenant_isolation_fiscal_periods ON financial.fiscal_periods + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- journals +ALTER TABLE financial.journals ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_journals ON financial.journals; +CREATE POLICY tenant_isolation_journals ON financial.journals + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- journal_entries +ALTER TABLE financial.journal_entries ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_journal_entries ON financial.journal_entries; +CREATE POLICY tenant_isolation_journal_entries ON financial.journal_entries + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- journal_entry_lines +ALTER TABLE financial.journal_entry_lines ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_journal_entry_lines ON financial.journal_entry_lines; +CREATE POLICY tenant_isolation_journal_entry_lines ON financial.journal_entry_lines + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- invoices (financial schema) +ALTER TABLE financial.invoices ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_financial_invoices ON financial.invoices; +CREATE POLICY tenant_isolation_financial_invoices ON financial.invoices + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- invoice_lines (financial schema) +ALTER TABLE financial.invoice_lines ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_invoice_lines ON financial.invoice_lines; +CREATE POLICY tenant_isolation_invoice_lines ON financial.invoice_lines + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- payments (financial schema) +ALTER TABLE financial.payments ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_financial_payments ON financial.payments; +CREATE POLICY tenant_isolation_financial_payments ON financial.payments + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- taxes +ALTER TABLE financial.taxes ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_taxes ON financial.taxes; +CREATE POLICY tenant_isolation_taxes ON financial.taxes + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- tax_groups +ALTER TABLE financial.tax_groups ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_tax_groups ON financial.tax_groups; +CREATE POLICY tenant_isolation_tax_groups ON financial.tax_groups + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- bank_statements +ALTER TABLE financial.bank_statements ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_bank_statements ON financial.bank_statements; +CREATE POLICY tenant_isolation_bank_statements ON financial.bank_statements + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- bank_statement_lines +ALTER TABLE financial.bank_statement_lines ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_bank_statement_lines ON financial.bank_statement_lines; +CREATE POLICY tenant_isolation_bank_statement_lines ON financial.bank_statement_lines + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- bank_reconciliation_rules +ALTER TABLE financial.bank_reconciliation_rules ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_bank_reconciliation_rules ON financial.bank_reconciliation_rules; +CREATE POLICY tenant_isolation_bank_reconciliation_rules ON financial.bank_reconciliation_rules + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- PROJECTS MODULE - RLS Policies +-- ===================================================== + +-- projects +ALTER TABLE projects.projects ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_projects ON projects.projects; +CREATE POLICY tenant_isolation_projects ON projects.projects + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- project_stages +ALTER TABLE projects.project_stages ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_project_stages ON projects.project_stages; +CREATE POLICY tenant_isolation_project_stages ON projects.project_stages + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- tasks +ALTER TABLE projects.tasks ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_tasks ON projects.tasks; +CREATE POLICY tenant_isolation_tasks ON projects.tasks + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- milestones +ALTER TABLE projects.milestones ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_milestones ON projects.milestones; +CREATE POLICY tenant_isolation_milestones ON projects.milestones + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- timesheets +ALTER TABLE projects.timesheets ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_timesheets ON projects.timesheets; +CREATE POLICY tenant_isolation_timesheets ON projects.timesheets + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- project_members +ALTER TABLE projects.project_members ENABLE ROW LEVEL SECURITY; +DROP POLICY IF EXISTS tenant_isolation_project_members ON projects.project_members; +CREATE POLICY tenant_isolation_project_members ON projects.project_members + USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid); + +-- ===================================================== +-- COMENTARIOS +-- ===================================================== +COMMENT ON POLICY tenant_isolation_products ON products.products IS 'Aislamiento multi-tenant para productos'; +COMMENT ON POLICY tenant_isolation_warehouses ON inventory.warehouses IS 'Aislamiento multi-tenant para almacenes'; +COMMENT ON POLICY tenant_isolation_partners ON partners.partners IS 'Aislamiento multi-tenant para socios comerciales'; +COMMENT ON POLICY tenant_isolation_journal_entries ON financial.journal_entries IS 'Aislamiento multi-tenant para polizas contables'; +COMMENT ON POLICY tenant_isolation_projects ON projects.projects IS 'Aislamiento multi-tenant para proyectos'; + +-- ===================================================== +-- NOTAS DE IMPLEMENTACION +-- ===================================================== +-- +-- 1. Las tablas hijas (ej: invoice_lines, order_items) heredan seguridad +-- via CASCADE DELETE en sus FK a tablas padre +-- +-- 2. Tablas de catalogo sistema (account_types) no tienen tenant_id +-- porque son compartidas entre todos los tenants +-- +-- 3. El setting 'app.current_tenant_id' debe configurarse en cada +-- sesion de BD via: SET app.current_tenant_id = 'uuid-here'; +-- +-- 4. El backend debe configurar este setting antes de cada query +-- Ver: backend/src/middleware/tenant.middleware.ts +-- +-- =====================================================