Skip to content

feat(credentials): organization-level shared credentials - #9

Open
suda wants to merge 11 commits into
devfrom
feature/org-credentials
Open

feat(credentials): organization-level shared credentials#9
suda wants to merge 11 commits into
devfrom
feature/org-credentials

Conversation

@suda

@suda suda commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

Credentials (PM keys, GitHub/GitLab tokens, Sentry, engine/LLM keys) can now be defined once per organization and are inherited by every project in it. A project-level credential with the same env var key overrides the org value for that project — so a new project needs zero credential re-entry.

How it works

  • New org_credentials table (migration 0062), values encrypted with AAD = org_id (project credentials keep AAD = project_id).
  • Inheritance lives in the two chokepoint resolvers (resolveProjectCredential / resolveAllProjectCredentials). Everything downstream inherits org values automatically: worker env injection, secretBuilder, PM discovery, webhook signature verification, GitHub personas, hasIntegration checks. Zero worker-side changes — the router merges tiers before flattening CASCADE_CREDENTIAL_KEYS.
  • tRPC: organization.credentials.{list,set,delete}, gated by per-org admin role (same refinement pattern as users.ts). List returns masked previews only.
  • projects.credentials.list now merges the org tier: each row carries source: 'project' | 'org' and hasOrgFallback; existing consumers keep working unchanged.
  • UI: new Settings → Credentials page with a grouped catalog (Source Control / Project Management / Alerting from the credential-role registry, Engines/LLM from ENGINE_SECRETS) plus free-form custom keys. ProjectSecretField shows an "Inherited from org ****xxxx" badge, an "Override organization value..." placeholder, and revert-to-org delete semantics.
  • CLI: cascade org credentials-set / credentials-list / credentials-delete.

Notes for review

  • Webhook verification behavior change: a project without e.g. GITHUB_WEBHOOK_SECRET now inherits the org secret — a previously-unverified webhook path may start enforcing verification. Intended.
  • Codex token refresh (CODEX_AUTH_JSON write-back) intentionally stays project-scoped, creating an override over an org-inherited seed (documented inline).
  • Deleting a project override naturally reverts to the org value — projects.credentials.delete needed no changes.
  • Historical context: an org-level credentials table existed pre-0040 and was collapsed into the project tier; this re-introduces the tier with live inheritance instead of a one-time backfill.

Testing

  • Unit: 10,979 passed (new: orgCredentialsRepository CRUD/AAD tests; fallback matrix in credentialsRepository; org-admin gating + masking in organization router; DTO merge cases in projects router).
  • Integration: full credential inheritance matrix green (org-only inherit, project override wins, delete-override reverts, per-tier AAD under encryption).
  • Pre-existing failures unrelated to this change: coalesce-bullmq + 2 github-personas tests require a local Redis (confirmed failing on clean tree).

🤖 Generated with Claude Code

Cascade Bot and others added 11 commits August 2, 2026 13:23
- Use array index as React key in claude-code-limits.tsx to avoid
  reconciliation bugs when tokens share the same trailing 4 chars;
  suppress noArrayIndexKey lint rule with explanation
- Fix contradictory JSDoc on cacheByToken — remove false claim that
  raw tokens are not stored as cache keys
- Remove unused createMockUser import in claudeCodeLimits.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous implementation called https://api.anthropic.com/api/account
which does not exist in Anthropic's API for OAuth tokens. The Claude Code
CLI actually uses https://api.anthropic.com/api/oauth/profile to fetch
subscription/organization info.

Update fetchClaudeSubscriptionLimits to call the correct endpoint and
parse the organization.organization_type field for the plan name. Per-token
usage stats (messages/tokens used vs. limit) are not available from this
endpoint, so those fields return 0 and the UI hides them automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split long lines in formatResetDate and extraUsage rendering to satisfy
biome's line-length formatting rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract parseBuckets() and parseExtraUsage() helpers from
  fetchClaudeSubscriptionLimits() to reduce cognitive complexity from
  16 to below the max of 15
- Fix import order in claude-code-limits.tsx (Separator before trpc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Credentials can now be defined once per organization and are inherited by
every project in it; a project-level credential with the same env var key
overrides the org value for that project.

- New org_credentials table (migration 0062) with AAD = org_id encryption
- Org fallback wired into the two chokepoint resolvers
  (resolveProjectCredential / resolveAllProjectCredentials), so worker env
  injection, secretBuilder, PM discovery, webhook signature verification,
  personas, and hasIntegration checks all inherit org values with zero
  worker-side changes
- organization.credentials.{list,set,delete} tRPC procedures gated by
  per-org admin role (users.ts refinement pattern)
- projects.credentials.list now merges the org tier and reports
  source: 'project' | 'org' plus hasOrgFallback per row
- New /settings/credentials page with a grouped key catalog (SCM, PM,
  alerting from the credential-role registry; engines from ENGINE_SECRETS)
  plus custom keys
- ProjectSecretField shows 'Inherited from org' state, override placeholder,
  and revert-to-org delete semantics
- CLI: cascade org credentials-set / credentials-list / credentials-delete
- Shared maskCredentialValue helper replaces the inline masking expression

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reworks the Claude Code subscription limits feature (previously a
superadmin-only sidebar widget) into the credential settings surfaces:

- claudeCodeLimits.forOrg (org-admin gated, same audience as the org
  credentials page): usage for every token source in the org — the shared
  org credential, each project-level override (with project name), and the
  server env token — with source attribution instead of value dedup.
- claudeCodeLimits.forProject (project access): usage for the credential
  candidates one project can run on — its override, the inherited org
  token, and the env token — with the credential-system winner marked
  active, rendered as a picker preview under CLAUDE_CODE_OAUTH_TOKEN on
  the project engine tab.
- Failed sources now return limits: null instead of being silently
  dropped, so the UI can distinguish 'no data' from 'not configured'.
- Shared ClaudeUsageCard display component; sidebar section deleted.
- listAllClaudeCodeCredentials now returns projectName for attribution.
- Org-role refinement helpers extracted to _shared/orgRole.ts (reused by
  organization.credentials.* and claudeCodeLimits.forOrg).
- Anthropic client added to the auth-header provenance accept list (LLM
  subscription API, not a PM/SCM/alerting integration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses four adversarially-verified review findings:

- Drop the server env token from both limits procedures. It was doubly
  wrong: the tRPC handlers run in the dashboard service while workers get
  the ROUTER service's env (the dashboard's view can be wrong in both
  directions), and it exposed a host-level operator secret's usage/billing
  data to tenant org members. Operators who want env-token usage visible
  should store it as an org credential — the feature's whole point.
- Fix the active flag semantics that followed from env removal: project
  override wins, else org token is active.
- Per-row decrypt resilience: listAllClaudeCodeCredentials skips
  undecryptable rows with a warning instead of 500ing the endpoint; new
  getProjectOwnCredential reads exactly one project-tier row (no org
  fallback, null on decrypt failure) instead of decrypting every project
  credential just to find the token; forOrg treats an undecryptable org
  token as absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@suda

suda commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Scope extended: the Claude Code subscription limits feature (previously PR #3's superadmin sidebar widget) is now folded into the credential views on this branch:

  • claudeCodeLimits.forOrg (org-admin gated): usage for the org-level shared token and each project-level override, with source attribution — rendered as a Claude Code Usage section on Settings → Credentials.
  • claudeCodeLimits.forProject: usage for the credential candidates a project can run on (its override vs the inherited org token) with the active one marked — rendered as a picker preview under CLAUDE_CODE_OAUTH_TOKEN on the project engine tab.
  • Sidebar widget deleted. Server env token deliberately not surfaced (dashboard env ≠ router env that workers get, and it's an operator secret whose billing data shouldn't reach tenant org members — store it as an org credential instead).
  • Per-row decrypt resilience: one corrupted credential row no longer 500s the limits endpoints.

Adversarial review workflow ran over the diff (3 lenses, per-finding verification); all 4 confirmed findings fixed in fb628fa.

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