Repository navigation
fix(identity): harden permission system, resolve test failures, and i… #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
521f7c3
fix(identity): harden permission system, resolve test failures, and i…
pramodnarayana 3ec2ed3
refactor(all): address code review feedback, fix auth retry, and hard…
pramodnarayana 1f9984d
refactor(all): address round 2 code review feedback
pramodnarayana c83b6b2
refactor(identity): strict typing for better-auth adapter and round 3…
pramodnarayana 801e9f9
refactor(identity): enhance better-auth adapter type safety and coverage
pramodnarayana a834738
chore(web): add auth provider tests, fix coverage, and pass quality gate
pramodnarayana 1751529
chore: code review round 3 fixes - security, error handling, and cons…
pramodnarayana bec796c
chore: code review round 4 fixes and build configuration updates
pramodnarayana 0dec3a5
chore: code review round 5 fixes (identity matcher, api security, web…
pramodnarayana 00405bd
chore: optimize tenant lookup and sync lockfile
pramodnarayana 7f0dc13
chore: ensure deterministic tenant lookup ordering
pramodnarayana e112655
chore: refine tenant lookup determinism with tie-breaker
pramodnarayana b75a88e
Merge branch 'development' into fix/tenant-provisioning-hook
pramodnarayana File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| import { Client } from 'pg'; | ||
| import * as bcrypt from 'bcryptjs'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
| import * as dotenv from 'dotenv'; | ||
| import * as path from 'node:path'; | ||
|
|
||
| // Load .env from apps/api root | ||
| dotenv.config({ path: path.resolve(__dirname, '../../.env') }); | ||
|
|
||
| const main = async () => { | ||
| if (!process.env.DATABASE_URL) { | ||
| console.error('DATABASE_URL is not defined in environment'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Production safeguard | ||
| if ( | ||
| process.env.NODE_ENV === 'production' || | ||
| process.env.DATABASE_URL.includes('prod') || | ||
| process.env.DATABASE_URL.includes('rds.amazonaws.com') | ||
| ) { | ||
| if (process.env.FORCE_RESET !== 'true') { | ||
| console.error( | ||
| 'FATAL: Attempting to run reset-e2e against production database!', | ||
| ); | ||
| console.error('This operation would destroy all production data.'); | ||
| console.error('If you absolutely must proceed, set FORCE_RESET=true'); | ||
| process.exit(1); | ||
| } | ||
| console.warn( | ||
| 'WARNING: FORCE_RESET=true detected, proceeding with production reset...', | ||
| ); | ||
| } | ||
|
|
||
| console.log('Connecting to DB...'); | ||
| const client = new Client({ connectionString: process.env.DATABASE_URL }); | ||
|
|
||
| try { | ||
| await client.connect(); | ||
| console.log('--- 1. Truncating Tables ---'); | ||
| const tables = [ | ||
| 'role_permission', | ||
| 'permission', | ||
| 'member', | ||
| 'invitation', | ||
| 'account', | ||
| 'session', | ||
| 'verification', | ||
| 'organization', | ||
| 'user', | ||
| 'role', | ||
| ]; | ||
|
|
||
| for (const table of tables) { | ||
| try { | ||
| await client.query(`TRUNCATE TABLE "${table}" CASCADE;`); | ||
| } catch (e) { | ||
| console.error(`Error truncating table "${table}":`, e); | ||
| console.log(`Skipped ${table} - check permissions or table existence`); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| console.log('--- 2. Seeding RBAC ---'); | ||
| // Roles | ||
| await client.query(` | ||
| INSERT INTO "role" (id, name, "isSystem", description, "createdAt") VALUES | ||
| ('owner', 'Owner', true, 'Full access', NOW()), | ||
| ('admin', 'Admin', true, 'Manage users', NOW()), | ||
| ('member', 'Member', true, 'Read only', NOW()), | ||
| ('platform_admin', 'Platform Admin', true, 'System Root Access', NOW()) | ||
| ON CONFLICT DO NOTHING; | ||
| `); | ||
|
|
||
| // Permissions | ||
| const perms = [ | ||
| 'users:read', | ||
| 'users:create', | ||
| 'users:update', | ||
| 'users:delete', | ||
| 'users:manage', | ||
| 'tenants:read', | ||
| 'tenants:create', | ||
| 'tenants:update', | ||
| 'tenants:delete', | ||
| 'tenants:manage', | ||
| 'dashboard:read', | ||
| 'admin_dashboard:view', // Required for Redirect to /admin | ||
| 'settings:manage', | ||
| 'settings:read', | ||
| // System Admin Permissions | ||
| 'system_users:read', | ||
| 'system_users:manage', | ||
| 'system_users:invite', | ||
| 'system_tenants:read', | ||
| 'system_tenants:manage', | ||
| ]; | ||
|
|
||
| for (const p of perms) { | ||
| const resource = p.split(':')[0]; | ||
| const action = p.split(':')[1]; | ||
| await client.query( | ||
| ` | ||
| INSERT INTO "permission" (id, resource, action, "createdAt") | ||
| VALUES ($1, $2, $3, NOW()) | ||
| ON CONFLICT DO NOTHING; | ||
| `, | ||
| [p, resource, action], | ||
| ); | ||
| } | ||
|
|
||
| // Role Permissions | ||
| // Owner & Platform Admin: All | ||
| for (const p of perms) { | ||
| await client.query( | ||
| `INSERT INTO "role_permission" ("roleId", "permissionId") VALUES ('owner', $1) ON CONFLICT DO NOTHING`, | ||
| [p], | ||
| ); | ||
| await client.query( | ||
| `INSERT INTO "role_permission" ("roleId", "permissionId") VALUES ('platform_admin', $1) ON CONFLICT DO NOTHING`, | ||
| [p], | ||
| ); | ||
| } | ||
|
|
||
| // Member: Read | ||
| await client.query( | ||
| `INSERT INTO "role_permission" ("roleId", "permissionId") VALUES ('member', 'users:read') ON CONFLICT DO NOTHING`, | ||
| ); | ||
| await client.query( | ||
| `INSERT INTO "role_permission" ("roleId", "permissionId") VALUES ('member', 'tenants:read') ON CONFLICT DO NOTHING`, | ||
| ); | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| // Admin: All non-system permissions | ||
| for (const p of perms) { | ||
| if (!p.startsWith('system_')) { | ||
| await client.query( | ||
| `INSERT INTO "role_permission" ("roleId", "permissionId") VALUES ('admin', $1) ON CONFLICT DO NOTHING`, | ||
| [p], | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| console.log('--- 3. Seeding User ---'); | ||
| const userId = uuidv4(); | ||
| const email = 'test.user+e2e@example.com'; | ||
|
|
||
| await client.query( | ||
| ` | ||
| INSERT INTO "user" (id, email, "emailVerified", name, "createdAt", "updatedAt") | ||
| VALUES ($1, $2, true, 'Test User', NOW(), NOW()); | ||
| `, | ||
| [userId, email], | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| console.log('--- 4. Seeding Account (Password) ---'); | ||
| const passwordStart = 'password123'; | ||
| const hash = await bcrypt.hash(passwordStart, 10); | ||
| const accountId = uuidv4(); | ||
|
|
||
| await client.query( | ||
| ` | ||
| INSERT INTO "account" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") | ||
| VALUES ($1, $2, $3, 'credential', $4, NOW(), NOW()); | ||
| `, | ||
| [accountId, userId, email, hash], | ||
| ); | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| console.log('--- 5. Seeding Organizations ---'); | ||
|
|
||
| // 5a. System Tenant (Nexiom) | ||
| const systemTenantId = '00000000-0000-0000-0000-000000000000'; | ||
| await client.query( | ||
| ` | ||
| INSERT INTO "organization" (id, name, status, "isSystem", "createdAt", "updatedAt") | ||
| VALUES ($1, 'Nexiom Platform', 'active', true, NOW(), NOW()) | ||
| ON CONFLICT (id) DO NOTHING; | ||
| `, | ||
| [systemTenantId], | ||
| ); | ||
|
|
||
| // 5b. Demo Customer Tenant | ||
| const demoTenantId = uuidv4(); | ||
| await client.query( | ||
| ` | ||
| INSERT INTO "organization" (id, name, status, "createdAt", "updatedAt") | ||
| VALUES ($1, 'Acme Corp', 'active', NOW(), NOW()); | ||
| `, | ||
| [demoTenantId], | ||
| ); | ||
|
|
||
| console.log('--- 6. Seeding Membership ---'); | ||
|
|
||
| // 6a. Platform Admin Access (System Context) | ||
| const sysMemberId = uuidv4(); | ||
| // Note: platform_admin role already seeded in step 2 (roles: owner, admin, member, platform_admin) | ||
|
|
||
| await client.query( | ||
| ` | ||
| INSERT INTO "member" (id, "organizationId", "userId", "roleId", "createdAt") | ||
| VALUES ($1, $2, $3, 'platform_admin', NOW()); | ||
| `, | ||
| [sysMemberId, systemTenantId, userId], | ||
| ); | ||
|
|
||
| // 6b. Customer Tenant Access (Business Context) | ||
| // STRICT: One User One Org. Pramod is Platform Admin ONLY. | ||
| // We create a separate user for the customer tenant. | ||
| const customerUserId = uuidv4(); | ||
| const customerEmail = 'customer@example.com'; | ||
|
|
||
| await client.query( | ||
| ` | ||
| INSERT INTO "user" (id, email, "emailVerified", name, "createdAt", "updatedAt") | ||
| VALUES ($1, $2, true, 'Acme Customer', NOW(), NOW()); | ||
| `, | ||
| [customerUserId, customerEmail], | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| const customerHash = await bcrypt.hash('password123', 10); | ||
| await client.query( | ||
| ` | ||
| INSERT INTO "account" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") | ||
| VALUES ($1, $2, $3, 'credential', $4, NOW(), NOW()); | ||
| `, | ||
| [uuidv4(), customerUserId, customerEmail, customerHash], | ||
| ); | ||
|
|
||
| const memberId = uuidv4(); | ||
| await client.query( | ||
| ` | ||
| INSERT INTO "member" (id, "organizationId", "userId", "roleId", "createdAt") | ||
| VALUES ($1, $2, $3, 'owner', NOW()); | ||
| `, | ||
| [memberId, demoTenantId, customerUserId], | ||
| ); | ||
|
|
||
| console.log('✅ SEED COMPLETE'); | ||
| // Security: Do not log full credentials in CI/Production logs | ||
| const showCreds = process.env.SHOW_CREDENTIALS === 'true'; | ||
| if (showCreds) { | ||
| console.log( | ||
| `[Platform Admin] ${email} / ${passwordStart} (Org: Nexiom Platform)`, | ||
| ); | ||
| console.log( | ||
| `[Customer User] ${customerEmail} / password123 (Org: Acme Corp)`, | ||
| ); | ||
| } else { | ||
| console.log( | ||
| `[Platform Admin] ${email} / ******** (Org: Nexiom Platform)`, | ||
| ); | ||
| console.log( | ||
| `[Customer User] ${customerEmail} / ******** (Org: Acme Corp)`, | ||
| ); | ||
| console.log('(Set SHOW_CREDENTIALS=true to view passwords)'); | ||
| } | ||
| } catch (error) { | ||
| console.error('Seed failed:', error); | ||
| process.exit(1); | ||
| } finally { | ||
| await client.end(); | ||
| } | ||
| }; | ||
|
|
||
| void main(); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.