docs: record portal authentication boundaries (#11)
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Architecture-and-Code-Map
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Architecture-and-Code-Map.-
|
||||
wiki_revision: 47686aa767849c6ecffc5db37a76a24564feb5e6
|
||||
synchronized_at: 2026-08-20T16:14:54Z
|
||||
wiki_revision: f9f5d67d06d86694948f5812ce8a565e30b4f266
|
||||
synchronized_at: 2026-08-20T16:41:50Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
@@ -56,7 +56,7 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
|
||||
- `migrations/000001_mvp0_base` 建立 `users`、`providers`、`provider_models`、`prompt_templates`、`generations`、`generation_inputs`、`generation_outputs`,生产启动不调用 AutoMigrate。
|
||||
- 迁移显式定义用户幂等唯一键、队列/历史索引、5 个外键和删除规则,以及状态、租约、错误、attempt 数组和输出内容检查约束。
|
||||
- `internal/seed/defaults.json` 保存已确认 Prompt 和 mock ProviderModel 数据;`cmd/chorus-seed` 在事务中幂等 upsert 构造用户、mock Provider/Model 和 Prompt,不保存或输出明文凭据。
|
||||
- `portal` 的 HTTP 路由仍待 #11;`portal/worker` 已在 #10 落地 Provider 选择、输入读取、上游调用、输出保存和最终 CAS,不得把 worker 完成误写成用户 API 已完成。
|
||||
- `portal/handler`、`service`、`auth` 和 `session` 已在 #11 落地用户 API 与安全边界;最终页面与静态资源仍属于 #12。`portal/worker` 负责异步上游调用,提交 handler 没有 Provider 依赖。
|
||||
|
||||
### #7 已落地的核心契约
|
||||
|
||||
@@ -96,6 +96,16 @@ MVP-0 第一张上传图自动作为 `primary`,其余为 `reference`;不提
|
||||
- 429/5xx/超时/连接错误以及 400/401/内容策略拒绝均映射为固定脱敏错误;MVP-0 记录 retryable 分类但不换 Provider、不自动重试。
|
||||
- `internal/platform/mockprovider` 和 `cmd/chorus-mock-provider` 提供本地协议 fixture;自动测试通过注入受控 DNS/dial 连接它,不放宽生产对回环/私网地址的 SSRF 拦截。
|
||||
- MySQL 8 集成测试分别完成一次 chat 和 images_edits,断言 rendered_prompt、attempt ProviderModel/latency、输出记录、原图和缩略图;全程不访问真实 Provider。
|
||||
### #11 已落地的 portal 认证与用户 API
|
||||
|
||||
- `portal/session` 使用服务端内存会话和 HMAC 不透明 Cookie;登录成功与退出都会轮换 session ID 和 CSRF token。Cookie 为 HttpOnly/SameSite=Lax,生产启用 Secure;写请求只接受 `X-CSRF-Token`,避免 multipart 在正文限流前被隐式解析。
|
||||
- MVP-0 会话仅支持单 portal 进程,重启后统一失效;会话数量有内存上限并在创建时清理过期项。多实例共享会话不在 MVP-0 范围。
|
||||
- `internal/platform/password` 固定 `bcrypt:v1:<hash>` 版本化编码;未知版本失败关闭。登录对未知账号、错误密码和禁用账号执行同类密码校验并返回同一错误,按远端地址做内存窗口节流。
|
||||
- `portal/service` 统一处理 prompt 渲染、用户作用域幂等、上传数量/单文件/总量、声明与实际 MIME、扩展名、解码和像素校验;第一张图为 primary,其余为 reference。
|
||||
- `CreateIdempotentPrepared` 在事务插入 generation 后才执行 inputs 回调,因而文件 metadata 可使用真实 generation ID;幂等重放不执行回调,回调或数据库失败只补偿删除本次保存文件。
|
||||
- 同步提交只创建 pending、inputs 和 rendered_prompt,不 import 或调用 Provider/worker,也不读取点数。JSON 与 HTMX 查询共享同一用户作用域服务,认证过期返回 401;HTMX 额外返回登录跳转提示,不修改 generation 状态。
|
||||
- 任务、原图、生成图和缩略图查询先用 generation.user_id 过滤,再核对文件 metadata 的 owner/generation;响应只给受控 URL、内容和安全文件名,不暴露 storage key 或文件系统路径。
|
||||
- Gin 1.12.0 与用户指定 go-admin 基线一致。portal 不注册公开文件目录、注册、找回密码、管理员登录、点数或同步上游路由。
|
||||
## 代码地图
|
||||
|
||||
| 想改什么 | 从哪里开始读 | 必要验证 |
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Local-Development-and-Verification
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Local-Development-and-Verification.-
|
||||
wiki_revision: 0f7d68926b23995be4481f05218484be1301a780
|
||||
synchronized_at: 2026-08-20T16:15:02Z
|
||||
wiki_revision: 075e82a07b58c105c46d7fbcfb22d8ed6d8f97f8
|
||||
synchronized_at: 2026-08-20T16:45:23Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 本地开发与验证
|
||||
@@ -45,6 +45,13 @@ synchronized_at: 2026-08-20T16:15:02Z
|
||||
| `CHORUS_SESSION_KEY` | portal 会话,必须与主密钥分离 |
|
||||
| `CHORUS_STORAGE_ROOT` | 受保护生成物目录 |
|
||||
| `CHORUS_ENV` | `development/test/production`,生产强制安全开关 |
|
||||
| `CHORUS_SESSION_TTL_MINUTES` | 会话有效分钟数;生产必填 |
|
||||
| `CHORUS_LOGIN_ATTEMPTS` / `CHORUS_LOGIN_WINDOW_SECONDS` | 单远端地址登录窗口限制;生产必填 |
|
||||
| `CHORUS_MAX_PROMPT_BYTES` | prompt UTF-8 字节上限;生产必填 |
|
||||
| `CHORUS_MAX_IMAGES` | 单任务图片数量上限;生产必填 |
|
||||
| `CHORUS_MAX_IMAGE_BYTES` / `CHORUS_MAX_UPLOAD_BYTES` | 单图/整次 multipart 字节上限;生产必填 |
|
||||
| `CHORUS_MAX_IMAGE_PIXELS` | 单图解码像素上限;生产必填 |
|
||||
| `CHORUS_HISTORY_LIMIT` | 单次历史查询上限;生产必填 |
|
||||
| `GITEA_TOKEN` | Wiki/工单操作,不影响产品服务 |
|
||||
|
||||
## 第一次运行
|
||||
@@ -144,6 +151,8 @@ go run ./cmd/chorus-mock-provider
|
||||
|
||||
生产安全 Client 始终拒绝回环/私网 Provider URL。自动化端到端测试使用构造公共域名解析和受控 `DialContext` 把请求送到进程内 mock,不应为了让 portal 直连本机 mock 而放宽 SSRF 规则。
|
||||
|
||||
种子密码从 #11 起保存为 `bcrypt:v1:<hash>`。已有无版本构造哈希不会被登录兼容;使用同一构造邮箱和本次选择的密码重新运行 `go run ./cmd/chorus-seed`,会更新密码哈希而不新增用户。开发/测试未提供 session key 时进程随机生成临时 key,重启后会话失效;生产必须显式提供至少 32 字节且与 Provider 主密钥分离的 key。
|
||||
|
||||
### 5. 启动 portal(MVP-0)
|
||||
|
||||
```powershell
|
||||
@@ -196,6 +205,14 @@ $env:CHORUS_TEST_DSN = $null
|
||||
```
|
||||
|
||||
- Provider:全部 retryable 类别使用 mock,不消耗真实额度。
|
||||
- Portal:真实 MySQL 8 覆盖登录/退出、session/CSRF 轮换、用户作用域幂等、pending 提交、上传边界、任务与文件跨用户授权、终态和 HTMX 401;测试结束 generation/input/output 和构造用户必须为 0。
|
||||
|
||||
```powershell
|
||||
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
|
||||
go test -v -count=1 ./portal/handler/... ./portal/service/... ./portal/session/... ./portal/auth/...
|
||||
$env:CHORUS_TEST_DSN = $null
|
||||
```
|
||||
|
||||
- SSRF:IPv4/IPv6 私网、DNS rebinding、redirect 链、环境代理、结果 URL。
|
||||
- 安全:密码/session/CSRF、登录节流、跨用户任务与文件、错误脱敏、密钥轮换。
|
||||
- 浏览器:HTMX 动态状态、终态停止、认证过期、网络错误;375/768/1024,无主区域横向滚动,键盘和 reduced-motion。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Deployment-and-Operations
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Deployment-and-Operations.-
|
||||
wiki_revision: 40b115d8da2f8b7d4e01783eb47a73659cc4d67f
|
||||
synchronized_at: 2026-08-20T16:15:29Z
|
||||
wiki_revision: efd73fb39c69cb94a9da7e2157b765b4f8da2431
|
||||
synchronized_at: 2026-08-20T16:42:26Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 部署与运维
|
||||
@@ -54,7 +54,11 @@ portal → /var/lib/chorus/storage(或后续对象存储适配)
|
||||
| `CHORUS_SESSION_KEY` | portal 会话 | 是 |
|
||||
| `CHORUS_STORAGE_ROOT` | 生成物目录 | 否 |
|
||||
| 上游/存储超时、lease、worker 数 | 执行与优雅退出 | 否 |
|
||||
| 上传和限流阈值 | 数量、大小、MIME、像素、请求频率 | 否 |
|
||||
| `CHORUS_SESSION_TTL_MINUTES` | 服务端内存会话有效期 | 否 |
|
||||
| `CHORUS_LOGIN_ATTEMPTS` / `CHORUS_LOGIN_WINDOW_SECONDS` | 登录节流窗口 | 否 |
|
||||
| `CHORUS_MAX_PROMPT_BYTES` | prompt 字节上限 | 否 |
|
||||
| `CHORUS_MAX_IMAGES` / `CHORUS_MAX_IMAGE_BYTES` / `CHORUS_MAX_UPLOAD_BYTES` / `CHORUS_MAX_IMAGE_PIXELS` | 上传边界 | 否 |
|
||||
| `CHORUS_HISTORY_LIMIT` | 历史查询上限 | 否 |
|
||||
| 保留期与磁盘告警 | 清理和容量保护 | 否 |
|
||||
|
||||
生产启动必须校验必需配置、密钥长度/key_id、目录权限、超时与 lease 关系;缺失或不安全时 fail closed。`CHORUS_ENV=production` 下若检测到 AutoMigrate 或 dev-tools 注册必须拒绝启动。
|
||||
@@ -105,6 +109,13 @@ journalctl -u chorus-portal -n 100 --no-pager
|
||||
|
||||
MVP-1 同样检查 admin 8081。外部检查经 TLS/nginx 访问,不把内部端口暴露公网。
|
||||
|
||||
## portal 会话与请求边界
|
||||
|
||||
MVP-0 的终端用户会话保存在单个 portal 进程内存中,Cookie 只携带签名后的不透明 session ID。部署必须保持单实例;portal 重启或切换版本会让全部终端用户重新登录。不要在负载均衡后启动多个 portal 实例,除非先建立新的共享会话设计和工单。
|
||||
|
||||
生产 Cookie 必须为 Secure/HttpOnly/SameSite=Lax。反向代理不得记录 Cookie 或 CSRF header;请求日志和 GORM SQL 使用参数化输出。JSON 正文、multipart 总量、单图字节/像素/数量和历史查询都有显式生产配置,缺少任一阈值时启动失败。
|
||||
|
||||
上传先在应用层校验,再在 generation 事务回调中保存带 owner/generation metadata 的文件。事务失败只清理本请求保存的 key;存储根目录不能作为 nginx/static 目录暴露。
|
||||
## worker 优雅退出
|
||||
|
||||
- 收到 TERM 后先停止认领新任务;
|
||||
|
||||
Reference in New Issue
Block a user