Repository navigation
feat(auth): [AGI-6072] log in at the router on router tenants and pick the org - #170
Draft
whiskeylotus wants to merge 6 commits into
Draft
whiskeylotus wants to merge 6 commits into
whiskeylotus wants to merge 6 commits into
Conversation
Router tenants run CLI login on the router, not on their own pool. Login now asks the tenant (GET /auth/cli/config) where to sign in and runs session, redeem and refresh there. A 404 is a stack that predates the endpoint and keeps its own login; any other failure, or an answer that is neither the tenant nor a known router, fails closed. Auth calls no longer follow redirects, so a 307 cannot replay a redemption secret elsewhere. Credentials are stored per auth host, with a hosts.json binding each API host to its auth host; old credential files keyed by the tenant keep working. Refresh no longer saves the server's default org over the stored scope. Plan-Step: 1/5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both client transports set the Authorization header on every request they carried, redirect hops included, so a 3xx from the API sent the user's token to wherever it pointed. The bearer now goes only on an https request to the client's own API host that the caller made; redirects, other hosts, other ports and plain http go out without it. A 401 from such a request no longer triggers a forced refresh. Plan-Step: 2/5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A router login covers every org the user has on a host, and with none named, multi-org requests fail with ErrNoTenantID. Old logins also saved the server's default org (the lowest id) as if the user had chosen it. Commands now run as the first of: --org (name or id), NULLIFY_ORG, NULLIFY_GITHUB_OWNER_ID, the pick saved by `nullify org use` if the login still covers it, the git remote's owner if it names one org, the only org. Otherwise a terminal prompts and remembers the answer, and anything else fails with the choices. The saved query_parameters are never a pick. The org list is the router's (or a legacy tenant's) /admin/installations, kept to installations whose nullifyBaseUrl is this host; orgs not routed yet and orgs on other hosts are reported apart. An API token names no org unless asked, so the server takes the token's own. `auth status`, `auth config` and `whoami` show the effective host, auth host and org. Also from the wave 1 review: store read errors are surfaced instead of "not authenticated", `auth switch` drops hosts whose login is gone, and a router login removes the tenant's previous login. Plan-Step: 3/5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`nullify mcp serve` resolved one org at startup and failed outright on a multi-org login. It now starts on auth alone. Every tool takes an optional org argument and list_organizations lists the choices; a call uses its org argument, then serve --org, then the CLI's chain, and returns the choices when that is still ambiguous. Resource reads follow the same rule. The scope rides in the request context and is set on each request by a transport, so concurrent calls for different orgs cannot cross. Plan-Step: 4/5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan-Step: 5/5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- MCP refuses an org argument that is blank or not a name or id instead of running the call as the default org; a numeric id is taken as that id. - The org list retries once with a force-refreshed token when the router rejects the stored one, so a revoked or skewed token no longer breaks every MCP call until its stored expiry. - An ambiguous --org settled at the prompt no longer replaces the remembered pick, and API-token commands no longer read hosts.json. - `org list` reports a NULLIFY_GITHUB_OWNER_ID scope, and has help text. - README: NULLIFY_ORG, API-token org rules, and the real host order. - Tests for serve --org as the MCP default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
nullify auth logindead-ends on router tenants (AGI-6072). The CLI runs login on the tenant's own Cognito pool, whose GitHub callback is not on the router's shared App, so GitHub refuses the redirect. A router token also covers every org the user has, and with no org named, multi-org requests fail withErrNoTenantID.Login:
GET /auth/cli/configwhere its login runs, then runs session, redeem and refresh there. A 404 means a stack that predates the endpoint, which keeps its own login. Any other failure, or an answer naming neither the tenant nor a known router (api.nullify.ai,api.router.dev.nullify.ai), fails closed.~/.nullify/hosts.jsonbinds each API host to its auth host. Old credential files keyed by the tenant keep working.Org picker:
--org(name or id),NULLIFY_ORG,NULLIFY_GITHUB_OWNER_ID, the pick saved bynullify org useif the login still covers it, the git remote's owner if it names one org, or the only org. Otherwise a terminal prompts and remembers the answer, and anything else fails with the choices. Thequery_parametersold logins saved are never a pick.GET /admin/installationson the router (or a legacy tenant), kept to installations whosenullifyBaseUrlis this host. Orgs not routed yet and orgs on other hosts are reported apart. An API token names no org unless asked, so the server takes the token's own.nullify mcp servestarts on auth alone. Every tool takes an optionalorgargument, andlist_organizationslists them. A call uses its argument, thenserve --org, then the same chain, and returns the choices if still ambiguous. Resource reads follow the same rule. The scope rides in the request context, so concurrent calls for different orgs cannot cross.auth status,auth configandwhoamishow the effective host, auth host and org. Newnullify org list/nullify org use. README covers all of it.Companion auth side: https://github.com/Nullify-Platform/nullify/pull/17026.
Evidence:
curl https://api.radiant.dev.nullify.ai/auth/cli/configand the dev router both answer404 page not foundtoday, the legacy path above.Pre-push review: independent four-reviewer pass plus judge on each wave.
CI: wave 1 checkpoint green at 97f81cb (https://github.com/Nullify-Platform/cli/actions/runs/40957439860).
plan: router-cli-login-public-cli
🤖 Generated with Claude Code