问题
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 失效/广播机制观察同一策略,不接受单实例局部开关。
验收标准
相关代码
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
问题
AsterDrive 已经支持 OIDC、Generic OAuth2、GitHub、Google、Microsoft、QQ 等外部认证 provider,但本地密码登录始终可用。部署者即使已经把账号生命周期、离职停用、MFA 和访问策略统一交给外部 IdP,用户仍可绕过 IdP,直接使用 AsterDrive 本地密码登录。
这会带来几个实际问题:
POST /api/v1/auth/login、密码找回和外部身份密码补绑仍可直接调用。must_change_password、密码找回和已经进入 MFA 的登录 flow;只加一个 UI 开关会留下多条绕过或自锁路径。目标
允许管理员在至少配置好一个外部认证 provider 后,关闭“本地密码作为登录首因子”的能力,用于以外部认证为唯一账号入口的部署。
建议新增独立运行时配置:
user.registration_and_logintrue,保持现有安装和升级行为兼容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,不要退化为只靠前端隐藏。first_factor重新检查当前策略。密码相关公开流程
关闭密码登录时,下列流程不能继续充当替代密码认证入口,也不能创建无法使用的本地注册账号:
后端必须强制执行上述策略。公开
/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 模式就会自相矛盾。建议把有效约束定义为:
must_change_password,但外部认证/Passkey 登录和后续 session 检查不再因此阻塞。会话与热更新
管理端约束
前端行为
扩展公开认证发现响应,返回
password_login_enabled。登录页根据服务端能力渲染,不自行推断 provider 数量或配置状态。当密码登录关闭时:
passkey_login_enabled。安全与兼容边界
true,保持升级兼容并避免旧实例意外锁死。验收标准
auth_password_login_enabled运行时配置,默认true,无需重启生效。/auth/login在用户查询/密码校验前返回稳定的 password-login-disabled 错误。/auth/check/ generated SDK 暴露password_login_enabled,并返回有效的注册可用状态。must_change_password=true的用户可通过外部认证登录;该字段不被清除,重新开启密码登录后恢复强制改密。相关代码
src/config/definitions.rssrc/config/auth_runtime.rssrc/api/dto/auth.rssrc/api/routes/auth/public.rssrc/api/routes/auth/session.rssrc/services/auth/local/*src/services/auth/mfa/login_flow.rssrc/services/auth/external/password_link.rssrc/api/routes/admin/external_auth.rssrc/services/auth/external/providers.rsfrontend-panel/src/pages/LoginPage.tsxfrontend-panel/src/pages/login/LoginAuthForm.tsxfrontend-panel/src/pages/login/ExternalAuthRecoveryPanel.tsxfrontend-panel/src/pages/admin/AdminSettingsPage.tsxfrontend-panel/src/pages/admin/AdminExternalAuthPage.tsxdeveloper-docs/zh-CN/design/external-auth.mddocs/src/content/docs/**/reference/config/external-auth.md非目标
Checklist