Skip to content

fix(auth): 全新部署零账号时放行首位注册,修复引导管理员死锁 (#1866) - #4460

Open
OldSlowDog wants to merge 1 commit into
Tencent:mainfrom
OldSlowDog:fix/1866-first-registration-bootstrap-deadlock
Open

OldSlowDog wants to merge 1 commit into
Tencent:mainfrom
OldSlowDog:fix/1866-first-registration-bootstrap-deadlock

Conversation

@OldSlowDog

@OldSlowDog OldSlowDog commented Oct 18, 2026 •

Copy link
Copy Markdown

closes #1866

问题

初部署设置 DISABLE_REGISTRATION=true(启动时合成 registration_mode=invite_only)后:

  • POST /auth/register 一律 403(Public registration is disabled),登录页也不显示注册页签;
  • WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL 按设计只提升已存在的用户、绝不创建账号(bootstrap.go 设计注释与 configuration.md:241 均有明文:"不会创建用户",该邮箱需先自行注册);

结果:没有任何账号能注册 → 无人能登录 → 首位系统管理员永远配不了,部署直接卡死。这正是 #1866 描述的场景。

方案选择

没有改 bootstrap 的"不创建用户"语义(那不是维护者本意,也有安全隐患:凭 env 静默造号)。改为零用户例外:全新部署一个账号都没有时,放行首位用户走正常公开注册流程,注册完再按既有 bootstrap 流程提拔为首位系统管理员。流程与 firstboot.sh 注释("注册后如需关闭后续注册再设 flag")一致。

与 #4183 的关系

提交后注意到 #4183 已针对同一 issue 开路(撰写本 PR 时未检索到,抱歉),思路同源:都不新增 endpoint(规避 #2918 里中间件 401 的教训)、都在 Register 门放行零账号首注册。两点差异供维护者取舍:

  1. 放行条件:fix: allow bootstrap admin signup when registration is closed #4183 要求注册邮箱匹配 WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL(大小写不敏感),且首位账号直接以系统管理员身份入库;本 PR 不依赖该 env 是否配置——部署内 0 账号即放行首注册,身份走正常用户创建,再交给既有 bootstrap 启动钩子按原语义提拔,不引入"凭一次公开注册直接赋予超管"的新语义。
  2. 完整性:本 PR 带 /auth/config 的 first_registration_open 标志与前端注册入口联动;fix: allow bootstrap admin signup when registration is closed #4183 未涉及前端——invite_only 下注册页签仍隐藏,只能靠调用方知道 API 其实可注册。

若维护者更倾向 #4183 的方案或希望取舍合并,本 PR 的差异(零 env 依赖、register/promote 语义分离、前后端 lock-step、fail-closed 单测)可以互补,也乐意把改进合到任一边。

改动

后端(internal/handler/auth.go)

  • Register 门新增零用户例外:mode 非 self_serve 且 ListAllUsers(ctx,0,1) 返回 0 条时放行首届注册;userService 为 nil 或查询出错一律 fail closed(403),不做乐观放行。
  • GET /auth/config 新增 first_registration_open 标志,与 Register 门 lock-step:mode 保持运维配置值(管理界面仍显示真实配置),前端另读该标志决定是否显示注册页签。

前端

  • registrationPolicy.canRegister(mode, hasInvite, firstRegistrationOpen) 增加第三参,仅零用户时放行;self_serve 行为不变。
  • AuthConfigResponse 补 first_registration_open 字段;Login.vue 接线。

文档(website-docs)

  • 03-features/01-tenant-auth.md:模式矩阵下新增"全新部署例外"小节;
  • 04-api/02-api-auth.md:/auth/config 响应示例与字段说明;
  • 01-getting-started/04-configuration.md / 03-quickstart.md:DISABLE_REGISTRATION、WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL 行补充该行为。

首位账号落库后例外立即关闭,行为回到原模式矩阵:已有用户时 invite_only 继续 403;register-by-invite 全程不受影响(仍按模式门控);OIDC、管理员建号通路不变。

Validation

单测:

  • 新增 4 例:TestRegister_FreshDeploymentAllowsFirstRegistration(零用户→201)、TestRegister_InviteOnlyStillRejectsWithExistingUsers(有用户→403)、TestRegister_UserCountErrorFailsClosed(查数失败→403)、TestGetAuthConfigExposesFirstRegistrationOpen(flag 双态且 mode 不变);go test ./internal/handler/ -run 'TestRegister|TestGetAuthConfig|TestRegistrationMode' 通过。
  • 前端 registrationPolicy.test.ts 补 2 例(零用户放行 / self_serve 不受影响),9/9 通过。

Lite 单二进制实测(DISABLE_REGISTRATION=true + WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL=admin@repro.local,干净数据目录):

步骤 结果
GET /auth/config(零用户) first_registration_open:true,registration_mode:invite_only
POST /auth/register(首位) 201,账号创建
注册后 GET /auth/config first_registration_open:false
第二位用户 POST /auth/register 403 Public registration is disabled
POST /auth/register-by-invite(全程) 403
重启后 bootstrap 日志 promoted user ... to system admin via WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL,登录 200 且 is_system_admin:true

升级兼容:只新增接口字段与一个受用户数约束的例外,不改任何既有配置语义;已有部署(哪怕真的 0 账号且开着 invite_only)也只是多了一次首注册机会,风险面等于一次正常注册。

问题:初部署设置了 DISABLE_REGISTRATION=true(或 invite_only/invite_register
模式)时,/auth/register 一律 403;而 WEKNORA_BOOTSTRAP_SYSTEM_ADMIN_EMAIL
按设计只提升已存在的用户、绝不创建账号,用户无从注册 → 无人可登录 →
无法配置首位系统管理员,部署直接卡死。

修复(保持 bootstrap「不创建用户」的原设计):
- Register 门新增零用户例外:mode 非 self_serve 且部署内用户数为 0 时
  放行首位公开注册;nil userService 或查询出错一律 fail closed(403)。
- GET /auth/config 新增 first_registration_open 标志(与 Register 门
  lock-step),mode 本身保持运维配置值不变,前端据此显示注册入口。
- 前端 registrationPolicy.canRegister 增加第三参;AuthConfigResponse 补
  该字段;Login.vue 接线。

首位账号落库后例外立即关闭,行为回到原模式矩阵;邀请注册
(register-by-invite)不受影响,仍有用户时继续 403。

测试:
- 新增 TestRegister_FreshDeploymentAllowsFirstRegistration /
  TestRegister_InviteOnlyStillRejectsWithExistingUsers /
  TestRegister_UserCountErrorFailsClosed /
  TestGetAuthConfigExposesFirstRegistrationOpen 四个用例;
- go test ./internal/handler/ -run 'TestRegister|TestGetAuthConfig|TestRegistrationMode' 通过;
- 前端 registrationPolicy.test.ts 9 例通过;
- Lite 单二进制实测(DISABLE_REGISTRATION=true + BOOTSTRAP 邮箱):
  零用户 register 201 → flag 转 false → 第二用户 403 → 重启后 bootstrap
  正常提拔首位用户为系统管理员(bootstrap 日志实锤)。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 项目初始部署并开启禁止注册,则无法配置超级管理员

1 participant