feat(auth): link the Atlas credential automatically on first sign-in - #4
Open
binyangzhu000-sudo wants to merge 3 commits into
Open
Conversation
Signing in with an upstream identity establishes who the user is, but the first sign-in still ends on a form asking them to paste an Atlas API key. The backend can answer that question itself: it already stores the provider subject it received at signup, so a verified identity is enough to find the account. When AUTH_CREDENTIAL_EXCHANGE_URL and AUTH_CREDENTIAL_EXCHANGE_TOKEN are set, the upstream callback asks the backend for the key belonging to the identity it just verified, encrypts it into the existing credential store, and completes the login. Anything short of a usable key — no matching account, a transport or auth failure, a key that fails verification — falls through to the manual form, since this path is an enhancement and must not be able to break sign-in. Notes on the parts that are easy to get wrong: - FederatedAccount now retains the subject the upstream asserted. The `sub` it is folded into is a one-way hash of issuer and subject, so it cannot name the identity to the backend. The field is optional so accounts stored before this change still parse against the strict schema. - An exchanged key is verified with the same read-only balance request as a pasted one. Trusting the source and skipping verification would let a backend-side mistake surface later as a puzzling tool failure. - Setting only one of the two variables is a startup error, not a silent no-op. A half-configured exchange is indistinguishable from a working one from the outside: every user just keeps pasting keys. - The audit event records the outcome only (linked / no_account / error), never the identity or the key, so a silent regression to manual entry is still visible in logs. The exchange stays optional: with neither variable set the flow is unchanged.
The exchange call carries the secret that can mint a user API key, so public hostnames still require TLS. In-cluster Service DNS (*.svc.cluster.local) never leaves the cluster; forcing TLS there pushes toward publishing the backend on a public ingress, which is the worse trade.
…the callback A consent prompt means oidc-provider found a live auth session, so the upstream callback — previously the only place the credential was ensured — never runs. If the stored credential was wiped or expired meanwhile (Redis TTL, an ops cleanup), the flow minted tokens for an account the resource server cannot serve: sign-in "succeeded", every MCP call then failed with account_not_linked, and neither the exchange nor the manual-key fallback had had a chance to run. - The consent branch now ensures the credential first: reuse a stored one, else re-run the exchange (validated before storing, same as the callback), else render the manual-key form; when even the federated account record has aged out, re-run the upstream sign-in so the callback can rebuild it. - The upstream callback, login-completion, and link-submit handlers accept a "consent" interaction as well as "login" — the consent path now legitimately resumes through them. - The resource server answers a missing credential with 401 + WWW-Authenticate (error="invalid_token") instead of a bare 403: RFC 6750 clients only re-authorize on 401, and re-authorizing is now exactly what repairs the binding. This also makes the 90-day credential expiry self-healing instead of a hard stop. Regression coverage: consent with wiped credential re-links via the exchange; no matching Atlas account falls back to the manual form; exchange or validation failures store nothing.
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.
背景
上游身份登录解决了"你是谁",但首次登录仍然停在一个让用户手贴 Atlas API Key 的表单上。这一步其实后端能自己答:Atlas 后端在建号时就存了 provider 的 subject,所以一个已验签的身份足够定位到账号。
改了什么
配了
AUTH_CREDENTIAL_EXCHANGE_URL+AUTH_CREDENTIAL_EXCHANGE_TOKEN之后,upstream 回调会拿刚验证过的身份向后端换取该用户的 key,加密进现有凭据库,然后直接完成登录。凡是拿不到可用 key 就回落到手工表单(没有匹配账号、传输/鉴权失败、key 校验不通过)——这条链路是增强,绝不能让登录整体失败。
配套后端接口见 AtlasCloudTeam/kubedl#603(同样 issuer 无关,上游是 Google 还是自建 provider 都能用)。
几个容易写错的点
FederatedAccount现在保留上游断言的原始 subject。它被折叠进的sub是 issuer+subject 的单向哈希,没法用来向后端指认身份。该字段是 optional,所以本次改动之前存下来的账号仍能通过.strict()schema 解析。linked/no_account/error),不记身份也不记 key,这样"悄悄退回手工模式"仍然能在日志里看出来。验证
tsc干净,npm test81/81 通过(原有 76 + 新增 5)。sub不同。兼容性
两个环境变量都不配时行为完全不变,首次登录照旧走手工粘贴。base 指向
fix/codex-oauth-callback-timeout-20260817,与 #3 保持一致(src/auth/目前只存在于该分支)。