Spaces:
Runtime error
Runtime error
| import postgres from 'postgres'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); | |
| const envPath = path.join(__dirname, '../artifacts/api-server/.env'); | |
| let connectionString = process.env.DATABASE_URL; | |
| if (!connectionString && fs.existsSync(envPath)) { | |
| const envContent = fs.readFileSync(envPath, 'utf8'); | |
| const match = envContent.match(/DATABASE_URL=(.*)/); | |
| if (match) connectionString = match[1].trim(); | |
| } | |
| if (!connectionString) { | |
| console.error("DATABASE_URL is not set"); | |
| process.exit(1); | |
| } | |
| const sql = postgres(connectionString); | |
| const USER_A = '00000000-0000-0000-0000-000000000001'; | |
| const USER_B = '00000000-0000-0000-0000-000000000002'; | |
| async function runTest() { | |
| try { | |
| console.log("--- Starting Comprehensive RLS Verification Test ---"); | |
| const tables = ['clients', 'projects', 'tasks', 'time_entries', 'briefs']; | |
| // 1. Clean up | |
| for (const table of tables) { | |
| await sql.unsafe(`DELETE FROM ${table} WHERE user_id IN ('${USER_A}', '${USER_B}')`); | |
| } | |
| console.log("✓ Cleaned up old test data for all tables."); | |
| const runAsUser = async (userId, queryFn) => { | |
| return await sql.begin(async (tx) => { | |
| await tx.unsafe(`SET LOCAL role TO authenticated`); | |
| await tx.unsafe(`SET LOCAL "request.jwt.claims" TO '{"sub": "${userId}"}'`); | |
| return await queryFn(tx); | |
| }); | |
| }; | |
| // Test each table | |
| for (const table of tables) { | |
| console.log(`\n--- Testing Table: ${table} ---`); | |
| // Prepare dummy data for insertion | |
| let data = { user_id: USER_A }; | |
| if (table === 'clients') { data.name = 'Test Client A'; data.email = 'a@test.com'; } | |
| if (table === 'projects') { data.title = 'Test Project A'; } | |
| if (table === 'tasks') { | |
| // Need a project first | |
| const p = await runAsUser(USER_A, tx => tx`INSERT INTO projects (title, user_id) VALUES ('Project for Task', ${USER_A}) RETURNING id`); | |
| data.title = 'Test Task A'; data.project_id = p[0].id; | |
| } | |
| if (table === 'time_entries') { | |
| const p = await runAsUser(USER_A, tx => tx`INSERT INTO projects (title, user_id) VALUES ('Project for Time', ${USER_A}) RETURNING id`); | |
| data.hours = 1.5; data.date = '2024-01-01'; data.project_id = p[0].id; | |
| } | |
| if (table === 'briefs') { data.title = 'Test Brief A'; data.content = 'Content A'; } | |
| // 2. Insert | |
| console.log(`Testing: User A inserts into ${table}...`); | |
| await runAsUser(USER_A, async (tx) => { | |
| await tx.unsafe(`INSERT INTO ${table} (${Object.keys(data).join(',')}) VALUES (${Object.values(data).map(v => typeof v === 'string' ? `'${v}'` : v).join(',')})`); | |
| }); | |
| console.log(`✓ User A successfully inserted into ${table}.`); | |
| // 3. Select (Self) | |
| const rowsA = await runAsUser(USER_A, async (tx) => { | |
| return await tx.unsafe(`SELECT * FROM ${table} WHERE user_id = '${USER_A}'`); | |
| }); | |
| if (rowsA.length > 0) { | |
| console.log(`✓ User A successfully read their own data from ${table}.`); | |
| } else { | |
| throw new Error(`User A could not read their own data from ${table}!`); | |
| } | |
| // 4. Select (Other) | |
| const rowsB = await runAsUser(USER_B, async (tx) => { | |
| return await tx.unsafe(`SELECT * FROM ${table} WHERE user_id = '${USER_A}'`); | |
| }); | |
| if (rowsB.length === 0) { | |
| console.log(`✓ User B cannot see User A's data in ${table} (RLS Working).`); | |
| } else { | |
| throw new Error(`CRITICAL: User B can see User A's data in ${table}!`); | |
| } | |
| // 5. Update (Other) | |
| const updateCol = (table === 'clients' ? 'name' : (table === 'time_entries' ? 'description' : 'title')); | |
| const updateResult = await runAsUser(USER_B, async (tx) => { | |
| return await tx.unsafe(`UPDATE ${table} SET ${updateCol} = 'Hacked' WHERE user_id = '${USER_A}'`); | |
| }); | |
| if (updateResult.count === 0) { | |
| console.log(`✓ User B cannot update User A's data in ${table} (RLS Working).`); | |
| } else { | |
| throw new Error(`CRITICAL: User B updated User A's data in ${table}!`); | |
| } | |
| // 6. Delete (Other) | |
| const deleteResult = await runAsUser(USER_B, async (tx) => { | |
| return await tx.unsafe(`DELETE FROM ${table} WHERE user_id = '${USER_A}'`); | |
| }); | |
| if (deleteResult.count === 0) { | |
| console.log(`✓ User B cannot delete User A's data in ${table} (RLS Working).`); | |
| } else { | |
| throw new Error(`CRITICAL: User B deleted User A's data in ${table}!`); | |
| } | |
| } | |
| console.log("\n--- All Comprehensive RLS Verification Tests Passed Successfully! ---"); | |
| } catch (err) { | |
| console.error("\nFAIL: RLS Verification Failed!"); | |
| console.error(err.stack || err.message); | |
| process.exit(1); | |
| } finally { | |
| await sql.end(); | |
| } | |
| } | |
| runTest(); | |