design: 可控 Portal 自助注册技术设计 #77

Closed
opened 2026-08-29 20:49:10 +08:00 by ila · 2 comments
Owner

基本信息

  • 类型:单元任务(技术设计)
  • 所属 MVP:#76
  • 所属 Epic:#75
  • 状态:已完成
  • 提出时间:2026-08-29
  • 原始需求来源:用户对话
  • 用户确认摘要:注册密码至少 6 位,其余按默认关闭、管理员即时开关、账号注册、邮箱可空、IP 限流与审计方案设计。
  • UI 原型:不适用;本工单只交付架构、API、数据、状态与安全设计。

目标

冻结可控 Portal 自助注册的数据库、接口、状态、安全、并发、配置和测试契约,为原型及生产拆单提供唯一技术基线。

数据设计

使用 migration 12,可逆 up/down:

  1. users.email 攓为可空;既有数据不修改。管理员建号仍可填写邮箱,自助注册不生成虚假邮箱。
  2. 新增单例表 portal_registration_policy:
    • id TINYINT UNSIGNED PRIMARY KEY,只允许 id=1;
    • enabled BOOLEAN NOT NULL DEFAULT FALSE;
    • version BIGINT UNSIGNED NOT NULL DEFAULT 1;
    • updated_at DATETIME(6)。
  3. 新增 portal_auth_events:
    • event_type:registration_succeeded / registration_rejected / registration_rate_limited;
    • outcome、reason_code、user_id 可空、request_id、created_at;
    • 不保存密码、密码哈希、账号、邮箱、原始 IP、User-Agent 或请求正文。
  4. migration 增补 Admin API、菜单接口映射与 chorus_operator Casbin 权限;down 先移除权限/接口,再移除新表并把 email 恢复为 NOT NULL。down 前若已有 NULL email 用户必须明确失败,不猜测或生成邮箱。

策略与并发

  • migration 插入 id=1、enabled=false,升级默认不开放注册。
  • Portal 登录页每次读取策略;读取失败时登录仍可用,但隐藏注册链接。
  • 注册页和注册提交每次独立读取策略;读取失败返回 503,关闭返回 404(页面)或 403 registration_closed(API)。
  • 注册事务对策略单例行加共享锁后再次检查,再写 users 和成功事件;Admin 关闭使用行更新,与在途注册形成确定顺序。
  • 账号与唯一约束冲突统一返回 409 account_unavailable,不区分并发或已存在。
  • 注册成功后轮换 Session 并直接登录;注册关闭、校验失败或限流不创建 Session 用户态。

Portal API

  • GET /register:仅开放时渲染注册页。
  • POST /api/session/register:CSRF 保护,JSON 最大 64 KiB。
  • 请求:username、display_name、password、password_confirmation。
  • 账号:trim + lowercase,3–64 位,沿用 ^[a-z0-9][a-z0-9._-]{2,63}$。
  • 昵称:trim,1–120 字符。
  • 密码:6–1024 字符;确认密码必须一致;使用现有 bcrypt:v1。
  • 成功:201,轮换后的 CSRF token 与非敏感用户摘要。
  • 错误:400 invalid_request、403 registration_closed / csrf_invalid、409 account_unavailable、429 registration_rate_limited、503 registration_unavailable。
  • 响应与日志不得返回或记录密码、哈希、数据库错误及个人信息。

Admin API

  • GET /api/v1/chorus/registration-policy:返回 enabled、version、updated_at。
  • PUT /api/v1/chorus/registration-policy:请求 enabled 与当前 version;使用 CAS 更新,冲突返回 409。
  • 两个接口受现有 Admin JWT + Casbin 保护。
  • 修改写 admin_audit_events,只记录 before/after、version、actor 和 request_id。
  • Admin UI 在“用户与访问 → 终端用户”标题区显示“自助注册”开关、当前状态和更新时间;开关时二次确认,失败恢复原状态。

客户端 IP 与限流

  • 新增 Portal YAML server.trusted_proxy_cidrs,默认空。
  • 只有直接连接地址命中可信代理 CIDR 时才读取标准 X-Forwarded-For 链;否则只使用 RemoteAddr,禁止无条件信任客户端 Header。
  • 登录和注册统一使用安全解析后的客户端 key,修复 nginx 后所有用户共享一个限流 key 的问题。
  • 新增 registration.attempts: 5、registration.window_seconds: 900,同名环境变量可覆盖;生产必须显式配置。
  • 限流按客户端 key 统计所有提交,包括成功;成功不清零。首版仍为单 Portal 实例内存限流,多实例不在范围。

页面状态

Portal:

  • 注册关闭:登录页不显示入口;直接访问显示 404。
  • 注册开放:登录页显示“创建账号”;注册页提供四个持久 label 字段、密码显隐、返回登录。
  • 校验错误贴近字段并聚焦首个错误;冲突使用不泄露存在性的统一提示。
  • 提交中按钮禁用;开关在填写过程中关闭时保留输入但清空密码,并提示注册已关闭。
  • 成功后直接进入 Portal;网络错误保留非密码字段并允许重试。
  • 375/768/1024/1440 无横向溢出,44px 目标、键盘可用、aria-live、reduced-motion。

Admin:

  • 开关关闭/开启、读取中、保存中、冲突、无权限、网络失败状态明确。
  • 开启和关闭均需要确认,文案说明只影响自助注册,不影响登录和管理员建号。

测试与验收

  • migration 1→12、12 down/up;NULL email down 门禁。
  • 策略默认关闭、读取失败关闭、Admin CAS 与审计。
  • 注册成功、关闭、CSRF、字段边界、重复与并发冲突、bcrypt、自动登录、禁用后登录失败。
  • 直连/可信代理/伪造 XFF、5 次/15 分钟限流及成功不清零。
  • Portal/Admin 单元、MySQL 集成、四视口浏览器 E2E、构建、vet、race、脱敏扫描。
  • 不调用 Provider,不使用真实用户或生产数据。

风险与回退

  • 公开注册会增加滥用面;默认关闭、独立限流、可信代理解析与失败关闭控制风险。
  • 6 位密码风险高于推荐值;按用户明确决定保留,并通过登录限流、注册限流和 bcrypt 缓解,不宣称等同强密码。
  • 回退先由 Admin 关闭注册,再回退应用;确认无 NULL email 用户后才执行 migration down。
  • 关闭注册不删除任何用户。

文档影响

确认后更新 Product-Requirements-Overview、Business-Rules-and-Glossary、Architecture-and-Code-Map、Local-Development-and-Verification、Deployment-and-Operations。

## 基本信息 - 类型:单元任务(技术设计) - 所属 MVP:#76 - 所属 Epic:#75 - 状态:已完成 - 提出时间:2026-08-29 - 原始需求来源:用户对话 - 用户确认摘要:注册密码至少 6 位,其余按默认关闭、管理员即时开关、账号注册、邮箱可空、IP 限流与审计方案设计。 - UI 原型:不适用;本工单只交付架构、API、数据、状态与安全设计。 ## 目标 冻结可控 Portal 自助注册的数据库、接口、状态、安全、并发、配置和测试契约,为原型及生产拆单提供唯一技术基线。 ## 数据设计 使用 migration 12,可逆 up/down: 1. `users.email` 攓为可空;既有数据不修改。管理员建号仍可填写邮箱,自助注册不生成虚假邮箱。 2. 新增单例表 `portal_registration_policy`: - `id TINYINT UNSIGNED PRIMARY KEY`,只允许 id=1; - `enabled BOOLEAN NOT NULL DEFAULT FALSE`; - `version BIGINT UNSIGNED NOT NULL DEFAULT 1`; - `updated_at DATETIME(6)`。 3. 新增 `portal_auth_events`: - event_type:registration_succeeded / registration_rejected / registration_rate_limited; - outcome、reason_code、user_id 可空、request_id、created_at; - 不保存密码、密码哈希、账号、邮箱、原始 IP、User-Agent 或请求正文。 4. migration 增补 Admin API、菜单接口映射与 chorus_operator Casbin 权限;down 先移除权限/接口,再移除新表并把 email 恢复为 NOT NULL。down 前若已有 NULL email 用户必须明确失败,不猜测或生成邮箱。 ## 策略与并发 - migration 插入 id=1、enabled=false,升级默认不开放注册。 - Portal 登录页每次读取策略;读取失败时登录仍可用,但隐藏注册链接。 - 注册页和注册提交每次独立读取策略;读取失败返回 503,关闭返回 404(页面)或 403 `registration_closed`(API)。 - 注册事务对策略单例行加共享锁后再次检查,再写 users 和成功事件;Admin 关闭使用行更新,与在途注册形成确定顺序。 - 账号与唯一约束冲突统一返回 409 `account_unavailable`,不区分并发或已存在。 - 注册成功后轮换 Session 并直接登录;注册关闭、校验失败或限流不创建 Session 用户态。 ## Portal API - `GET /register`:仅开放时渲染注册页。 - `POST /api/session/register`:CSRF 保护,JSON 最大 64 KiB。 - 请求:`username`、`display_name`、`password`、`password_confirmation`。 - 账号:trim + lowercase,3–64 位,沿用 `^[a-z0-9][a-z0-9._-]{2,63}$`。 - 昵称:trim,1–120 字符。 - 密码:6–1024 字符;确认密码必须一致;使用现有 `bcrypt:v1`。 - 成功:201,轮换后的 CSRF token 与非敏感用户摘要。 - 错误:400 invalid_request、403 registration_closed / csrf_invalid、409 account_unavailable、429 registration_rate_limited、503 registration_unavailable。 - 响应与日志不得返回或记录密码、哈希、数据库错误及个人信息。 ## Admin API - `GET /api/v1/chorus/registration-policy`:返回 enabled、version、updated_at。 - `PUT /api/v1/chorus/registration-policy`:请求 enabled 与当前 version;使用 CAS 更新,冲突返回 409。 - 两个接口受现有 Admin JWT + Casbin 保护。 - 修改写 `admin_audit_events`,只记录 before/after、version、actor 和 request_id。 - Admin UI 在“用户与访问 → 终端用户”标题区显示“自助注册”开关、当前状态和更新时间;开关时二次确认,失败恢复原状态。 ## 客户端 IP 与限流 - 新增 Portal YAML `server.trusted_proxy_cidrs`,默认空。 - 只有直接连接地址命中可信代理 CIDR 时才读取标准 `X-Forwarded-For` 链;否则只使用 RemoteAddr,禁止无条件信任客户端 Header。 - 登录和注册统一使用安全解析后的客户端 key,修复 nginx 后所有用户共享一个限流 key 的问题。 - 新增 `registration.attempts: 5`、`registration.window_seconds: 900`,同名环境变量可覆盖;生产必须显式配置。 - 限流按客户端 key 统计所有提交,包括成功;成功不清零。首版仍为单 Portal 实例内存限流,多实例不在范围。 ## 页面状态 Portal: - 注册关闭:登录页不显示入口;直接访问显示 404。 - 注册开放:登录页显示“创建账号”;注册页提供四个持久 label 字段、密码显隐、返回登录。 - 校验错误贴近字段并聚焦首个错误;冲突使用不泄露存在性的统一提示。 - 提交中按钮禁用;开关在填写过程中关闭时保留输入但清空密码,并提示注册已关闭。 - 成功后直接进入 Portal;网络错误保留非密码字段并允许重试。 - 375/768/1024/1440 无横向溢出,44px 目标、键盘可用、aria-live、reduced-motion。 Admin: - 开关关闭/开启、读取中、保存中、冲突、无权限、网络失败状态明确。 - 开启和关闭均需要确认,文案说明只影响自助注册,不影响登录和管理员建号。 ## 测试与验收 - migration 1→12、12 down/up;NULL email down 门禁。 - 策略默认关闭、读取失败关闭、Admin CAS 与审计。 - 注册成功、关闭、CSRF、字段边界、重复与并发冲突、bcrypt、自动登录、禁用后登录失败。 - 直连/可信代理/伪造 XFF、5 次/15 分钟限流及成功不清零。 - Portal/Admin 单元、MySQL 集成、四视口浏览器 E2E、构建、vet、race、脱敏扫描。 - 不调用 Provider,不使用真实用户或生产数据。 ## 风险与回退 - 公开注册会增加滥用面;默认关闭、独立限流、可信代理解析与失败关闭控制风险。 - 6 位密码风险高于推荐值;按用户明确决定保留,并通过登录限流、注册限流和 bcrypt 缓解,不宣称等同强密码。 - 回退先由 Admin 关闭注册,再回退应用;确认无 NULL email 用户后才执行 migration down。 - 关闭注册不删除任何用户。 ## 文档影响 确认后更新 Product-Requirements-Overview、Business-Rules-and-Glossary、Architecture-and-Code-Map、Local-Development-and-Verification、Deployment-and-Operations。
Author
Owner

技术设计 v1 已整理完成,状态:待用户确认。

确认范围包括:migration 12、users.email 可空、默认关闭的数据库策略、Portal 注册 API 与错误契约、Admin 乐观锁开关、可信代理 IP 解析、独立注册限流、安全事件审计、事务内策略复核、自动化测试和回退边界。

用户于 2026-08-29 明确选择注册密码最少 6 位;设计保留最大 1024 字符和 bcrypt:v1,不引入邮箱验证、验证码、计费、点数或每日配额。

关联原型:#78。#77 与 #78 均确认前,不建立和实施生产代码工单。

技术设计 v1 已整理完成,状态:待用户确认。 确认范围包括:migration 12、users.email 可空、默认关闭的数据库策略、Portal 注册 API 与错误契约、Admin 乐观锁开关、可信代理 IP 解析、独立注册限流、安全事件审计、事务内策略复核、自动化测试和回退边界。 用户于 2026-08-29 明确选择注册密码最少 6 位;设计保留最大 1024 字符和 bcrypt:v1,不引入邮箱验证、验证码、计费、点数或每日配额。 关联原型:#78。#77 与 #78 均确认前,不建立和实施生产代码工单。
Author
Owner

用户于 2026-08-29 明确验收通过技术设计 v1。结论:数据库、API、安全、并发、配置、测试和回退基线已确认;允许据此拆分生产实现工单。

用户于 2026-08-29 明确验收通过技术设计 v1。结论:数据库、API、安全、并发、配置、测试和回退基线已确认;允许据此拆分生产实现工单。
ila closed this issue 2026-08-29 20:59:30 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/chorus#77