Repository navigation
feat(accounts): GitHub 与 Google 登录,关闭普通账号口令登录的开关 - #185
Merged
Merged
Conversation
…唯一索引,登记 migrations.lock(Refs #184)
…gister 加 forbidden、oauthFinish 加 registrations_paused 且会话分支带 csrfToken,契约 §29 说明(Refs #184)
…info 的认证能力位按配置组装(Refs #184)
…ecurity.oauthLogin / oauthUnlink(Refs #184)
…Redis 一次性取走(Refs #184)
…uth.login 与第三方登录共用 createSession(Refs #184)
…OG Unreleased(中英,Refs #184)
This was referenced Nov 1, 2026
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
Go 控制面实现 GitHub 与 Google 登录,并加开关
ARMADRA_CLOUD_PASSWORD_AUTH关闭普通账号的口令登录(设计依据:M2 实施设计草稿 §4.0、§4.3 的登录 / 注册部分;绑定到已登录账号、passkey、两步验证等 M2 其余内容不在本 PR)。account_credentials.external_id,两个部分唯一索引(一个第三方身份只属于一个账号;一个账号在同一提供方至多一个身份),kind = 'oauth'行必须带<提供方>:<主体>。库里不存任何令牌。ARMADRA_CLOUD_OAUTH_GITHUB_CLIENT_ID/_CLIENT_SECRET_FILE、ARMADRA_CLOUD_OAUTH_GOOGLE_*、ARMADRA_CLOUD_PASSWORD_AUTH(on缺省 /off);提供方地址可由测试覆盖(Deps.OAuthEndpoints,生产不设)。auth.oauthStart:redirectUri严格等于APP_ORIGIN/login/oauth/<provider>;state与 PKCE(S256)校验串存 Redis(键sha256(state),10 分钟,GETDEL 一次性);Cookie 传输下发__Host-armadra-oauth(SameSite=Lax),Bearer 传输不发;按 IP 限流oauth.start.ip。auth.oauthFinish:state 不存在 / 过期 / 提供方对不上 →401 credentials_invalid;Cookie 传输或发起时下发过随机数就要求 Cookie 一致(定时安全比较),用后清除;访问令牌只用一次、不落库不进日志;GitHub 只认primary && verified,Google 只认email_verified。已绑定登录 / 无同邮箱账号建号(邮箱已验证、无口令)/ 同邮箱账号:off时绑定并登录,on时409 conflict(details.reason = "account_exists")/ 邮箱未验证403 email_unverified/ 注册暂停503/ 停用403 account_disabled。会话经新抽出的createSession,与auth.login同一条路径(停用检查、新设备提醒、security.login,另写security.oauthLogin;Cookie 传输答csrfToken)。auth.oauthUnlink:解绑后至少留一种登录方式(口令关闭时只看第三方),否则409;未绑定404。off:auth.register、非运营auth.login、auth.changePassword答403 forbidden;auth.passwordResetStart照常答成功、做同样的库写入但只插占位发件(不发信);scope: "operator"登录不受影响。platform.Capabilities按配置组装(auth.oauth、auth.oauth.github/auth.oauth.google;口令关闭时不报auth.password、auth.password-change)。PLATFORM_CAPABILITIES加两个值;auth.register错误表加forbidden;auth.oauthFinish加registrations_paused,输出会话分支换成cookieSessionResultSchema(加法)。契约 §29、运维手册、deploy/saas/compose.yml(两个可选 secret 与环境变量)、CHANGELOG 同步。实现时的两处补充(设计草稿没写,请审):
identity.NoPassword()(password_json里{"kdf":"none"})占位,VerifyPassword对它也做一次等价派生再答否,时延不暴露「这个账号没有口令」。off时绑定到一个邮箱还没验证过的本地账号:先前可能是别人抢注的,所以绑定时把邮箱记成已验证、清掉口令并撤销它的全部设备(经撤销钩子)。已验证的账号保留原口令记录。Go control plane gains GitHub and Google sign-in plus a switch,
ARMADRA_CLOUD_PASSWORD_AUTH, that turns off password sign-in for regular accounts (design basis: M2 implementation draft §4.0 and §4.3, sign-in / sign-up part only; linking to a signed-in account, passkeys and two-step verification are out of scope).account_credentials.external_idwith two partial unique indexes (a third-party identity belongs to one account; an account has at most one identity per provider). No token is ever stored.ARMADRA_CLOUD_PASSWORD_AUTH; provider endpoints are overridable by tests only (Deps.OAuthEndpoints).auth.oauthStart/auth.oauthFinish/auth.oauthUnlinkas described above (strictredirectUri, PKCE S256, one-shot state in Redis keyed bysha256(state), browser-binding cookie__Host-armadra-oauthfor the cookie transport, per-IP rate limits, verified-email-only rules, same-email handling that depends on the password switch).createSession, the same path asauth.login.auth.register, regularauth.loginandauth.changePasswordanswer403 forbidden, password-reset start answers success without mailing, and operator sign-in is unaffected.forbiddenonauth.register,registrations_pausedonauth.oauthFinishand acookieSessionResultSchemasession branch (additive).Two choices the draft does not spell out, please review: accounts created by OAuth have a placeholder "no password" record that still costs a derivation to verify; and when password sign-in is off, binding to a local account whose email was never verified marks it verified, clears its password and revokes all its devices (it may have been squatted).
关联 issue / Related issues
Closes #184
改动范围 / Scope
go/)packages/platform-protocol)packages/relay-kernel)packages/relay-runtime-workers、apps/relay-workers)packages/control-core)packages/cloud-shared)deploy/、go/Dockerfile、go/Dockerfile.cloud)契约与协议变化 / Contract and protocol changes
PROTOCOL_VERSION不变(1.4);全是加法(能力位、错误码表项、可选的csrfToken);协议包版本由维护者发版时定。deploy/compat.json不动。Armadra 控制台需按能力位auth.oauth.github/auth.oauth.google与csrfToken对接,回调页路径是/login/oauth/<provider>auth.oauthStart/auth.oauthFinish)、cloud-openapi.json(两份)、cloud-api.md生成块已重新生成;Gogen/无变化数据库迁移 / Database migrations
migrations.lock: 已同步;go generate拷到 Go 的 embed 目录;带测试(空库、停在 0010 的旧库升级、唯一索引与 CHECK、编译进compat与 CLI 的计数)测试 / Testing
pnpm test): 通过pnpm test:integration,需pnpm dev:up): 未跑;Go 的 PostgreSQL / Redis / mailpit 测试用本机临时容器跑过(pnpm go:check)pnpm e2e --mode personal): 未跑pnpm images:build): 未跑pnpm check与 / andpnpm repo:test: 通过;pnpm go:check(gofmt、vet、staticcheck、-race测试、生成物新鲜度)通过新增用例(假提供方 httptest + 真实 PostgreSQL / Redis):授权地址与
redirectUri校验、Cookie 下发属性、state 一次性 / 过期 / 提供方对不上、Cookie 随机数缺失 / 不符 / 重复 / 传输不匹配、邮箱未验证(GitHub 非主邮箱 / 未验证,Googleemail_verified = false)、新建账号(库里形状、安全日志、无口令、令牌不落库不进日志、提供方只被访问一次)、已绑定登录、同邮箱在 on / off 下的行为(含未验证本地账号被清口令撤设备)、同一身份不能绑两个账号、同提供方第二个身份冲突、停用账号、注册暂停、口令关闭后各过程的答复、运营登录不受影响、找回口令不发信、能力位组装、解绑规则、限流、提供方 5xx 答 503 且日志不含秘密。检查清单 / Checklist
@armadra/platform-protocol与镜像往来 / apps do not import Armadra sources, and everything shared with Armadra goes through@armadra/platform-protocoland images@orpc/*只出现在允许的门面文件里;协议包不 importnode:*(夹具除外) /@orpc/*appears only in the allowed facade files; the protocol package does not importnode:*(fixtures excepted)aud、exp、jti;中继只把流转给令牌src指定的源 / Token checks keepaud,expandjti; the relay only forwards to the source named by the token'ssrcpersonal模式与其入口不依赖 PostgreSQL / Redis /personalmode and its entry points do not depend on PostgreSQL / Redismigrations.lock已同步;没有自动清库、重建或跳过损坏账本的逻辑 / Migrations are append-only and contiguous,migrations.lockupdated; no auto-wipe, rebuild or skipping of a corrupt ledger{ code, message, requestId?, details? },JSON 为 camelCase / Error codes come from the protocol package registry, the wire shape is{ code, message, requestId?, details? }, JSON is camelCasecloud-api.md、OpenAPI、夹具与身份向量(pnpm protocol:check、pnpm contract:check通过) / Shape changes update the protocol package,cloud-api.md, OpenAPI, fixtures and identity vectors (pnpm protocol:checkandpnpm contract:checkpass)pnpm notices并提交THIRD_PARTY_NOTICES.md/ Dependencies use exact versions; production dependency changes include a regeneratedTHIRD_PARTY_NOTICES.md(无新依赖 / no new dependencies)docs/README.md/ CLI and log messages are short Simplified Chinese sentences with variables in fields; new docs are registered indocs/README.md(无新文档,运维手册已有索引行 / no new document; the runbook already has an index row)CHANGELOG.md的 Unreleased / User-visible changes are recorded under Unreleased inCHANGELOG.md需要维护者提供的外部条件 / External prerequisites
开启前要在 GitHub 建 OAuth App、在 Google Cloud 建 OAuth 客户端(步骤与回调地址
https://app.<域>/login/oauth/<provider>见运维手册「第三方登录与口令开关」),把密钥写进secrets/oauth_github_secret/secrets/oauth_google_secret,再在.env填变量。控制台的/login/oauth/<provider>回调页由 Armadra 仓提供,发布顺序是先控制台、后开配置、最后才设PASSWORD_AUTH=off。本地只用假提供方验证,没有对真实的 GitHub / Google 跑过。Verification here uses fake providers only; the real GitHub and Google endpoints have not been exercised.已知限制 / Known limitations
intent: "link")、第三方登录后的 MFA 挑战(普通账号的用户侧两步验证还没有)、me.security、无口令账号的注销 / 改邮箱(me.delete、me.emailChange仍要口令)、绑定与解绑要求提权。auth.changePassword对运营账号同样答 403,运营账号的口令靠命令行维护。auth.stepUp只能用 TOTP / passkey,普通无口令账号暂时没有可用的提权方式(M2 内容)。me.security, account deletion / email change for password-less accounts, and step-up requirements for link / unlink. With password sign-in off, operators also cannot useauth.changePassword.