-
-
Notifications
You must be signed in to change notification settings - Fork 85
fix(rbac): allow org_admin to manage user roles in legacy permission path #2007
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| -- Enable RBAC for all existing organizations. | ||
| -- Uses rbac_enable_for_org() to properly backfill role_bindings from org_users | ||
| -- before flipping the use_new_rbac flag. | ||
| -- | ||
| -- Rollback (if critical issues are discovered): | ||
| -- UPDATE "public"."orgs" SET "use_new_rbac" = false WHERE "use_new_rbac" = true; | ||
| -- Note: role_bindings created by this migration will remain but become unused | ||
| -- when the flag is false. They do not need to be deleted for a safe rollback. | ||
| DO $$ | ||
| DECLARE | ||
| v_org_id uuid; | ||
| v_result jsonb; | ||
| BEGIN | ||
| FOR v_org_id IN | ||
| SELECT id FROM "public"."orgs" WHERE "use_new_rbac" = false | ||
| LOOP | ||
| v_result := "public"."rbac_enable_for_org"(v_org_id, NULL); | ||
| END LOOP; | ||
| END $$; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| -- Fix: Allow org_admin to manage user roles in legacy fallback path | ||
| -- | ||
| -- Previously org.update_user_roles mapped to super_admin in the legacy | ||
| -- permission resolver, meaning only super_admins could delete/manage | ||
| -- org members. This was inconsistent with the RBAC system where | ||
| -- org_admin explicitly has org.update_user_roles granted. | ||
| -- | ||
| -- Change: org.update_user_roles -> admin (was super_admin) | ||
| -- The priority_rank guard in the application layer still prevents | ||
| -- org_admin (rank 90) from deleting org_super_admin (rank 95) bindings. | ||
|
|
||
| CREATE OR REPLACE FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") RETURNS "public"."user_min_right" | ||
| LANGUAGE "plpgsql" IMMUTABLE | ||
| SET "search_path" TO '' | ||
| AS $$ | ||
| BEGIN | ||
| -- Map permissions to their legacy equivalents | ||
| -- This mapping should match PERMISSION_TO_LEGACY_RIGHT in utils/rbac.ts | ||
| CASE p_permission_key | ||
| -- Read permissions -> public.rbac_right_read() | ||
| WHEN public.rbac_perm_org_read() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_org_read_members() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_app_read() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_app_read_bundles() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_app_read_channels() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_app_read_logs() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_app_read_devices() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_channel_read() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_channel_read_history() THEN RETURN public.rbac_right_read(); | ||
| WHEN public.rbac_perm_channel_read_forced_devices() THEN RETURN public.rbac_right_read(); | ||
|
|
||
| -- Upload permissions -> public.rbac_right_upload() | ||
| WHEN public.rbac_perm_app_upload_bundle() THEN RETURN public.rbac_right_upload(); | ||
|
|
||
| -- Write permissions -> public.rbac_right_write() | ||
| WHEN public.rbac_perm_app_update_settings() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_app_create_channel() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_app_manage_devices() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_app_build_native() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_channel_update_settings() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_channel_promote_bundle() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_channel_rollback_bundle() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_channel_manage_forced_devices() THEN RETURN public.rbac_right_write(); | ||
| WHEN public.rbac_perm_org_create_app() THEN RETURN public.rbac_right_write(); | ||
|
|
||
| -- Admin permissions -> public.rbac_right_admin() | ||
| WHEN public.rbac_perm_org_update_settings() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_org_invite_user() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_org_read_billing() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_org_read_invoices() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_org_read_audit() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_app_delete() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_app_read_audit() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_bundle_delete() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_channel_delete() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_channel_read_audit() THEN RETURN public.rbac_right_admin(); | ||
| WHEN public.rbac_perm_org_update_user_roles() THEN RETURN public.rbac_right_admin(); | ||
|
|
||
| -- Super admin permissions -> public.rbac_right_super_admin() | ||
| WHEN public.rbac_perm_org_update_billing() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_org_read_billing_audit() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_org_delete() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_app_transfer() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_impersonate_user() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_manage_orgs_any() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_manage_apps_any() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_manage_channels_any() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_run_maintenance_jobs() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_delete_orphan_users() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_read_all_audit() THEN RETURN public.rbac_right_super_admin(); | ||
| WHEN public.rbac_perm_platform_db_break_glass() THEN RETURN public.rbac_right_super_admin(); | ||
|
|
||
| ELSE RETURN NULL; -- Unknown permission | ||
| END CASE; | ||
| END; | ||
| $$; | ||
|
|
||
| ALTER FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") OWNER TO "postgres"; | ||
| REVOKE ALL ON FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") FROM PUBLIC; | ||
| GRANT EXECUTE ON FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") TO "service_role"; | ||
| GRANT EXECUTE ON FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") TO "authenticated"; | ||
| GRANT EXECUTE ON FUNCTION "public"."rbac_legacy_right_for_permission"("p_permission_key" "text") TO "anon"; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -949,6 +949,81 @@ describe('[DELETE] /organization/members', () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('[DELETE] /organization/members - org_admin can delete members', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const adminDeleteOrgId = randomUUID() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const adminDeleteGlobalId = randomUUID() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const adminDeleteCustomerId = `cus_test_admin_del_${adminDeleteOrgId}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| beforeAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error: stripeError } = await getSupabaseClient().from('stripe_info').insert({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customer_id: adminDeleteCustomerId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 'succeeded', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| product_id: 'prod_LQIregjtNduh4q', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subscription_id: `sub_${adminDeleteGlobalId}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trial_at: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000).toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is_good_plan: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (stripeError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw stripeError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create org with legacy RBAC path (use_new_rbac: false) to test the fixed legacy mapping | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error: orgError } = await getSupabaseClient().from('orgs').insert({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: adminDeleteOrgId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: `Admin Delete Test Org ${adminDeleteGlobalId}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| management_email: TEST_EMAIL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| created_by: USER_ID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| customer_id: adminDeleteCustomerId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| website: 'https://admin-delete-test.example/', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use_new_rbac: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (orgError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw orgError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add USER_ID as admin (NOT super_admin) — this is the key scenario under test | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error: orgUserError } = await getSupabaseClient().from('org_users').insert({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| org_id: adminDeleteOrgId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: USER_ID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_right: 'admin', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (orgUserError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw orgUserError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| afterAll(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await getSupabaseClient().from('org_users').delete().eq('org_id', adminDeleteOrgId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await getSupabaseClient().from('orgs').delete().eq('id', adminDeleteOrgId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await getSupabaseClient().from('stripe_info').delete().eq('customer_id', adminDeleteCustomerId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it.concurrent('org_admin can delete a read member', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add USER_ADMIN as a read member to the org | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: userData, error: userError } = await getSupabaseClient().from('users').select().eq('email', USER_ADMIN_EMAIL).single() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(userError).toBeNull() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(userData).toBeTruthy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { error: addError } = await getSupabaseClient().from('org_users').insert({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| org_id: adminDeleteOrgId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_id: userData!.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user_right: 'read', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(addError).toBeNull() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // USER_ID (admin) deletes USER_ADMIN (read) — should succeed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${BASE_URL}/organization/members?orgId=${adminDeleteOrgId}&email=${USER_ADMIN_EMAIL}`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: 'DELETE', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(response.status).toBe(200) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const responseData = await response.json() as { status: string } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(responseData.status).toBe('ok') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify the member was actually removed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data, error: orgUserError } = await getSupabaseClient().from('org_users').select().eq('org_id', adminDeleteOrgId).eq('user_id', userData!.id).single() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(orgUserError).toBeTruthy() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(data).toBeNull() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+998
to
+1024
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also assert the lingering RBAC binding is removed. This regression test proves the member row is deleted, but it still misses the authorization state cleanup. If a Suggested test addition const { data, error: orgUserError } = await getSupabaseClient().from('org_users').select().eq('org_id', adminDeleteOrgId).eq('user_id', userData!.id).single()
expect(orgUserError).toBeTruthy()
expect(data).toBeNull()
+
+ const { data: bindingsAfterDelete, error: bindingsAfterDeleteError } = await getSupabaseClient()
+ .from('role_bindings')
+ .select('id')
+ .eq('principal_type', 'user')
+ .eq('principal_id', userData!.id)
+ .eq('org_id', adminDeleteOrgId)
+ expect(bindingsAfterDeleteError).toBeNull()
+ expect(bindingsAfterDelete).toHaveLength(0)
})
})📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('[POST] /organization', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it.concurrent('create organization', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const name = `Created Organization ${new Date().toISOString()}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.