Skip to content

fix(frontend): accept org RBAC delete role#1858

Merged
riderx merged 3 commits into
mainfrom
codex/fix-org-delete-rbac-check
Mar 25, 2026
Merged

fix(frontend): accept org RBAC delete role#1858
riderx merged 3 commits into
mainfrom
codex/fix-org-delete-rbac-check

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Mar 25, 2026

Summary (AI generated)

  • fix organization deletion permission checks to accept the RBAC org_super_admin role instead of only legacy role names
  • reuse the centralized store permission helper from both the delete action and the settings page delete gate
  • add a unit regression test that covers allowed deletion for org_super_admin and denied deletion for lower org roles

Motivation (AI generated)

Organization deletion was blocked for users who legitimately had the new RBAC org-level super admin role because the frontend action still compared against legacy super_admin and owner strings directly.

Business Impact (AI generated)

This restores a critical org-management flow for RBAC-enabled customers and reduces support/debugging time caused by false permission denials in settings.

Test Plan (AI generated)

  • bunx vitest run tests/organization-store-delete.unit.test.ts
  • bunx eslint src/stores/organization.ts src/pages/settings/organization/index.vue tests/organization-store-delete.unit.test.ts
  • bun run typecheck

Generated with AI

Summary by CodeRabbit

  • Bug Fixes
    • Consistent organization deletion and admin/super-admin detection across settings, plans, members, and admin dialogs to prevent incorrect permission grants or denials.
  • Refactor
    • Centralized role-check logic so UI controls (delete actions, admin lists, delegation) behave uniformly.
  • Tests
    • Added unit tests covering organization deletion flows and role helper mappings.
  • Chores
    • Improved store/load reliability during app initialization.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Centralized role checks into store helpers (roleHasLegacyMinRight, isAdminRole, isSuperAdminRole) and added canDeleteOrganization(orgId?); components now call these helpers. deleteOrganization gates via canDeleteOrganization. New unit tests cover deletion authorization and role helper mappings.

Changes

Cohort / File(s) Summary
Organization store
src/stores/organization.ts
Added roleHasLegacyMinRight, isAdminRole, isSuperAdminRole; introduced canDeleteOrganization(orgId?); refactored hasPermissionsInRole to use legacy helper; deleteOrganization now authorizes via canDeleteOrganization; replaced Promise.withResolvers usage with createDeferredPromise.
Core utils (deferred promise)
src/utils/promise.ts
Added DeferredPromise<T> interface and createDeferredPromise<T>() factory used by stores for typed deferred promises.
Pages & components (role checks)
src/pages/settings/organization/index.vue, src/pages/settings/account/index.vue, src/pages/settings/organization/Members.vue, src/pages/settings/organization/Plans.vue, src/components/AdminOnlyModal.vue
Replaced inline string-role checks with centralized helpers (isAdminRole / isSuperAdminRole); removed local role constants/inline helper functions and delegated role logic to store helpers.
Main store init change
src/stores/main.ts
Replaced _initialLoadPromise initialization with createDeferredPromise<boolean>() and adjusted usage to the new deferred wrapper.
Tests
tests/organization-store-delete.unit.test.ts
New Vitest suite mocking Supabase and related stores; tests successful delete for org_super_admin, permission denial for org_admin, and validates role helper mappings.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Component
  participant Store as OrganizationStore
  participant DB as Supabase
  Client->>Store: request deleteOrganization(orgId)
  Store->>Store: canDeleteOrganization(orgId)? (uses isAdminRole / isSuperAdminRole / roleHasLegacyMinRight)
  alt authorized
    Store->>DB: DELETE from "orgs" where id=orgId
    DB-->>Store: {data, error}
    Store-->>Client: {data, error:null}
  else unauthorized
    Store-->>Client: {data:null, error: "Insufficient permissions"}
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐰 I hopped through roles and knitted rights,
Gathering helpers in cozy bytes,
One store now holds the permission key,
Tests cheered softly beneath a tree,
Carrots, code, and harmony — whee! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: accepting the new RBAC org_super_admin role for organization deletion, which is the core fix addressed by this PR.
Description check ✅ Passed The description covers the main summary and motivation, and includes a test plan with concrete verification steps, though the Test Plan section deviates from the template's structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-org-delete-rbac-check

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/organization-store-delete.unit.test.ts (2)

51-56: Consider using it.concurrent() for parallelism per coding guidelines.

The coding guidelines specify using it.concurrent() instead of it() in test files to maximize parallelism. Since these tests are stateless (each creates its own Pinia instance), they are good candidates for concurrent execution.

♻️ Suggested change
 describe('organization store deleteOrganization', () => {
   beforeEach(() => {
     vi.clearAllMocks()
     setActivePinia(createPinia())
     mockEq.mockResolvedValue({ data: null, error: null })
   })

-  it('allows org deletion for org_super_admin roles', async () => {
+  it.concurrent('allows org deletion for org_super_admin roles', async () => {

Apply the same change to the other test case.

As per coding guidelines: "Use it.concurrent() instead of it() to maximize parallelism and run tests in parallel within the same file"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/organization-store-delete.unit.test.ts` around lines 51 - 56, Replace
standard synchronous test declarations with concurrent tests in this suite:
change any occurrences of it(...) to it.concurrent(...) inside the
describe('organization store deleteOrganization', ...) block (and the sibling
test case mentioned) so both test cases run in parallel; ensure the tests still
call setActivePinia(createPinia()) in beforeEach and keep
mockEq.mockResolvedValue(...) as-is to preserve isolation while switching to
it.concurrent.

58-74: Good test case, but consider adding coverage for legacy roles.

This test validates the new org_super_admin RBAC role correctly. However, the PR objective states the change should still support legacy roles (super_admin, owner). Consider adding test cases to ensure these legacy roles continue to work:

  1. Legacy super_admin role (with use_new_rbac: false)
  2. Legacy owner role (with use_new_rbac: false)

This would provide regression coverage ensuring the fix doesn't break existing legacy role users. Based on learnings, the owner role exists only at the organization level, so this is the appropriate place to test it.

💡 Suggested additional test cases
it.concurrent('allows org deletion for legacy super_admin role', async () => {
  const { useOrganizationStore } = await import('../src/stores/organization.ts')
  const store = useOrganizationStore()
  const orgId = 'org-legacy-super-admin'

  store.getAllOrgs().set(orgId, {
    gid: orgId,
    role: 'super_admin',
    use_new_rbac: false,
  } as any)

  const result = await store.deleteOrganization(orgId)

  expect(result.error).toBeNull()
  expect(mockFrom).toHaveBeenCalledWith('orgs')
})

it.concurrent('allows org deletion for legacy owner role', async () => {
  const { useOrganizationStore } = await import('../src/stores/organization.ts')
  const store = useOrganizationStore()
  const orgId = 'org-legacy-owner'

  store.getAllOrgs().set(orgId, {
    gid: orgId,
    role: 'owner',
    use_new_rbac: false,
  } as any)

  const result = await store.deleteOrganization(orgId)

  expect(result.error).toBeNull()
  expect(mockFrom).toHaveBeenCalledWith('orgs')
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/organization-store-delete.unit.test.ts` around lines 58 - 74, Add
regression tests covering legacy RBAC roles by duplicating the existing
org_super_admin test but setting role to 'super_admin' and 'owner' with
use_new_rbac: false: call useOrganizationStore(), populate
store.getAllOrgs().set(orgId, { gid: orgId, role: 'super_admin', use_new_rbac:
false }/ { role: 'owner', use_new_rbac: false } as any), invoke
deleteOrganization(orgId) and assert result.error is null and mockFrom was
calledWith('orgs') (same expectation as the current test); use it.concurrent for
parity with other tests and unique orgId values like 'org-legacy-super-admin'
and 'org-legacy-owner'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/organization-store-delete.unit.test.ts`:
- Around line 51-56: Replace standard synchronous test declarations with
concurrent tests in this suite: change any occurrences of it(...) to
it.concurrent(...) inside the describe('organization store deleteOrganization',
...) block (and the sibling test case mentioned) so both test cases run in
parallel; ensure the tests still call setActivePinia(createPinia()) in
beforeEach and keep mockEq.mockResolvedValue(...) as-is to preserve isolation
while switching to it.concurrent.
- Around line 58-74: Add regression tests covering legacy RBAC roles by
duplicating the existing org_super_admin test but setting role to 'super_admin'
and 'owner' with use_new_rbac: false: call useOrganizationStore(), populate
store.getAllOrgs().set(orgId, { gid: orgId, role: 'super_admin', use_new_rbac:
false }/ { role: 'owner', use_new_rbac: false } as any), invoke
deleteOrganization(orgId) and assert result.error is null and mockFrom was
calledWith('orgs') (same expectation as the current test); use it.concurrent for
parity with other tests and unique orgId values like 'org-legacy-super-admin'
and 'org-legacy-owner'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b9757928-7403-4df5-8f51-04ca7f81f5a5

📥 Commits

Reviewing files that changed from the base of the PR and between df22b1d and a526add.

📒 Files selected for processing (3)
  • src/pages/settings/organization/index.vue
  • src/stores/organization.ts
  • tests/organization-store-delete.unit.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/settings/account/index.vue (1)

662-666: ⚠️ Potential issue | 🟡 Minor

Use translation keys instead of hardcoded English text.

These strings should use the t() function with translation keys instead of hardcoded English text. As per coding guidelines, all user-facing text must use translation keys.

🔧 Suggested fix
       <div class="text-base text-gray-500 dark:text-gray-400">
         <p class="mb-4">
-          This action cannot be undone. Your account and all associated data will be permanently deleted.
+          {{ t('delete-account-warning') }}
         </p>
         <p class="font-medium text-gray-700 dark:text-gray-300">
-          Your account will be deleted after 30 days
+          {{ t('delete-account-delay-notice') }}
         </p>

Add corresponding keys to messages/en.json:

{
  "delete-account-warning": "This action cannot be undone. Your account and all associated data will be permanently deleted.",
  "delete-account-delay-notice": "Your account will be deleted after 30 days"
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/settings/account/index.vue` around lines 662 - 666, Replace the
hardcoded English paragraphs in the template with calls to the translation
helper (t) using new keys (e.g., "delete-account-warning" and
"delete-account-delay-notice") so the template uses t('delete-account-warning')
and t('delete-account-delay-notice') instead of raw strings; then add those same
keys and English values to the messages/en.json locale file so the translations
are available at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/pages/settings/account/index.vue`:
- Around line 662-666: Replace the hardcoded English paragraphs in the template
with calls to the translation helper (t) using new keys (e.g.,
"delete-account-warning" and "delete-account-delay-notice") so the template uses
t('delete-account-warning') and t('delete-account-delay-notice') instead of raw
strings; then add those same keys and English values to the messages/en.json
locale file so the translations are available at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58183400-2f79-4633-8bfc-ab8d7d8f4b80

📥 Commits

Reviewing files that changed from the base of the PR and between a526add and 77acc5d.

📒 Files selected for processing (6)
  • src/components/AdminOnlyModal.vue
  • src/pages/settings/account/index.vue
  • src/pages/settings/organization/Members.vue
  • src/pages/settings/organization/Plans.vue
  • src/stores/organization.ts
  • tests/organization-store-delete.unit.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/organization-store-delete.unit.test.ts
  • src/stores/organization.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/stores/main.ts (1)

9-9: Use ~/ alias for imports per coding guidelines.

The import should use the path alias instead of a relative path.

Suggested fix
-import { createDeferredPromise } from '../utils/promise'
+import { createDeferredPromise } from '~/utils/promise'

As per coding guidelines: "Import using ~/ alias for src/ directory in frontend code instead of relative paths"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/main.ts` at line 9, Update the import in src/stores/main.ts to use
the project alias for src instead of a relative path: replace the relative
import that imports createDeferredPromise (symbol: createDeferredPromise) from
'../utils/promise' with the aliased path using '~/utils/promise' so it follows
the frontend coding guideline of using the `~/` alias for imports rooted at src.
src/stores/organization.ts (1)

8-8: Use ~/ alias for imports per coding guidelines.

The import should use the path alias instead of a relative path.

Suggested fix
-import { createDeferredPromise } from '../utils/promise'
+import { createDeferredPromise } from '~/utils/promise'

As per coding guidelines: "Import using ~/ alias for src/ directory in frontend code instead of relative paths"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/organization.ts` at line 8, The import of createDeferredPromise
should use the project alias for the src directory; replace the relative import
"import { createDeferredPromise } from '../utils/promise'" with an aliased
import that starts with "~/" (e.g., import { createDeferredPromise } from
'~/utils/promise') so the store module references the promise utility via the
codebase alias.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/stores/main.ts`:
- Line 9: Update the import in src/stores/main.ts to use the project alias for
src instead of a relative path: replace the relative import that imports
createDeferredPromise (symbol: createDeferredPromise) from '../utils/promise'
with the aliased path using '~/utils/promise' so it follows the frontend coding
guideline of using the `~/` alias for imports rooted at src.

In `@src/stores/organization.ts`:
- Line 8: The import of createDeferredPromise should use the project alias for
the src directory; replace the relative import "import { createDeferredPromise }
from '../utils/promise'" with an aliased import that starts with "~/" (e.g.,
import { createDeferredPromise } from '~/utils/promise') so the store module
references the promise utility via the codebase alias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c15ba05-a68a-4b11-b442-620327b48c25

📥 Commits

Reviewing files that changed from the base of the PR and between 77acc5d and 885eaf7.

📒 Files selected for processing (3)
  • src/stores/main.ts
  • src/stores/organization.ts
  • src/utils/promise.ts
✅ Files skipped from review due to trivial changes (1)
  • src/utils/promise.ts

@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit c811189 into main Mar 25, 2026
15 checks passed
@riderx riderx deleted the codex/fix-org-delete-rbac-check branch March 25, 2026 04:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant