Identity 是 BitzOrcas Platform 第一条完整落地的前端纵切片。它不是一张孤立登录页,
而是从公开入口、认证状态、账号生命周期到管理端安全操作的一套信任边界。页面统一消费
Design System 1.2 与 @bitz/platform-sdk,不保存 refresh token,不手写后端 DTO,
也不在页面内直接 fetch。
1. 页面入口
公开入口
| 路由 | 用途 |
|---|---|
/login | 凭证登录;按服务端响应进入验证码、MFA 或强制改密 |
/forgot-password | 请求密码重置;始终显示一致结果,避免账号枚举 |
/reset-password?userId=&token= | 从安全链接设置新密码 |
/accept-invitation?token= | 接受定向/开放邀请并提交准入资料 |
/activate?token= | 审批通过后激活账号并设置首次密码 |
/verify-email?userId=&token= | 确认邮箱 |
/verify-phone?userId=&token= | 确认手机号 |
/external-login/complete | 外部登录 callback 后恢复 HttpOnly Cookie 会话 |
/session-expired | 会话失效后的明确恢复入口 |
认证入口
| 路由 | 用途 |
|---|---|
/change-password | 主动改密或完成强制改密 |
/settings/security | MFA、恢复码、通行密钥、OTP、设备、会话与个人登录历史 |
/users | 用户搜索、创建、编辑、状态、组织和角色 |
/identity/access | 邀请、准入、角色权限、ABAC、功能开关与租户登录审计 |
/identity/organization | 组织树与层级维护 |
/identity/applications | API Key、HMAC 客户端与 OAuth 应用 |
/host/tenants | 租户创建、供给、暂停、恢复与停用(Host 面) |
/integrations | 外部登录、SCIM、Webhooks、通知与 AI 等租户集成 |
2. 登录状态机
页面不根据空 token 或 HTTP 状态猜分支。AuthSession.handleLoginSuccess() 把服务端响应
收敛为稳定的联合类型:
// 用可辨识联合约束登录后续步骤,页面不解析临时字段猜测状态。type LoginOutcome = | { kind: "authenticated" } | { kind: "captcha"; challengeId: string; renderData: string } | { kind: "mfa"; mfaToken: string } | { kind: "mfa-enrollment-required" } | { kind: "password-change-required" };图形验证码不是每次登录的固定步骤。只有连续失败,或系统检测到异常网络、设备等风险时, 服务端才会要求追加验证。页面显示“换一张”按钮;点击后只提交账号和上一张验证码的标识, 不会再次发送密码。刷新后旧验证码立即失效;验证码错误或本次登录失败时,页面也会自动 换一张、清空输入并把焦点放回验证码输入框。
验证码支持 SVG、PNG、JPEG、GIF Base64 与 data/blob URL。MFA 验证使用短时
MfaToken 证明已经完成密码阶段,不发送 Bearer token。
登录页动态效果
登录页沿用法律顾问工作室 C4D 视觉。静态海报始终作为首屏和加载失败时的基线;只有桌面端、 未开启“减少动态效果”且未启用省流量模式时才加载静音循环视频。小屏和上述偏好场景只显示 静态海报,功能与信息不依赖动画。视频不进入 PWA 预缓存,避免首次使用额外下载大体积素材。
外观设置
公开 Identity 页面右上角提供“外观设置”,可切换浅色、深色、跟随系统以及衡蓝、香槟金、 章红、青律。登录、验证码、MFA、邀请和找回密码固定使用 Comfortable 密度,因此不显示 无效的密度选项;登录后的全局顶栏另提供 Dense、Compact、Comfortable 三档。
Mode、Brand 和 Density 只改变视觉表现,不改变路由、登录分支、权限或 SDK。页面只在
localStorage 保存这三项非敏感视觉偏好;账号、工作空间、权限和凭据仍全部来自服务端。
浏览器拒绝持久化时切换仍即时生效,但只保留到当前页面会话。
3. 会话与凭据
- access token 只存在
AuthSession的内存字段。 - refresh token 只存在后端设置的 HttpOnly Cookie。
- 首屏先 refresh,再读取
/api/auth/me;完成前状态为bootstrapping。 - 并发 401 共用一个 refresh Promise,成功后每个原请求只重试一次。
sessionRevision阻止旧 refresh 覆盖稍后发生的登录、MFA 或登出。- logout 请求先尝试撤销服务端会话;网络失败也会清理本地内存状态。
一次性链接的 token 和 userId 会在启动阶段捕获,随后使用 replace 导航从地址栏清除,
避免被浏览器历史、资源 Referer 或截图继续携带。
4. MFA 与安全中心
两阶段启用
POST /api/mfa/setup返回二维码和手工密钥,只保存短时 pending secret。- 用户使用 Authenticator 扫码并输入首个 6 位 TOTP。
POST /api/mfa/setup/confirm验证成功后才正式启用 MFA。- 禁用 MFA 必须再次提供当前 TOTP。
恢复码
- 状态端点只返回剩余数量,不回传历史明文。
- 重新生成必须通过当前 TOTP。
- 新代码只在本次响应显示;页面提供保存提醒,不写入浏览器持久化存储。
- 登录使用一组未消费的 16 位恢复码;成功后该码立即失效。
通行密钥
- 安全中心通过 FIDO2 register begin/complete 登记系统钥匙串或安全密钥。
- 登录阶段通过 login begin 取得 challenge,再调用
navigator.credentials.get()。 - 浏览器不支持或用户取消时明确降级到 TOTP/恢复码。
- 生物特征数据不会发送给应用;服务端只验证公钥凭据、challenge、RP 和 origin。
可信设备、会话与审计
- 信任当前设备需要 TOTP,服务端按设备归属记录 30 天有效期。
- 可以撤销设备信任、移除设备、撤销单个会话或其他全部会话。
- 个人登录历史展示结果、验证方式、IP、设备和失败原因。
- 租户管理员可在“准入与授权”按用户查询租户登录审计。
5. 密码与联系方式
密码页面读取 GET /api/password/policy 返回的部署实际策略:
// 长度与唯一字符数直接取自服务端密码策略。type PasswordPolicyResponse = { requiredLength: number; maximumLength: number; requiredUniqueChars: number; requireDigit: boolean; requireLowercase: boolean; // 三个字符类别开关分别驱动界面提示与本地预检。 requireUppercase: boolean; requireNonAlphanumeric: boolean;};激活、重置和改密共用同一个策略 hook 与规则组件。策略端点暂时不可达时使用保守 fallback, 但服务端始终执行最终校验;密码历史、当前密码和租户策略不能由浏览器替代。
通知行为:
- 密码重置申请始终返回一致结果,避免根据响应枚举账号。
- 邮箱验证发送一次性链接。
- 手机确认和 SMS OTP 发送 6 位验证码。
- 未配置邮件/短信供应商时服务端会明确失败;“API 成功”不能代替真实消息可达性验收。
6. 邀请、准入与用户
/identity/access 区分邀请、准入、授权策略和登录审计:
- 邀请支持定向邮箱和开放链接,可配置允许域、角色、组织、办公室、到期时间与使用次数。
- 接受邀请只创建
PendingReview准入申请,不直接创建可登录用户。 - 审核通过后创建待激活用户并发送激活指引。
- 拒绝、撤销与其他危险动作都经过二次确认。
/users 提供用户搜索、服务端分页、创建、资料编辑、组织归属、角色分配、锁定、解锁、
启用、禁用和删除。资料更新与角色授权使用不同接口和权限边界,避免把授权变更夹在普通
资料表单里。
7. 角色、ABAC 与功能开关
- 角色负责汇集权限;用户只分配角色,不在用户表单中拼装零散权限。
- 权限树和角色授权来自服务端。
- ABAC 规则以稳定枚举提交作用域、动作、条件、裁决和优先级。
- Feature 管理同时展示平台默认、租户覆盖与实际生效状态。
- 前端 gate 只改善可见性和交互;后端 Authorization 是唯一安全事实来源。
8. 组织、应用凭据与租户
组织架构支持根/子组织创建、编辑、移动和删除。页面会排除明显非法目标,服务端最终防止 移动到自身或后代,并保护存在成员/子组织时的完整性。
应用与凭据页统一管理:
- API Key:最小 scope、轮换与撤销;
- HMAC:请求签名客户端、scope、secret 轮换;
- OAuth 应用:redirect URI、scope、PKCE 与 client secret。
所有 secret 只在创建或轮换响应中显示一次,列表永远不回显明文。
租户治理按后端状态机提供创建、供给步骤、暂停、恢复和停用。页面不自行推测非法转换, 每次操作都带理由并等待服务端裁决。
/integrations 的外部登录 Sheet 管理当前租户的 Azure AD、通用 OAuth2、LDAP 与微信小程序
配置。列表只显示已配置的 Secret 键名,不回显凭据;保存支持替换或明确清除 Secret,删除带
Version 乐观并发,连接测试使用已保存配置。该入口要求 identity.externallogin.manage,
协议 Adapter 仍由 Host 静态装载。字段、就绪状态和运行时失效规则见
Identity 配置与外部集成。
9. 页面开发约束
// 统一 API 结果先映射稳定错误,再更新会话列表状态。const { api } = usePlatform();const result = await api.listSessions();
// 失败分支不把后端内部信息直接呈现给用户。if (!result.ok) { setMessage(friendlyIdentityError(result.error, t.tRaw)); return;}
setSessions(result.data);- HTTP 只走
usePlatform().api;登录协议走 Provider 的login/verifyMfa/logout。 - DTO 从
@bitz/platform-sdk导入;字段来自 OpenAPI generated client。 - 原语从
@bitz/components,复合件从@bitz/widgets;禁止深路径导入。 - 表单字段使用
IdentityField连接 label、hint、error 和 ARIA 属性。 - 危险操作使用
ConfirmAction;提交中禁用重复动作并显示Spinner。 - 颜色、字体、间距、圆角、阴影和导航前景全部消费 Design System 1.2 Token。
10. 部署验收
- 配置有效 Runtime License,并以真实 Api Host 启动应用。
Frontend:BaseUrl使用公开 HTTPS origin。- 邮件、短信与外部身份 Provider 已配置真实适配器;集成中心显示
Ready,并通过真实连接测试。 - refresh Cookie 的 Secure、SameSite、Domain 与部署拓扑一致。
- 用真实 Authenticator 完成 setup → confirm → logout → MFA login。
- 用平台钥匙串或安全密钥完成 passkey register → login → delete。
- 验证恢复码只能消费一次,可信设备撤销后恢复完整 MFA。
- 用最低权限账号复核页面、页签、按钮和服务端 403。
- 验证用户、角色、设备、会话、组织、审计和凭据不能跨租户读取。
11. 回归命令
# 后端契约与应用测试scripts/build/export-openapi.shdotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \ --filter 'FullyQualifiedName~Identity|FullyQualifiedName~Authorization'
# 前端cd frontendyarn workspace @bitz/platform-sdk generate-clientyarn workspace @bitz/platform-sdk testyarn workspace @bitz/materials testyarn lintyarn typecheckyarn build
# Identity 设计防腐:三个命令均应无输出rg -n '#[0-9A-Fa-f]{3,8}|rgb\\(|hsl\\(|oklch\\(' \ apps/app/src/pages/identity apps/app/src/pages/loginrg -n -- '--ref-|--login-' apps/app/src/pages/identity apps/app/src/pages/loginrg -n '\\b(fetch|axios|XMLHttpRequest)\\b|localStorage|sessionStorage' \ apps/app/src/pages/identity apps/app/src/pages/login