406 lines
12 KiB
TypeScript
406 lines
12 KiB
TypeScript
/**
|
|
* RLS Policies - Usage Examples
|
|
*
|
|
* This file demonstrates how to use the centralized RLS policies
|
|
* in various scenarios across the ERP-Suite.
|
|
*/
|
|
|
|
import { Pool } from 'pg';
|
|
import {
|
|
applyTenantIsolationPolicy,
|
|
applyAdminBypassPolicy,
|
|
applyUserDataPolicy,
|
|
applyCompleteRlsPolicies,
|
|
applyCompletePoliciesForSchema,
|
|
batchApplyRlsPolicies,
|
|
isRlsEnabled,
|
|
listRlsPolicies,
|
|
getSchemaRlsStatus,
|
|
setRlsContext,
|
|
clearRlsContext,
|
|
withRlsContext,
|
|
RlsPolicyOptions,
|
|
} from '@erp-suite/core';
|
|
|
|
// Database connection pool
|
|
const pool = new Pool({
|
|
host: 'localhost',
|
|
port: 5432,
|
|
database: 'erp_suite',
|
|
user: 'postgres',
|
|
password: 'password',
|
|
});
|
|
|
|
/**
|
|
* EXAMPLE 1: Apply tenant isolation to a single table
|
|
*/
|
|
async function example1_singleTable() {
|
|
console.log('Example 1: Apply tenant isolation to core.partners');
|
|
|
|
// Apply tenant isolation policy
|
|
await applyTenantIsolationPolicy(pool, 'core', 'partners');
|
|
|
|
// Verify it was applied
|
|
const enabled = await isRlsEnabled(pool, 'core', 'partners');
|
|
console.log(`RLS enabled: ${enabled}`);
|
|
|
|
// List policies
|
|
const status = await listRlsPolicies(pool, 'core', 'partners');
|
|
console.log(`Policies: ${status.policies.map(p => p.name).join(', ')}`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 2: Apply complete policies (tenant + admin) to a table
|
|
*/
|
|
async function example2_completePolicies() {
|
|
console.log('Example 2: Apply complete policies to inventory.products');
|
|
|
|
// Apply tenant isolation + admin bypass
|
|
await applyCompleteRlsPolicies(pool, 'inventory', 'products');
|
|
|
|
// Check status
|
|
const status = await listRlsPolicies(pool, 'inventory', 'products');
|
|
console.log(`Applied ${status.policies.length} policies:`);
|
|
status.policies.forEach(p => {
|
|
console.log(` - ${p.name} (${p.command})`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 3: Apply policies to multiple tables in a schema
|
|
*/
|
|
async function example3_multipleTablesInSchema() {
|
|
console.log('Example 3: Apply policies to multiple inventory tables');
|
|
|
|
const tables = ['products', 'warehouses', 'locations', 'lots', 'pickings'];
|
|
|
|
await applyCompletePoliciesForSchema(pool, 'inventory', tables);
|
|
|
|
console.log(`Applied RLS to ${tables.length} tables in inventory schema`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 4: Batch apply with different configurations
|
|
*/
|
|
async function example4_batchApply() {
|
|
console.log('Example 4: Batch apply with custom configurations');
|
|
|
|
const configs: RlsPolicyOptions[] = [
|
|
// Standard tables
|
|
{ schema: 'core', table: 'partners' },
|
|
{ schema: 'core', table: 'addresses' },
|
|
{ schema: 'core', table: 'notes' },
|
|
|
|
// Table without admin bypass
|
|
{ schema: 'financial', table: 'audit_logs', includeAdminBypass: false },
|
|
|
|
// Table with custom tenant column
|
|
{ schema: 'projects', table: 'tasks', tenantColumn: 'company_id' },
|
|
];
|
|
|
|
await batchApplyRlsPolicies(pool, configs);
|
|
|
|
console.log(`Applied RLS to ${configs.length} tables`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 5: Apply user data policy for user-specific tables
|
|
*/
|
|
async function example5_userDataPolicy() {
|
|
console.log('Example 5: Apply user data policy to projects.tasks');
|
|
|
|
// Apply user data policy (users can only see their own tasks)
|
|
await applyUserDataPolicy(pool, 'projects', 'tasks', ['created_by', 'assigned_to']);
|
|
|
|
const status = await listRlsPolicies(pool, 'projects', 'tasks');
|
|
console.log(`User data policy applied: ${status.policies[0]?.name}`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 6: Get RLS status for entire schema
|
|
*/
|
|
async function example6_schemaStatus() {
|
|
console.log('Example 6: Get RLS status for core schema');
|
|
|
|
const statuses = await getSchemaRlsStatus(pool, 'core');
|
|
|
|
console.log(`RLS Status for core schema:`);
|
|
statuses.forEach(status => {
|
|
const policyCount = status.policies.length;
|
|
const rlsStatus = status.rlsEnabled ? 'enabled' : 'disabled';
|
|
console.log(` ${status.table}: RLS ${rlsStatus}, ${policyCount} policies`);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 7: Query data with RLS context
|
|
*/
|
|
async function example7_queryWithContext() {
|
|
console.log('Example 7: Query data with RLS context');
|
|
|
|
const tenantId = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
const userId = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb';
|
|
|
|
// Method 1: Manual context setting
|
|
await setRlsContext(pool, { tenantId, userId, userRole: 'user' });
|
|
|
|
const result1 = await pool.query('SELECT COUNT(*) FROM core.partners');
|
|
console.log(`Partners visible to user: ${result1.rows[0].count}`);
|
|
|
|
await clearRlsContext(pool);
|
|
|
|
// Method 2: Using withRlsContext helper
|
|
const result2 = await withRlsContext(
|
|
pool,
|
|
{ tenantId, userId, userRole: 'admin' },
|
|
async (client) => {
|
|
return await client.query('SELECT COUNT(*) FROM core.partners');
|
|
}
|
|
);
|
|
console.log(`Partners visible to admin: ${result2.rows[0].count}`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 8: Migration script for a vertical
|
|
*/
|
|
async function example8_verticalMigration() {
|
|
console.log('Example 8: Migration script for construccion vertical');
|
|
|
|
// Tables in construccion vertical
|
|
const constructionTables = [
|
|
{ schema: 'construction', table: 'projects' },
|
|
{ schema: 'construction', table: 'estimates' },
|
|
{ schema: 'construction', table: 'work_orders' },
|
|
{ schema: 'construction', table: 'materials' },
|
|
{ schema: 'hse', table: 'incidents' },
|
|
{ schema: 'hse', table: 'inspections' },
|
|
];
|
|
|
|
console.log('Applying RLS to construccion vertical tables...');
|
|
|
|
for (const { schema, table } of constructionTables) {
|
|
try {
|
|
await applyCompleteRlsPolicies(pool, schema, table, 'constructora_id');
|
|
console.log(` ✓ ${schema}.${table}`);
|
|
} catch (error) {
|
|
console.error(` ✗ ${schema}.${table}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
console.log('Migration complete!');
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 9: Verify RLS isolation (testing)
|
|
*/
|
|
async function example9_testRlsIsolation() {
|
|
console.log('Example 9: Test RLS isolation between tenants');
|
|
|
|
const tenant1 = 'tenant-1111-1111-1111-111111111111';
|
|
const tenant2 = 'tenant-2222-2222-2222-222222222222';
|
|
|
|
// Query as tenant 1
|
|
const result1 = await withRlsContext(
|
|
pool,
|
|
{ tenantId: tenant1, userRole: 'user' },
|
|
async (client) => {
|
|
return await client.query('SELECT COUNT(*) FROM core.partners');
|
|
}
|
|
);
|
|
|
|
// Query as tenant 2
|
|
const result2 = await withRlsContext(
|
|
pool,
|
|
{ tenantId: tenant2, userRole: 'user' },
|
|
async (client) => {
|
|
return await client.query('SELECT COUNT(*) FROM core.partners');
|
|
}
|
|
);
|
|
|
|
console.log(`Tenant 1 sees: ${result1.rows[0].count} partners`);
|
|
console.log(`Tenant 2 sees: ${result2.rows[0].count} partners`);
|
|
console.log('RLS isolation verified!');
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 10: Apply admin bypass for support team
|
|
*/
|
|
async function example10_adminBypass() {
|
|
console.log('Example 10: Test admin bypass policy');
|
|
|
|
const tenantId = 'tenant-1111-1111-1111-111111111111';
|
|
|
|
// Query as regular user
|
|
const userResult = await withRlsContext(
|
|
pool,
|
|
{ tenantId, userRole: 'user' },
|
|
async (client) => {
|
|
return await client.query('SELECT COUNT(*) FROM core.partners');
|
|
}
|
|
);
|
|
|
|
// Query as admin (should see all tenants)
|
|
const adminResult = await withRlsContext(
|
|
pool,
|
|
{ userRole: 'admin' }, // No tenantId set
|
|
async (client) => {
|
|
return await client.query('SELECT COUNT(*) FROM core.partners');
|
|
}
|
|
);
|
|
|
|
console.log(`User sees: ${userResult.rows[0].count} partners (own tenant only)`);
|
|
console.log(`Admin sees: ${adminResult.rows[0].count} partners (all tenants)`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 11: Apply RLS to ERP Core tables
|
|
*/
|
|
async function example11_erpCoreTables() {
|
|
console.log('Example 11: Apply RLS to ERP Core tables');
|
|
|
|
const erpCoreConfigs: RlsPolicyOptions[] = [
|
|
// Core schema
|
|
{ schema: 'core', table: 'partners' },
|
|
{ schema: 'core', table: 'addresses' },
|
|
{ schema: 'core', table: 'product_categories' },
|
|
{ schema: 'core', table: 'tags' },
|
|
{ schema: 'core', table: 'sequences' },
|
|
{ schema: 'core', table: 'attachments' },
|
|
{ schema: 'core', table: 'notes' },
|
|
|
|
// Inventory schema
|
|
{ schema: 'inventory', table: 'products' },
|
|
{ schema: 'inventory', table: 'warehouses' },
|
|
{ schema: 'inventory', table: 'locations' },
|
|
{ schema: 'inventory', table: 'lots' },
|
|
{ schema: 'inventory', table: 'pickings' },
|
|
{ schema: 'inventory', table: 'stock_moves' },
|
|
|
|
// Sales schema
|
|
{ schema: 'sales', table: 'sales_orders' },
|
|
{ schema: 'sales', table: 'quotations' },
|
|
{ schema: 'sales', table: 'pricelists' },
|
|
|
|
// Purchase schema
|
|
{ schema: 'purchase', table: 'purchase_orders' },
|
|
{ schema: 'purchase', table: 'rfqs' },
|
|
{ schema: 'purchase', table: 'vendor_pricelists' },
|
|
|
|
// Financial schema
|
|
{ schema: 'financial', table: 'accounts' },
|
|
{ schema: 'financial', table: 'invoices' },
|
|
{ schema: 'financial', table: 'payments' },
|
|
{ schema: 'financial', table: 'journal_entries' },
|
|
|
|
// Projects schema
|
|
{ schema: 'projects', table: 'projects' },
|
|
{ schema: 'projects', table: 'tasks' },
|
|
{ schema: 'projects', table: 'timesheets' },
|
|
];
|
|
|
|
console.log(`Applying RLS to ${erpCoreConfigs.length} ERP Core tables...`);
|
|
|
|
let successCount = 0;
|
|
let errorCount = 0;
|
|
|
|
for (const config of erpCoreConfigs) {
|
|
try {
|
|
await applyCompleteRlsPolicies(
|
|
pool,
|
|
config.schema,
|
|
config.table,
|
|
config.tenantColumn || 'tenant_id',
|
|
config.includeAdminBypass ?? true
|
|
);
|
|
console.log(` ✓ ${config.schema}.${config.table}`);
|
|
successCount++;
|
|
} catch (error) {
|
|
console.error(` ✗ ${config.schema}.${config.table}: ${error.message}`);
|
|
errorCount++;
|
|
}
|
|
}
|
|
|
|
console.log(`\nComplete! Success: ${successCount}, Errors: ${errorCount}`);
|
|
}
|
|
|
|
/**
|
|
* EXAMPLE 12: Cleanup - Remove RLS from a table
|
|
*/
|
|
async function example12_cleanup() {
|
|
console.log('Example 12: Remove RLS from a table');
|
|
|
|
// Import dropAllRlsPolicies
|
|
const { dropAllRlsPolicies, disableRls } = await import('@erp-suite/core');
|
|
|
|
const schema = 'test';
|
|
const table = 'temp_table';
|
|
|
|
// Drop all policies
|
|
const policyCount = await dropAllRlsPolicies(pool, schema, table);
|
|
console.log(`Dropped ${policyCount} policies from ${schema}.${table}`);
|
|
|
|
// Disable RLS
|
|
await disableRls(pool, schema, table);
|
|
console.log(`Disabled RLS on ${schema}.${table}`);
|
|
|
|
// Verify
|
|
const enabled = await isRlsEnabled(pool, schema, table);
|
|
console.log(`RLS enabled: ${enabled}`);
|
|
}
|
|
|
|
/**
|
|
* Main function - Run all examples
|
|
*/
|
|
async function main() {
|
|
try {
|
|
console.log('='.repeat(60));
|
|
console.log('RLS POLICIES - USAGE EXAMPLES');
|
|
console.log('='.repeat(60));
|
|
console.log();
|
|
|
|
// Uncomment the examples you want to run
|
|
// await example1_singleTable();
|
|
// await example2_completePolicies();
|
|
// await example3_multipleTablesInSchema();
|
|
// await example4_batchApply();
|
|
// await example5_userDataPolicy();
|
|
// await example6_schemaStatus();
|
|
// await example7_queryWithContext();
|
|
// await example8_verticalMigration();
|
|
// await example9_testRlsIsolation();
|
|
// await example10_adminBypass();
|
|
// await example11_erpCoreTables();
|
|
// await example12_cleanup();
|
|
|
|
console.log();
|
|
console.log('='.repeat(60));
|
|
console.log('Examples completed successfully!');
|
|
console.log('='.repeat(60));
|
|
} catch (error) {
|
|
console.error('Error running examples:', error);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
// Export examples for individual use
|
|
export {
|
|
example1_singleTable,
|
|
example2_completePolicies,
|
|
example3_multipleTablesInSchema,
|
|
example4_batchApply,
|
|
example5_userDataPolicy,
|
|
example6_schemaStatus,
|
|
example7_queryWithContext,
|
|
example8_verticalMigration,
|
|
example9_testRlsIsolation,
|
|
example10_adminBypass,
|
|
example11_erpCoreTables,
|
|
example12_cleanup,
|
|
};
|