Skip to content

feat(auth): 支持关闭本地密码登录并启用仅第三方认证 #559

Description

@AptS-1547

问题

AsterDrive 已经支持 OIDC、Generic OAuth2、GitHub、Google、Microsoft、QQ 等外部认证 provider,但本地密码登录始终可用。部署者即使已经把账号生命周期、离职停用、MFA 和访问策略统一交给外部 IdP,用户仍可绕过 IdP,直接使用 AsterDrive 本地密码登录。

这会带来几个实际问题:

  • 企业或团队部署不能真正执行“只允许第三方身份提供商登录”的策略。
  • IdP 侧的禁用、条件访问或 MFA 策略不能覆盖仍然开放的本地密码入口。
  • 仅在前端隐藏密码表单没有后端约束,POST /api/v1/auth/login、密码找回和外部身份密码补绑仍可直接调用。
  • 关闭密码入口还会影响公开注册、邀请注册、must_change_password、密码找回和已经进入 MFA 的登录 flow;只加一个 UI 开关会留下多条绕过或自锁路径。

目标

允许管理员在至少配置好一个外部认证 provider 后,关闭“本地密码作为登录首因子”的能力,用于以外部认证为唯一账号入口的部署。

建议新增独立运行时配置:

auth_password_login_enabled = true
  • category: user.registration_and_login
  • type: bool
  • default: true,保持现有安装和升级行为兼容
  • requires_restart: false

不要新增含义过宽的 auth_login_mode = external_only 枚举。AsterDrive 已经有独立的 auth_passkey_login_enabled,外部 provider 也有各自的启停状态;登录能力应保持可组合。严格的“仅第三方登录”部署应同时关闭密码登录和 Passkey 登录,邮箱验证码继续只作为现有 MFA,而不是独立首因子。

后端行为

密码首因子

auth_password_login_enabled=false 时:

  • POST /api/v1/auth/login 在账号查询和密码哈希验证前拒绝请求。
  • 返回稳定错误码,例如 auth.password_login_disabled,不要退化为只靠前端隐藏。
  • 已经通过密码首因子创建、但尚未完成的 MFA flow 不能在开关关闭后继续完成登录;MFA 完成阶段必须根据 flow 的 first_factor 重新检查当前策略。
  • 外部认证和 Passkey 登录不受该开关直接影响,仍使用各自策略,并继续进入本地用户状态检查、MFA、Cookie、session 和 refresh rotation 流程。

密码相关公开流程

关闭密码登录时,下列流程不能继续充当替代密码认证入口,也不能创建无法使用的本地注册账号:

  • 密码找回 request / confirm
  • 注册激活重发
  • 公开用户名/邮箱 + 密码注册
  • 当前基于用户名和密码的邀请接受流程
  • 外部认证 recovery 中的“输入现有账号密码并绑定”流程,包括开关关闭前已经创建的 flow

后端必须强制执行上述策略。公开 /auth/check 返回的 allow_user_registration 应反映有效策略;即使数据库里遗留 auth_allow_user_registration=true,前端也不能继续展示可提交但必然失败的注册入口。

本 issue 不把邀请流程重做成 IdP 邀请或 domain/group 自动入组;外部认证部署可以先通过管理员创建用户、verified email 自动绑定或 provider 自动创建用户完成账号落地。

must_change_password

管理员创建的现有用户可能带有 must_change_password=true。如果该标记仍对外部认证成功后的 session 生效,用户会被强制跳转到本地改密页,外部认证-only 模式就会自相矛盾。

建议把有效约束定义为:

effective_password_change_required =
    auth_password_login_enabled && user.must_change_password
  • 关闭密码登录时不清除数据库中的 must_change_password,但外部认证/Passkey 登录和后续 session 检查不再因此阻塞。
  • 重新开启密码登录后,原有标记恢复生效,用户仍需修改本地密码。
  • 密码哈希不删除、不批量重写,避免不可逆迁移和回滚困难。

会话与热更新

  • 开关关闭后立即阻止新的密码首因子登录和未完成的密码 MFA flow。
  • 已经签发的 access/refresh session 保持现有有效期;当前 session 模型没有可靠记录登录首因子,不能假装只撤销密码来源 session。
  • 管理员需要立即清退现有登录态时,继续使用现有会话撤销能力;文档和设置提示应明确这一点。
  • 重新开启后,本地密码登录恢复,已有密码哈希继续有效。

管理端约束

  • 在“系统设置 -> 用户管理 -> 注册与登录”展示“允许本地密码登录”开关。
  • 关闭前至少校验存在一个已启用的外部认证 provider,避免管理员保存后立即把所有人锁在系统外。
  • 关闭时明确提示:
    • 登录页、找回密码、公开注册和密码补绑入口会关闭。
    • 已有会话不会自动撤销。
    • Passkey 由独立开关控制;严格仅第三方登录需要同时关闭 Passkey。
  • 当密码登录已关闭时,不允许禁用或删除最后一个已启用的外部 provider;provider 更新与配置更新都要在服务端检查,不能只做前端确认弹窗。
  • 外部 provider 暂时不可达不应自动打开密码登录。故障恢复由仍有效的管理员 session、数据库/运维配置和重新启用 provider 处理,不能静默降低认证策略。

前端行为

扩展公开认证发现响应,返回 password_login_enabled。登录页根据服务端能力渲染,不自行推断 provider 数量或配置状态。

当密码登录关闭时:

  • 登录模式不展示用户名/邮箱、密码、登录按钮、忘记密码、激活重发和公开注册链接。
  • 仍展示启用的外部认证 provider;Passkey 是否展示继续读取 passkey_login_enabled
  • 外部认证 recovery 不展示“现有账号密码绑定”tab,只保留允许的邮箱补验路径。
  • 如果 provider 加载失败或没有可用 provider,显示明确的不可登录状态和联系管理员提示,不能留下空白表单或无限 loading。
  • 设置页中的本地密码管理区域应说明当前密码登录策略;不要让用户误以为修改密码后可以直接登录。
  • 配置、错误提示和说明文案同步中英文 i18n。

安全与兼容边界

  • 首个管理员 setup 保持现状。外部 provider 只能在系统初始化后配置,因此不能让新安装在 provider 尚未建立时进入 password-disabled 状态。
  • 禁用密码登录不删除用户、密码哈希、Passkey、外部身份绑定或 session。
  • 不绕过禁用用户、邮箱策略、provider policy、MFA 或审计。
  • 密码登录被策略拒绝时,不记录成功登录审计,也不执行昂贵密码哈希。
  • 配置缺失或非法时回退到 true,保持升级兼容并避免旧实例意外锁死。
  • 多 primary 实例通过现有 runtime config 失效/广播机制观察同一策略,不接受单实例局部开关。

验收标准

  • 新增 auth_password_login_enabled 运行时配置,默认 true,无需重启生效。
  • 默认配置下密码登录、注册、邀请、密码找回和外部认证密码补绑行为保持不变。
  • 关闭后 /auth/login 在用户查询/密码校验前返回稳定的 password-login-disabled 错误。
  • 关闭前创建的密码 MFA flow 不能在关闭后完成;外部认证 MFA flow 仍可完成。
  • 密码找回 request/confirm、公开注册、激活重发、密码邀请接受和外部身份密码补绑均由后端拒绝。
  • /auth/check / generated SDK 暴露 password_login_enabled,并返回有效的注册可用状态。
  • 登录页隐藏全部密码相关入口,只展示当前允许的外部认证/Passkey入口。
  • 外部认证 recovery 在密码关闭时不展示或调用密码补绑。
  • 没有可用外部 provider 或 provider 列表加载失败时展示明确错误状态。
  • must_change_password=true 的用户可通过外部认证登录;该字段不被清除,重新开启密码登录后恢复强制改密。
  • 关闭密码登录需要至少一个启用 provider;关闭后不能禁用或删除最后一个启用 provider。
  • 关闭/重新开启不会删除密码哈希、Passkey、身份绑定或已有 session。
  • 多 primary 下配置热更新后,各实例的新登录与未完成 flow 行为一致。
  • 后端 service/route、runtime config、OpenAPI、前端登录页、外部认证 recovery、Admin 设置和中英文文档均有对应测试。
  • 更新外部认证、注册登录、密码找回和部署故障恢复文档,明确已有会话不会自动撤销。

相关代码

  • src/config/definitions.rs
  • src/config/auth_runtime.rs
  • src/api/dto/auth.rs
  • src/api/routes/auth/public.rs
  • src/api/routes/auth/session.rs
  • src/services/auth/local/*
  • src/services/auth/mfa/login_flow.rs
  • src/services/auth/external/password_link.rs
  • src/api/routes/admin/external_auth.rs
  • src/services/auth/external/providers.rs
  • frontend-panel/src/pages/LoginPage.tsx
  • frontend-panel/src/pages/login/LoginAuthForm.tsx
  • frontend-panel/src/pages/login/ExternalAuthRecoveryPanel.tsx
  • frontend-panel/src/pages/admin/AdminSettingsPage.tsx
  • frontend-panel/src/pages/admin/AdminExternalAuthPage.tsx
  • developer-docs/zh-CN/design/external-auth.md
  • docs/src/content/docs/**/reference/config/external-auth.md

非目标

  • 不删除本地密码字段或迁移现有密码哈希。
  • 不在本 issue 实现 SAML、SCIM、IdP group/team 映射或 RP-Initiated Logout。
  • 不把邮箱验证码改造成独立的无密码登录方式。
  • 不按登录首因子追踪并选择性撤销历史 session;如需该能力,应单独扩展 session provenance。

Checklist

  • 已搜索现有 issue,没有发现等价的密码登录禁用或 external-only 登录策略任务。

Metadata

Metadata

Assignees

Labels

DocumentationImprovements or additions to documentationEnhancementNew feature or requestPriority: HighHigh priority issueRisk: HighChanges a high-risk data, security, protocol, or deployment boundaryRustPull requests that update Rust codeScope: Admin UIAdministrator-facing frontend workflows and management interfacesTypeScriptPull requests that update JavaScript code

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions