Skip to content

feat(cli): migrate CLI auth checks to RBAC-aware permissions#1983

Merged
riderx merged 5 commits intomainfrom
feat/rbac-cli-permissions
Apr 29, 2026
Merged

feat(cli): migrate CLI auth checks to RBAC-aware permissions#1983
riderx merged 5 commits intomainfrom
feat/rbac-cli-permissions

Conversation

@Dalanir
Copy link
Copy Markdown
Contributor

@Dalanir Dalanir commented Apr 29, 2026

Summary (AI generated)

  • Port of CLI PR #571 (feat/rbac-cli-permissions) into the monorepo cli/ workspace
  • Migrates all CLI auth checks to RBAC-aware backend wrappers (hasCliPermission, filterOrgsByPermission, getOrganizationListWithPermission, resolveUserIdFromApiKey)
  • Makes login, user account, and organization add identity-only (no legacy key-mode gating)
  • Uses org.create_app permission key for app creation and init flow
  • Legacy fallback semantics preserved for older API keys
  • Includes build/request.ts refactoring to reduce cognitive complexity and extract helpers

Motivation (AI generated)

The CLI repository has been integrated into the capgo monorepo as the cli/ workspace. The RBAC permission changes from CLI PR #571 need to be applied here instead of the standalone CLI repo, so that the CLI permission model stays aligned with the backend RBAC system being developed on rbac-api-keys.

Business Impact (AI generated)

This enables fine-grained API key permissions for CLI users, allowing organizations to restrict which operations each API key can perform. This is a prerequisite for the full RBAC API key system, improving security posture for enterprise customers.

Test Plan (AI generated)

  • Verify CLI builds successfully in the monorepo (bun run cli:build)
  • Verify CLI lint passes (bun run cli:check)
  • Run CLI E2E tests (bun test:cli)
  • Verify RBAC permission checks work with new API key types
  • Verify legacy API keys still work with fallback semantics

Generated with AI

Summary by CodeRabbit

  • Improvements

    • Standardized CLI auth to resolve users from API keys and added centralized permission helpers for org/app/channel scoping.
    • Improved organization selection by filtering orgs by permissions and added clearer 2FA/access helpers.
    • More robust build request handling with consolidated WebSocket message decoding, routing, and acknowledgement logic.
  • Behavioral Changes

    • More explicit insufficient-permission messages and consistent respect for silent/quiet flags.

Port CLI PR #571 (Cap-go/CLI) changes into monorepo cli/ workspace.

Migrates all CLI auth checks to RBAC-aware backend wrappers
(hasCliPermission, filterOrgsByPermission, getOrganizationListWithPermission,
resolveUserIdFromApiKey). Makes login, user account, and organization add
identity-only (no legacy key-mode gating). Uses org.create_app permission
key for app creation and init flow. Legacy fallback semantics preserved
for older API keys.

Includes build/request.ts refactoring to reduce cognitive complexity and
extract helpers (dispatchCustomMsg, WsEntry interface).
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d362866-74c8-49f6-b139-7c7c57285bc0

📥 Commits

Reviewing files that changed from the base of the PR and between d7fdae9 and 3feb98b.

📒 Files selected for processing (1)
  • cli/src/init/command.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • cli/src/init/command.ts

📝 Walkthrough

Walkthrough

Replaces legacy API-key validation with a CLI permission system: adds resolveUserIdFromApiKey, hasCliPermission/assertCliPermission/assertOrgPermission, org/app permission helpers, updates many CLI commands to use these helpers, and refactors build WebSocket message handling and log confirmation logic.

Changes

Cohort / File(s) Summary
Core Permission System
cli/src/utils.ts
Removes checkKey/verifyUser. Adds resolveUserIdFromApiKey, hasCliPermission, assertCliPermission, assertOrgPermission, org filtering/selection helpers, and getAccessibleAppsForApiKey.
App commands
cli/src/app/add.ts, cli/src/app/delete.ts, cli/src/app/list.ts, cli/src/app/set.ts
Swap verifyUserresolveUserIdFromApiKey; add uses getOrganizationWithPermission(...,'org.create_app') and assertCliPermission('org.create_app'); set changes org resolution to getOrganizationId(...) and removes user_id equality from update filter.
Build command
cli/src/build/request.ts
Replace permission check with assertCliPermission('app.build_native'). Large refactor of WebSocket handling: centralized decoding, message routing, custom message dispatch, confirmation gating, and terminal status handling.
Bundle commands
cli/src/bundle/cleanup.ts, cli/src/bundle/compatibility.ts, cli/src/bundle/delete.ts, cli/src/bundle/list.ts, cli/src/bundle/unlink.ts, cli/src/bundle/upload.ts
Uniformly replace verifyUser with resolveUserIdFromApiKey; preserve downstream permission checks like checkAppExistsAndHasPermissionOrgErr.
Channel commands
cli/src/channel/add.ts, cli/src/channel/currentBundle.ts, cli/src/channel/delete.ts, cli/src/channel/list.ts, cli/src/channel/set.ts
Derive userId via resolveUserIdFromApiKey across channel flows; existing org/app permission enforcement unchanged.
Organization commands
cli/src/organization/add.ts, cli/src/organization/delete.ts, cli/src/organization/list.ts, cli/src/organization/members.ts, cli/src/organization/set.ts
add/list use resolveUserIdFromApiKey. delete replaces owner-only deletion logic with assertOrgPermission(...,'org.delete'). members/set use assertOrgPermission for scoped checks.
Init, Login & User
cli/src/init/command.ts, cli/src/login.ts, cli/src/user/account.ts
init refactors org selection to permission-based flow (getOrganizationListWithPermission / hasCliPermission('org.create_app')) and improves resume handling; login/user derive user id via resolveUserIdFromApiKey.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as "CLI Command"
  participant Utils as "cli/src/utils"
  participant DB as "Supabase RPC / DB"
  participant Action as "Command Action"

  CLI->>Utils: resolveUserIdFromApiKey(apikey)
  Utils->>DB: get_user_id(apikey)
  DB-->>Utils: userId
  Utils-->>CLI: userId
  CLI->>Utils: assertCliPermission(permissionKey, scope)
  Utils->>DB: cli_check_permission(permissionKey, scope)
  DB-->>Utils: allowed / denied
  Utils-->>CLI: return or throw
  CLI->>Action: perform operation (uses org/app ids)
  Action->>DB: insert/update/delete rows / emit events
  DB-->>Action: result
  Action-->>CLI: command output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hopping through flags and permission trees,

I sniff apikeys on the breeze.
Resolve, assert, then onward we prance,
Org and app checks lead the dance.
A carrot for each secure CLI chance. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 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 comprehensive summary of changes, motivation, and business impact. However, the test plan section contains unchecked checkboxes rather than completed test results, and the description template's required 'Test plan' section lacks concrete steps. Complete the test plan section by checking off verified steps or providing specific reproduction steps. Ensure all checklist items are addressed with concrete evidence of testing.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: migrating CLI authentication checks to RBAC-aware permissions, which is the primary focus of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/rbac-cli-permissions

Review rate limit: 1/5 review remaining, refill in 36 minutes and 42 seconds.

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented Apr 29, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing feat/rbac-cli-permissions (3feb98b) with main (0fe72c4)

Open in CodSpeed

Comment thread cli/src/init/command.ts Fixed
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)
cli/src/init/command.ts (1)

1285-1297: ⚠️ Potential issue | 🔴 Critical

Guard against zero permitted organizations before allowedOrganizations[0].

If allowedOrganizations.length === 0, Line 1297 dereferences allowedOrganizations[0].gid and crashes. Please handle the empty-permission case explicitly (friendly message + exit/error).

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

In `@cli/src/init/command.ts` around lines 1285 - 1297, The code assumes
allowedOrganizations has at least one element when assigning organizationUidRaw;
add an explicit guard for allowedOrganizations.length === 0 before using
allowedOrganizations[0] (e.g., check length, display a friendly error message
and exit or throw), then keep the existing branch that calls pSelect when length
> 1 and uses allowedOrganizations[0].gid when length === 1; ensure the check
happens before the current ternary so allowedOrganizations[0].gid is never
dereferenced on an empty array.
🧹 Nitpick comments (1)
cli/src/channel/add.ts (1)

37-40: Resolve the API key once and reuse the result.

This path looks up the same identity twice. Keep the first return value in userId and reuse it for created_by to avoid an extra lookup on every channel creation.

♻️ Proposed fix
-  await resolveUserIdFromApiKey(supabase, options.apikey)
+  const userId = await resolveUserIdFromApiKey(supabase, options.apikey)
   await checkAppExistsAndHasPermissionOrgErr(supabase, options.apikey, appId, OrganizationPerm.admin, silent, true)
@@
-  const userId = await resolveUserIdFromApiKey(supabase, options.apikey)
-
   const res = await createChannel(supabase, {

Also applies to: 52-53

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

In `@cli/src/channel/add.ts` around lines 37 - 40, The code calls
resolveUserIdFromApiKey twice causing an extra lookup; capture its return once
(e.g., const userId = await resolveUserIdFromApiKey(supabase, options.apikey))
right after creating the Supabase client and then reuse userId wherever
created_by is set instead of calling resolveUserIdFromApiKey again (also update
the similar duplicate at the second occurrence around the other block
referencing created_by), leaving calls to check2FAComplianceForApp and
checkAppExistsAndHasPermissionOrgErr unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/build/request.ts`:
- Around line 1130-1134: Remove the redundant identity validation call
resolveUserIdFromApiKey(...) before asserting permissions; the call's return
value is unused and can block valid non-user API keys, so delete the await
resolveUserIdFromApiKey(supabase, options.apikey, silent) line and leave the
await assertCliPermission(supabase, options.apikey, 'app.build_native', { appId
}, { message: `Insufficient permissions to request a native build for app
${appId}`, silent }) invocation intact so permission checks rely solely on
cli_check_permission as in assertCliPermission.

In `@cli/src/bundle/compatibility.ts`:
- Line 67: Remove the redundant identity validation call to
resolveUserIdFromApiKey(supabase, enrichedOptions.apikey) since
checkAppExistsAndHasPermissionOrgErr(...) already performs API-key-to-user
resolution and enforces permissions; delete that call (and any unused userId
variable it would assign) from the flow where enrichedOptions.apikey and
supabase are passed, leaving checkAppExistsAndHasPermissionOrgErr as the sole
validation step so behavior and RPC-based permission checks remain unchanged.

In `@cli/src/init/command.ts`:
- Line 30: Remove the unused import getInstalledVersion from the import list in
command.ts; locate the import statement that currently brings in
createSupabaseClient, findBuildCommandForProjectType, ..., getInstalledVersion,
validateIosUpdaterSync and delete only the getInstalledVersion identifier (and
any now-incorrect trailing commas) so the file no longer imports an unused
symbol and lint/CI will pass.

---

Outside diff comments:
In `@cli/src/init/command.ts`:
- Around line 1285-1297: The code assumes allowedOrganizations has at least one
element when assigning organizationUidRaw; add an explicit guard for
allowedOrganizations.length === 0 before using allowedOrganizations[0] (e.g.,
check length, display a friendly error message and exit or throw), then keep the
existing branch that calls pSelect when length > 1 and uses
allowedOrganizations[0].gid when length === 1; ensure the check happens before
the current ternary so allowedOrganizations[0].gid is never dereferenced on an
empty array.

---

Nitpick comments:
In `@cli/src/channel/add.ts`:
- Around line 37-40: The code calls resolveUserIdFromApiKey twice causing an
extra lookup; capture its return once (e.g., const userId = await
resolveUserIdFromApiKey(supabase, options.apikey)) right after creating the
Supabase client and then reuse userId wherever created_by is set instead of
calling resolveUserIdFromApiKey again (also update the similar duplicate at the
second occurrence around the other block referencing created_by), leaving calls
to check2FAComplianceForApp and checkAppExistsAndHasPermissionOrgErr unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9eb6a31-895e-449e-898f-7180a860b5b6

📥 Commits

Reviewing files that changed from the base of the PR and between 5183a02 and 3384fff.

📒 Files selected for processing (25)
  • cli/src/app/add.ts
  • cli/src/app/delete.ts
  • cli/src/app/list.ts
  • cli/src/app/set.ts
  • cli/src/build/request.ts
  • cli/src/bundle/cleanup.ts
  • cli/src/bundle/compatibility.ts
  • cli/src/bundle/delete.ts
  • cli/src/bundle/list.ts
  • cli/src/bundle/unlink.ts
  • cli/src/bundle/upload.ts
  • cli/src/channel/add.ts
  • cli/src/channel/currentBundle.ts
  • cli/src/channel/delete.ts
  • cli/src/channel/list.ts
  • cli/src/channel/set.ts
  • cli/src/init/command.ts
  • cli/src/login.ts
  • cli/src/organization/add.ts
  • cli/src/organization/delete.ts
  • cli/src/organization/list.ts
  • cli/src/organization/members.ts
  • cli/src/organization/set.ts
  • cli/src/user/account.ts
  • cli/src/utils.ts

Comment thread cli/src/build/request.ts Outdated
Comment thread cli/src/bundle/compatibility.ts Outdated
Comment thread cli/src/init/command.ts Outdated
Dalanir added 2 commits April 29, 2026 16:03
… compatibility

assertCliPermission and checkAppExistsAndHasPermissionOrgErr already
validate API key permissions independently via cli_check_permission RPC.
The prior identity gate was redundant and could incorrectly reject
scoped or service keys that have valid permissions but don't resolve
to a user.
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/src/init/command.ts`:
- Around line 3969-3993: When falling back to a new organization (the branches
that call selectOrganizationForInit and discardResumedState), also clear the
restored onboarding metadata so stale resumed values (resumed.appId,
resumed.packageJson, resumed.channelId, resumed.platform, etc.) cannot leak into
the fresh flow; either extend discardResumedState() to reset the full resumed
object/state restored by tryResumeOnboarding(), or call a new
clearResumedState() immediately after organization = await
selectOrganizationForInit(...) (and in the blocked/permission branches) to wipe
resumed before proceeding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbb69a78-8372-445a-9120-e1d91753a877

📥 Commits

Reviewing files that changed from the base of the PR and between 3384fff and 2ab0a19.

📒 Files selected for processing (1)
  • cli/src/init/command.ts

Comment thread cli/src/init/command.ts
Dalanir added 2 commits April 29, 2026 16:28
…ng state

discardResumedState() now resets all globals that tryResumeOnboarding()
may have restored (globalAppId, globalPathToPackageJson, globalChannelName,
globalPlatform, globalDelta, globalCurrentVersion, globalOrgId,
globalOrgName) and sets resumed to undefined. This prevents stale values
from a previous onboarding session leaking into the fresh org flow.
…signment

Capture resumed into a const resumedSnapshot at the top of the if-block
so TypeScript can narrow the type correctly across await boundaries.
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit 9e1151f into main Apr 29, 2026
50 checks passed
@riderx riderx deleted the feat/rbac-cli-permissions branch April 29, 2026 14:49
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