Skip to content

feat(RBAC): channel access management#1585

Merged
Dalanir merged 37 commits intomainfrom
channel-access-management
Feb 18, 2026
Merged

feat(RBAC): channel access management#1585
Dalanir merged 37 commits intomainfrom
channel-access-management

Conversation

@Dalanir
Copy link
Copy Markdown
Contributor

@Dalanir Dalanir commented Feb 5, 2026

Summary (AI generated)

  • Added channel permission overrides (DB migration + RBAC override resolution)
  • Introduced UI to manage channel permissions and enforced read/promote gating in channel list + channel detail
  • Updated app index “App Access” column to show app-scoped role

Motivation (AI generated)

We need channel-scoped permission overrides to allow fine-grained access (e.g., allow development only), and surface this in the UI.

Business Impact (AI generated)

Enables safer delegation for app teams, reducing accidental production changes while keeping development velocity.

Test Plan (AI generated)

  • Verify channel permission overrides can be created/updated/removed via UI
  • Verify channel list disables name click when channel.read is denied
  • Verify channel detail bundle association is disabled without channel.promote_bundle
  • Verify app index shows app-scoped role (not org role)
  • Run relevant frontend checks (if applicable)

Generated with AI

Summary by CodeRabbit

  • New Features
    • Channel-level permissions UI to view and edit per-channel allow/deny overrides.
  • Improvements
    • Per-channel controls now gate promote/read actions and disable promotion flows when not permitted.
    • Dynamic tooltips/titles on action buttons for improved accessibility.
    • French locale updated to use consistent "canal" wording and new channel-permissions strings.
  • Chores
    • Backend persistence and direct permission checks for channel-scoped overrides; added local ignore rule.

Dalanir and others added 22 commits February 3, 2026 18:37
- Add SearchInput component with icon for consistent search UI
- Add RoleSelect component for role dropdown selection
- Both components support v-model and are fully typed
- Reduces code duplication across modals and forms

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Reusable modal for role/permission selection
- Integrates with RoleSelect component
- Handles validation and loading states
- Supports custom titles and descriptions
- Emits confirm/cancel events for flexible usage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
AppAccess.vue:
- Replace search input with SearchInput component
- Replace role select with RoleSelect component
- Replace Edit Role Modal with RoleSelectionModal
- Remove duplicate modal code (~40 lines)
- Refactor updateRoleBinding to handleEditRoleConfirm

Members.vue:
- Replace search input in App Access modal with SearchInput
- Replace role select with RoleSelect component
- Remove unused selectedOrgRoleSummary computed property

Benefits:
- Reduced code duplication
- Consistent UI across modals
- Easier maintenance and testing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Import getRbacRoleI18nKey for proper role translation
- Normalize role names (remove invite_ prefix)
- Use i18n translations when available
- Fallback to human-readable format (replace _ with spaces)
- Fixes display of roles like "app_admin" → "App Admin"

Applies the same display logic used in Members.vue and AppAccess.vue
for consistent user experience across the application.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Change middleware from '/' to '*' for useCors and middlewareAuth
- Ensures middleware applies to all routes in groups, role_bindings, and roles
- Fixes potential CORS and auth issues on nested routes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Migration:
- Remove app, bundle, and channel scope permissions from org_member role
- Update role description to reflect org-only access
- Ensures org_member has no direct app access by default

Seed:
- Enable RBAC for test organization
- Remove unused variables (v_org, v_migration_result)

This enforces the principle that org_member should only have
org-level access and requires explicit app-level role assignments.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove app-role-hint key from all language files
- Key was not referenced in the codebase
- Cleanup to reduce translation maintenance burden

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix import order in AppAccess.vue (SearchInput before RoleSelectionModal)
- Fix static class in SearchInput.vue (separate static class attribute)
- Fix inconsistent quote-props in RoleSelectionModal.vue emit types
- Fix indentation errors in Members.vue template (79 errors total)

All errors auto-fixed with eslint --fix

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove invalid default string argument from t() calls in Members.vue.
The vue-i18n t() function expects (key, interpolationObject) not
(key, defaultString, interpolationObject).
After removing app/channel/bundle permissions from org_member role,
the apps table INSERT policy was still checking for app-level admin
permissions. This caused RLS violations when creating apps since the
app_id doesn't exist yet during creation.

Updated the policy to check org-level write permissions instead,
which maps to org.update_settings in RBAC and aligns with the API
endpoint's permission check.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previous attempt modified an already-applied migration, which
doesn't get re-run in CI. This creates a new migration with a
current timestamp to properly fix the RLS policy for app creation.

Changes:
- Remove RLS policy fix from old migration (already applied)
- Create new migration 20260203220918 with the RLS policy fix
- Policy now checks org-level 'write' permissions instead of
  app-level 'admin' permissions for INSERT operations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When creating a new app, the app_id doesn't exist yet, so we can't use
get_identity_org_appid which expects an app_id parameter. Instead, use
get_identity_org_allowed which only needs the org_id.

This fixes the RLS policy to properly check org-level 'write' permissions
when creating apps, which admins and super_admins have through RBAC.

Also added debugging to expose-metadata test to help diagnose any remaining
issues with app creation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@Dalanir Dalanir self-assigned this Feb 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds channel-scoped permission overrides end-to-end: DB schema, RLS policies, and a direct RBAC check function; backend init tweak; client UI and components for managing per-principal per-channel permissions; locale additions (EN/FR); and client-side permission caches and gating.

Changes

Cohort / File(s) Summary
Localization
messages/en.json, messages/fr.json
Added 16 English channel-permission strings; updated French translations to use “canal” and added channel-permission strings, labels and error messages.
DB schema & RBAC function
supabase/migrations/20260204181424_add_channel_permission_overrides.sql
New channel_permission_overrides table, indexes, RLS policies, role_permission inserts, and new public.rbac_check_permission_direct(...) function implementing channel-level override checks and legacy fallbacks.
Access control UI & logic
src/components/tables/AccessTable.vue
New channel-permissions modal, channel override state, load/update flows (upsert/delete), search/filter, and UI wiring for editing per-principal channel permissions.
Channel permission caching & gating
src/components/tables/ChannelTable.vue
Batch fetches per-channel read/promote permissions, caches canReadChannel/canPromoteChannel, and gates name/promote actions with disabled state and tooltips.
App RBAC mapping
src/components/tables/AppTable.vue
Adds appRoleByAppId, loadAppRoles() RPC call and logic to render per-app role labels with loading state and fallbacks.
Table action API & templates
src/components/comp_def.ts, src/components/DataTable.vue
Added optional `title?: string
Small Vue imports
src/components/forms/RoleSelect.vue, src/components/modals/RoleSelectionModal.vue
Added computed, ref, watch imports to support reactive logic; no API surface changes.
Page-level permission guard
src/pages/app/[app].channel.[channel].vue
Refactored canPromoteBundle to check channelId (use id.value), added early permission guard in openSelectVersion(), and disabled button styling when promotion is not allowed.
Backend init change
supabase/functions/_backend/private/roles.ts
Switched Hono app initialization to createHono('', version) and added version import.
Ignore config
.gitignore
Added .codex/ to .gitignore.

Sequence Diagram

sequenceDiagram
    participant UI as Channel Permissions UI
    participant Comp as AccessTable Component
    participant RPC as RPC Backend
    participant DB as Database

    UI->>Comp: Open permissions dialog for principal
    Comp->>RPC: request channels + overrides (principal_id)
    RPC->>DB: SELECT channels and channel_permission_overrides
    DB-->>RPC: channels + overrides
    RPC-->>Comp: channel list + overrides
    Comp->>UI: render channels and current selections

    UI->>Comp: Toggle permission for a channel
    Comp->>Comp: compute nextOverride (allow/deny/default)
    Comp->>RPC: updateChannelPermission(principal, channel, permission, is_allowed)
    alt explicit override needed
        RPC->>DB: UPSERT channel_permission_overrides
    else explicit override removed (matches default)
        RPC->>DB: DELETE channel_permission_overrides
    end
    DB-->>RPC: ack
    RPC-->>Comp: result
    Comp->>UI: update state, show toast
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through channels old and new,
I nudged some toggles, set permissions true,
Per-principal rules in tidy rows,
I guard the canals where bundles go,
🛡️✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Description check ❓ Inconclusive The PR description includes a summary of changes, but the test plan and checklist are AI-generated and incomplete (checkboxes unchecked), missing manual testing steps and verification details. Complete the test plan with specific manual testing steps and check off items as they are verified. Include how to reproduce channel permission override creation/update/removal in the UI.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(RBAC): channel access management' clearly summarizes the main change: adding channel-scoped access/permission management to the RBAC system.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch channel-access-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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.

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
supabase/functions/_backend/public/app/post.ts (1)

17-31: ⚠️ Potential issue | 🔴 Critical

Authorization bypass when owner_org is empty or falsy.

The permission check on line 29 uses body.owner_org && ..., which means if owner_org is an empty string (""), null, or undefined, the check is skipped entirely and the insert proceeds without authorization verification. Add explicit validation for owner_org similar to app_id and name.

🔒 Proposed fix to add owner_org validation
   if (!body.name) {
     throw simpleError('missing_name', 'Missing name', { body })
   }
+  if (!body.owner_org) {
+    throw simpleError('missing_owner_org', 'Missing owner_org', { body })
+  }

   // Check if the user is allowed to create an app in this organization (auth context set by middlewareKey)
-  if (body.owner_org && !(await checkPermission(c, 'org.update_settings', { orgId: body.owner_org }))) {
+  if (!(await checkPermission(c, 'org.update_settings', { orgId: body.owner_org }))) {
     throw quickError(403, 'cannot_access_organization', 'You can\'t access this organization', { org_id: body.owner_org })
   }
🤖 Fix all issues with AI agents
In `@messages/fr.json`:
- Around line 1499-1514: The locale strings under keys like
"channel-permission-read-required", "channel-permission-associate-required",
"channel-permissions-title", "channel-permissions-description",
"channel-permissions-empty", "channel-permission-read",
"channel-permission-history", "channel-permission-associate",
"error-loading-channel-permissions", and "error-saving-channel-permissions" use
"canal" and should be changed to the consistent term "chaîne" (e.g., "Vous
n'avez pas la permission de voir cette chaîne.", "Associer le bundle" ->
"Associer le bundle à cette chaîne" if needed for clarity) — update each value
to replace "canal" with "chaîne" and ensure grammatical agreement in surrounding
text so all channel-related labels in this file use "chaîne".

In `@src/components/forms/RoleSelect.vue`:
- Around line 1-40: The component uses Vue's computed API but never imports it,
causing a runtime ReferenceError; in RoleSelect.vue add computed to the script
imports (alongside any existing imports such as useI18n) so that the computed()
calls for localValue and placeholderText resolve correctly and the component can
emit 'update:modelValue' and compute placeholder text without crashing.

In `@src/components/modals/RoleSelectionModal.vue`:
- Around line 1-5: The file is missing Vue reactive imports used later (ref,
watch, computed), so add an import from 'vue' that brings in ref, watch, and
computed at the top of the <script setup> block so usages of ref(), watch(), and
computed() (lines referencing those symbols) resolve and avoid runtime errors;
update the import section near useI18n and RoleSelect to include these symbols.
- Around line 76-116: Replace the current :open="open" binding with a template
ref (e.g., dialogRef) and call dialogRef.showModal() when the modal should open
and dialogRef.close() when it should close; in practice, remove the :open
attribute on the <dialog>, add a ref="dialogRef", and in the component watch the
open prop (or call from the method that toggles it) to invoke
dialogRef.showModal() and save document.activeElement as openerEl; on close
(handleClose / `@close`) call dialogRef.close() if not already closed and restore
focus to the saved openerEl; ensure the backdrop click still calls handleClose
and that handleConfirm continues to use selectedRole and isLoading as before.

In `@src/components/tables/AppTable.vue`:
- Around line 165-174: The role lookup uses the wrong key: appRoleByAppId is
keyed by app_id but the code calls appRoleByAppId.value.get(item.id), causing a
permanent fallback to "none"; change the lookup to use item.app_id (i.e.,
appRoleByAppId.value.get(item.app_id)) and keep the surrounding logic
(appRoleLoaded check, normalizedRole processing, getRbacRoleI18nKey) unchanged
so role resolution works correctly.

In `@src/components/tables/ChannelTable.vue`:
- Around line 114-117: Resetting canPromoteChannel only leaves stale read
permissions visible during reload; update the getData function to also clear
canReadChannel before refetching so both permission caches are reset. Inside
async function getData(), alongside the existing canPromoteChannel.value = {},
add a reset for canReadChannel (e.g., canReadChannel.value = {}) so the UI won't
briefly show stale read access while permissions load.

In `@supabase/functions/_backend/private/roles.ts`:
- Around line 10-13: Replace the direct instantiation "new Hono()" with the
shared initializer "createHono()" and update imports accordingly: change the
declaration "export const app = new Hono<MiddlewareKeyVariables>()" to use
"createHono<MiddlewareKeyVariables>()" so this private endpoint uses the common
backend initialization; keep the existing middleware registrations (app.use('*',
useCors) and app.use('*', middlewareAuth)) unchanged.

In `@supabase/functions/_backend/public/app/post.ts`:
- Around line 61-64: The catch block in post.ts currently logs the DB error via
logPgError(c, 'create_app', error) but then exposes the raw error message to the
client in simpleError; remove the sensitive payload by throwing a generic client
error (e.g. throw simpleError('cannot_create_app', 'Cannot create app') or
include only a safe, non-DB-specific code), keep the detailed logPgError(c,
'create_app', error) call for internal diagnostics, and if you need client
correlation include a non-sensitive identifier returned/created when logging
rather than error.message.

In `@supabase/migrations/20260203201308_rbac_org_member_no_app_access.sql`:
- Around line 202-247: The org 2FA enforcement (org.enforcing_2fa +
public.has_2fa_enabled(auth.uid())) is only checked inside the super-admin RBAC
branch; move this check earlier so it runs for all callers after resolving
calling_user_id/org and before any role-specific logic (i.e., immediately after
selecting org and after
public.get_identity_org_allowed()/public.check_min_rights validation), and
remove the duplicate check inside the super-admin branch (references:
invite_user_to_org, calling_user_id, org.enforcing_2fa,
public.has_2fa_enabled(auth.uid()), public.get_identity_org_allowed,
public.check_min_rights). Ensure the check logs the same denial code/message
(e.g., 'SUPER_ADMIN_2FA_REQUIRED' or a more generic '2FA_REQUIRED') and returns
'NO_RIGHTS' when 2FA is not enabled.

In `@supabase/migrations/20260204181424_add_channel_permission_overrides.sql`:
- Around line 36-81: The SELECT policy
(channel_permission_overrides_admin_select) and the ALL policy
(channel_permission_overrides_admin_write) lack correlation to
channel_permission_overrides.channel_id, allowing cross-app access; remove the
redundant SELECT policy and consolidate into a single ALL policy
(channel_permission_overrides_admin_write) that covers
SELECT/INSERT/UPDATE/DELETE, and modify both its USING and WITH CHECK clauses to
join channels ON channels.channel_id = channel_permission_overrides.channel_id
and include the same rbac_check_permission(...) call (using
rbac_perm_app_update_user_roles(), apps.owner_org, apps.app_id, NULL::bigint) so
permission is evaluated against the channel's app only.

In `@tests/expose-metadata.test.ts`:
- Around line 113-126: The error-logging branch for createResponse currently
calls createResponse.json() and falls back to createResponse.text(), which can
double-consume the response body; instead read the body once (await
createResponse.text()) into a variable, then attempt to JSON.parse that string
and log either the parsed object or the raw text along with
createResponse.status and the original parse error if JSON.parse fails; update
the block that references createResponse, errorBody, and errorText to use this
single-read approach so the body stream is not consumed twice.
🧹 Nitpick comments (7)
supabase/functions/_backend/public/app/post.ts (1)

42-69: Consider using Drizzle ORM instead of raw SQL for consistency with codebase patterns.

The coding guidelines specify using Drizzle ORM query patterns with schema from postgres_schema.ts for all database operations. The apps table is defined in the schema. While the parameterized query is safe from SQL injection, refactoring to Drizzle would provide better type safety and match the patterns used elsewhere in the backend:

const drizzleClient = getDrizzleClient(pgClient)
const data = await drizzleClient
  .insert(schema.apps)
  .values(dataInsert)
  .returning()
src/pages/app/[package].channel.[channel].vue (1)

660-664: Use DaisyUI d-btn for this interactive control.

This button is interactive and should use DaisyUI button classes to align with frontend guidelines.

♻️ Suggested tweak
-                <button
-                  v-if="channel"
-                  class="p-1 transition-colors border border-gray-200 rounded-md dark:border-gray-700 hover:bg-gray-50 hover:border-gray-300 dark:hover:border-gray-600 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-gray-200 dark:disabled:hover:border-gray-700"
-                  :disabled="!canPromoteBundle"
-                  `@click`="openSelectVersion()"
-                >
+                <button
+                  v-if="channel"
+                  class="d-btn d-btn-ghost d-btn-xs transition-colors border border-gray-200 rounded-md dark:border-gray-700 hover:bg-gray-50 hover:border-gray-300 dark:hover:border-gray-600 dark:hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-gray-200 dark:disabled:hover:border-gray-700"
+                  :disabled="!canPromoteBundle"
+                  `@click`="openSelectVersion()"
+                >

As per coding guidelines: Use DaisyUI components (d-btn, d-input, d-card) for interactive elements in Vue components.

src/components/forms/SearchInput.vue (1)

4-15: class prop won’t be populated; use a dedicated prop for input classes.

Vue treats class as a special attribute and doesn’t pass it as a prop, so props.class stays empty. Rename the prop (e.g., inputClass) or forward $attrs.class to the input.

♻️ Suggested refactor
 interface Props {
   modelValue: string
   placeholder?: string
   disabled?: boolean
-  class?: string
+  inputClass?: string
 }
 
 const props = withDefaults(defineProps<Props>(), {
   placeholder: 'Search...',
   disabled: false,
-  class: '',
+  inputClass: '',
 })
@@
-      class="w-full pl-10 d-input" :class="[props.class]"
+      class="w-full pl-10 d-input" :class="[props.inputClass]"

Also applies to: 34-34

src/components/tables/ChannelTable.vue (1)

249-272: Consider batching permission checks to reduce RPC fan‑out.

With N rows you currently execute 2N permission calls. If this table grows, a batch RPC or cache layer would reduce latency and load.

src/pages/settings/organization/Members.vue (1)

1219-1443: Consider batching role binding updates when multiple apps are selected.

The current loop updates/creates bindings sequentially, which can add noticeable latency for larger selections. A batched Promise.all (or a backend batch endpoint) would speed this up.

src/components/tables/AccessTable.vue (2)

200-220: Potential race condition if loadChannelPermissions is called rapidly.

The function sets channelOverridesLoading.value = true but doesn't guard against being invoked while already loading. If openChannelPermissions is called multiple times in quick succession (e.g., rapid clicks), concurrent fetches could overwrite each other's results.

🛡️ Proposed fix: Add early return guard
 async function loadChannelPermissions() {
   if (!selectedPrincipal.value)
     return
+  if (channelOverridesLoading.value)
+    return
 
   channelOverridesLoading.value = true

223-231: Type assertion as any bypasses type safety.

Using 'channel_permission_overrides' as any (also at Lines 309, 321) suggests the table isn't yet in ~/types/supabase.types. This works but loses type checking on the query response shape.

Consider regenerating Supabase types to include the new table, or add a local type assertion for the response to maintain some type safety.

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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
supabase/functions/_backend/private/roles.ts (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Code comments must be in English.

Lines 14, 26, and 70 have French comments (e.g., "Liste des rôles assignables", "Récupérer tous les rôles assignables", "Récupérer les rôles pour ce scope"). As per coding guidelines, all code comments must be in English regardless of chat language.

✏️ Proposed fix
-// GET /private/roles - Liste des rôles assignables
+// GET /private/roles - List assignable roles
-    // Récupérer tous les rôles assignables
+    // Fetch all assignable roles
-    // Récupérer les rôles pour ce scope
+    // Fetch roles for this scope
🤖 Fix all issues with AI agents
In `@src/components/tables/AppTable.vue`:
- Around line 75-78: The early-return block that checks userId and orgId resets
appRoleLoaded to false and returns, which can permanently block downstream code;
remove the appRoleLoaded.value = false assignment from that early-return so the
existing finally block controls the loaded state, and ensure the watcher or
effect that calls the function depends on userId and orgId (e.g., the reactive
watcher that invokes the loader) so the load re-runs when those IDs become
available; references: userId, orgId, appRoleByAppId, appRoleLoaded and the
watcher that triggers the load.
- Around line 72-111: The app-scoped roles loaded by loadAppRoles (into
appRoleByAppId and appRoleLoaded) are never used; update the app-perm column's
displayFunction to first check appRoleByAppId.value.get(item.app_id) and return
that if present, otherwise fall back to
organizationStore.getOrgByAppId(item.app_id)?.role; also use appRoleLoaded to
avoid flashing “unknown” by rendering a loading/placeholder value while
appRoleLoaded.value is false and only showing the resolved role once true.
Ensure you reference appRoleByAppId, appRoleLoaded, loadAppRoles and
organizationStore.getOrgByAppId in the change so the new logic replaces the
current org-only lookup.

In `@supabase/functions/_backend/private/roles.ts`:
- Line 9: The app initialization incorrectly calls createHono with a type
parameter; remove the generic and call createHono with the required positional
parameters (function name and version) to match the pattern in groups.ts and
role_bindings.ts — e.g., replace the line using
createHono<MiddlewareKeyVariables>() with createHono('roles', 'v1') (or the same
functionName/version pair used in the other files) so createHono is invoked with
two positional args and no type args.
🧹 Nitpick comments (2)
src/components/tables/AppTable.vue (1)

108-111: Duplicate watchEffect trigger — both watchers fire on the same condition.

This watchEffect and the one at Line 67-70 share the same guard (props.apps.length > 0), so every reactive change to props.apps fires both loadMauNumbers and loadAppRoles in parallel. That's likely fine functionally, but consider consolidating them into a single watcher for clarity, or at least adding a distinct reactive dependency (e.g. organizationStore.currentOrganization?.gid) to avoid re-fetching roles when only app data refreshes but the org hasn't changed.

src/components/tables/ChannelTable.vue (1)

250-273: Performance: N×2 sequential-per-row permission checks on every page load.

loadChannelPermissions fires 2 × rows.length concurrent checkPermissions calls (up to 20 for a full page of 10). If checkPermissions hits the backend each time, this can noticeably delay table rendering since getData awaits it before clearing isLoading.

Consider batching: if the permissions service supports checking multiple channels in one call, that would reduce round-trips. Otherwise, this is acceptable for a page size of 10 but worth keeping in mind if the offset grows.

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)
supabase/functions/_backend/private/roles.ts (1)

15-15: ⚠️ Potential issue | 🟡 Minor

Code comments must be in English.

Lines 15, 27, 54, and 71 contain French comments (e.g., "Liste des rôles assignables", "Récupérer tous les rôles assignables"). As per coding guidelines, all code comments must be in English regardless of the chat language.

♻️ Proposed fix
-// GET /private/roles - Liste des rôles assignables
+// GET /private/roles - List assignable roles
...
-    // Récupérer tous les rôles assignables
+    // Fetch all assignable roles
...
-// GET /private/roles/:scope_type - Liste des rôles par scope
+// GET /private/roles/:scope_type - List roles by scope type
...
-    // Récupérer les rôles pour ce scope
+    // Fetch roles for this scope

As per coding guidelines, "All code comments must be in English, regardless of the chat language."

Also applies to: 27-27, 54-54, 71-71

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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@supabase/migrations/20260204181424_add_channel_permission_overrides.sql`:
- Around line 238-242: The lookup of the API key is wrong: instead of querying
public.apikeys WHERE key = p_apikey (which fails for hashed keys), call the
existing helper public.find_apikey_by_value(p_apikey) to resolve either plain or
hashed keys and assign its returned rbac_id into v_apikey_principal; ensure you
handle the case where the helper returns NULL (i.e., no principal found) the
same way the code expects in the surrounding IF/THEN logic.

@sonarqubecloud
Copy link
Copy Markdown

@Dalanir Dalanir merged commit 2b40872 into main Feb 18, 2026
14 checks passed
@Dalanir Dalanir deleted the channel-access-management branch February 18, 2026 17:38
@coderabbitai coderabbitai bot mentioned this pull request Feb 23, 2026
6 tasks
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.

2 participants