Repository navigation
Make OAuth tokens account-wide with per-request team selection - #182
Merged
Merged
Conversation
OAuth tokens no longer bind a team at consent. Each request names its team (REST: X-Frontlit-Team-Id or /teams/:teamId; MCP: a teamId tool argument added centrally by the tool registry), and resolveTeamContext checks live membership and permissions on every call. API keys stay bound to one team and reject others with team_mismatch. - Remove the OAuth team picker, team_id claim, and selection table - Remove team-bound token revocation; membership checks deny removed teams - Strip teamId from MCP handler args; name the team in mutating results - Mark tools returning outside-written text as untrusted - Record the OAuth client ID on authorization audit events - Document the design, SDK/CLI notes, and security considerations
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
OAuth tokens are now account-wide. One OAuth connection (MCP or REST) works in every team the user belongs to, limited to the user's role and permissions in each team. Plan and security notes:
apps/api/docs/account-wide-oauth.md.X-Frontlit-Team-Idor/teams/:teamId, MCP through ateamIdargument.resolveTeamContext(auth/team-context.ts) checks live membership on every call. A sole team is used when none is named; otherwise the request fails withteam_required, which lists the user's teams.teamIdto every team-scoped tool and resolves the team before authorization. It removesteamIdfrom handler args, because handlers spread args after the internal team ID. Results of mutating tools end with a line naming the team. The 90 tool handlers are unchanged.team_idclaim;oauth_post_login_team_selections(migration0014);referenceIdand would have silently done nothing.Leaving a team is now enforced by the per-request membership check.
teamIdor header returnsteam_mismatch.metadata.oauthClientIdfor OAuth requests. It is carried through AsyncLocalStorage set in the auth middleware.api-contract: exports the team header name and error codes for the future SDK and CLI.docs/architecture.md,team-memberships.md, and the developer docs (authentication, MCP, members).Known gaps
@codelitdev/oauth-server-kithas no option for it, so "covers all your teams" needs an upstream change.Test plan
vitest run(API): 768 passed. Includes newteam-context,require-teamandtool-registrytests, and integration tests on real handlers covering cross-team permissions, removal, rejoining, andteam_mismatch.tscfor the API and web, ESLint, Biometeam_id.teamIdwork.team_requiredwithout ateamId; reads in that team succeed; writes there returninsufficient_permission; writes in the owned team succeed; an unknown team returnsinvalid_team_id.oauthClientId.codelit/frontlit:qa-acctoauth-8fc33c3(linux/arm64, built locally). Web and sites use the same tag but point at the unchangedqa-ghinstall-2fddaadimages. Deployed withinfra/deploy.sh; migration0014applied. Repeated the browser run on QA with fresh users and the same results as locally: no team picker, noteam_idin the token, sole-team calls work, and after joining a second teamteam_required, read allowed, write denied by permission, owned-team write allowed,invalid_team_id. After removal through the Members UI, the next call is denied and the other team keeps working. The auditoauthClientIdwas checked only locally, because QA has no endpoint for reading audit events.