-- ============================================================================ -- EN: PostgreSQL Row-Level Security (RLS) Policies for Multi-Tenant Isolation -- VI: PostgreSQL Row-Level Security (RLS) Policies cho Cách Ly Đa Tenant -- ============================================================================ -- -- EN: These policies provide defense-in-depth for tenant isolation. -- Primary mechanism: EF Core global query filters (application level). -- Secondary mechanism: PostgreSQL RLS (database level). -- -- The application sets session variables via: -- SET LOCAL app.current_shop_id = ''; -- SET LOCAL app.current_merchant_id = ''; -- -- RLS policies use current_setting() to read these session variables. -- When no session variable is set, the policy defaults to allowing access -- (to support migrations, health checks, and admin operations). -- -- VI: Các policies này cung cấp phòng thủ theo chiều sâu cho cách ly tenant. -- Cơ chế chính: EF Core global query filters (cấp ứng dụng). -- Cơ chế phụ: PostgreSQL RLS (cấp cơ sở dữ liệu). -- ============================================================================ -- EN: Helper function to safely get current_setting with fallback -- VI: Hàm trợ giúp để lấy current_setting an toàn với giá trị mặc định CREATE OR REPLACE FUNCTION get_current_shop_id() RETURNS uuid AS $$ BEGIN RETURN current_setting('app.current_shop_id', true)::uuid; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; CREATE OR REPLACE FUNCTION get_current_merchant_id() RETURNS uuid AS $$ BEGIN RETURN current_setting('app.current_merchant_id', true)::uuid; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; CREATE OR REPLACE FUNCTION get_current_user_id() RETURNS uuid AS $$ BEGIN RETURN current_setting('app.current_user_id', true)::uuid; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$ LANGUAGE plpgsql STABLE; -- ============================================================================ -- ORDER SERVICE DATABASE (order_service_db) -- ============================================================================ -- EN: Connect to order service database before running these -- VI: Kết nối đến database order service trước khi chạy các lệnh này -- \connect order_service_db; -- EN: Enable RLS on orders table -- VI: Bật RLS trên bảng orders ALTER TABLE orders ENABLE ROW LEVEL SECURITY; -- EN: Policy: users can only see orders belonging to their shop -- VI: Policy: users chỉ thấy orders thuộc shop của họ CREATE POLICY tenant_isolation_orders ON orders USING ( get_current_shop_id() IS NULL -- EN: No tenant set = allow (admin/migration) OR shop_id = get_current_shop_id() ); -- EN: Enable RLS on order_items table -- VI: Bật RLS trên bảng order_items ALTER TABLE order_items ENABLE ROW LEVEL SECURITY; -- EN: Policy: order_items are isolated via their parent order's shop_id -- VI: Policy: order_items được cách ly qua shop_id của order cha CREATE POLICY tenant_isolation_order_items ON order_items USING ( get_current_shop_id() IS NULL OR order_id IN (SELECT id FROM orders WHERE shop_id = get_current_shop_id()) ); -- ============================================================================ -- FNB ENGINE DATABASE (fnb_engine_db) -- ============================================================================ -- \connect fnb_engine_db; -- EN: Tables ALTER TABLE tables ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_tables ON tables USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); -- EN: Sessions ALTER TABLE sessions ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_sessions ON sessions USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); -- EN: Reservations ALTER TABLE reservations ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_reservations ON reservations USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); -- EN: Kitchen tickets (isolated via session's shop) ALTER TABLE kitchen_tickets ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_kitchen_tickets ON kitchen_tickets USING ( get_current_shop_id() IS NULL OR session_id IN (SELECT id FROM sessions WHERE shop_id = get_current_shop_id()) ); -- ============================================================================ -- INVENTORY SERVICE DATABASE (inventory_service_db) -- ============================================================================ -- \connect inventory_service_db; ALTER TABLE inventory_items ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_inventory_items ON inventory_items USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); -- EN: Inventory transactions are isolated via their parent item's shop -- VI: Inventory transactions được cách ly qua shop của item cha ALTER TABLE inventory_transactions ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_inventory_transactions ON inventory_transactions USING ( get_current_shop_id() IS NULL OR inventory_item_id IN (SELECT id FROM inventory_items WHERE shop_id = get_current_shop_id()) ); -- ============================================================================ -- CATALOG SERVICE DATABASE (catalog_service_db) -- ============================================================================ -- \connect catalog_service_db; ALTER TABLE products ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_products ON products USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); ALTER TABLE categories ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_categories ON categories USING ( get_current_shop_id() IS NULL OR shop_id = get_current_shop_id() ); -- ============================================================================ -- WALLET SERVICE DATABASE (wallet_service_db) -- ============================================================================ -- EN: Wallet uses user_id as tenant key (not shop_id) -- VI: Wallet sử dụng user_id làm tenant key (không phải shop_id) -- \connect wallet_service_db; ALTER TABLE wallets ENABLE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation_wallets ON wallets USING ( get_current_user_id() IS NULL OR user_id = get_current_user_id() ); -- EN: Payments don't have direct user_id, accessed via wallet or order -- VI: Payments không có user_id trực tiếp, truy cập qua wallet hoặc order -- ============================================================================ -- EN: IMPORTANT NOTES -- ============================================================================ -- 1. RLS policies apply to the database user running queries. -- The superuser (postgres) bypasses RLS by default. -- Use ALTER ROLE NOBYPASSRLS; for the application user. -- -- 2. To bypass RLS for migrations and admin operations: -- - Run as superuser (postgres), OR -- - Don't set app.current_shop_id session variable (policy allows NULL) -- -- 3. To test RLS: -- SET LOCAL app.current_shop_id = ''; -- SELECT * FROM orders; -- Should only return rows for that shop -- -- 4. These helper functions need to be created in EACH service database. -- Run the CREATE FUNCTION statements per database. -- ============================================================================