feat(api-keys): support unscoped API keys (optional project_id) - #584
Merged
Conversation
API keys were hard-required to be scoped to a single project. This makes `project_id` optional: a key created without one is "unscoped" and spans every project its owner can reach, bounded by the intersection of the owner's permissions and the key's own attached policies. Multi-project access is now expressed through IAM policies rather than a per-key project. - ApiKey.projectId is now nullable (was NOT NULL) - POST /api-keys: project_id no longer required; omit or null → unscoped - PUT /api-keys/:id: project_id null clears the scope; a value (re)scopes it - auth: resolveProjectKey tolerates a null project and builds an unscoped resolveProjectIds that enumerates reachable projects via the key's own authorizer (which already intersects owner + key policies) - extracted the project-id resolver factories into authProjectResolvers.ts - OpenAPI, module docs, and smoke tests updated Scoped-key behavior is unchanged: a key with a project is still hard-locked to it. The intersection ceiling still applies — an unscoped key can never exceed its owner's permissions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DKN1qmitp2qFuGW1TzxkKg
Deploy Outputs
|
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
API keys were hard-required to be scoped to a single project. This PR makes
project_idoptional: a key created without one is unscoped and spans every project its owner can reach, bounded by the intersection of the owner's permissions and the key's own attached policies. Multi-project access is now expressed through IAM policies rather than a per-key project — as requested.The intersection ceiling is unchanged: an unscoped key can never exceed its owner's permissions. When
project_idis null and the key has no attached policies, effective permissions are just the owner's; with policies, it's the intersection of owner policies ∩ key policies — across all reachable projects.Behavior
project_idsetpolicy_idsBecause an unscoped key has no implicit project, requests that operate on a specific project must supply
project_idexplicitly (endpoints already return 400 in that case).Changes
ApiKey.projectIdis now nullable (wasNOT NULL).POST /api-keysno longer requiresproject_id(omit ornull→ unscoped).PUT /api-keys/:idacceptsproject_id: nullto clear scope, a value to (re)scope, omitted to leave unchanged.resolveProjectKeytolerates a null project (no more non-null assertion) and builds an unscopedresolveProjectIdsthat enumerates reachable projects through the key's own authorizer (which already intersects owner + key policies). Admin owner with no key boundary keeps the unrestricted fast path.auth.tsintoauthProjectResolvers.ts(keepsauth.tsunder themax-lineslimit; no behavior change).project_idmarked optional + nullable; SDK and CLI manifests regenerated (generated files are gitignored and rebuilt in CI).modules/api-keys.mdupdated (overview, data model, permission inheritance, project scoping).apiKeys.test.tscovers unscoped create (omit/null), null-clearing and re-scoping via PUT, unscoped keys spanning projects, intersection narrowing via a key policy, and the owner-ceiling bound. Smoke test adds an unscoped-key flow.Testing
pnpm typecheck(src + tests)pnpm eslint(no warnings),pnpm docs-lint🤖 Generated with Claude Code
Generated by Claude Code