feat: add cookie sessions and backend role authorization
This commit is contained in:
@@ -14,7 +14,7 @@ FastAPI + Pydantic
|
||||
|-- 来源获取 -> 人工核验 -> 文本切分 -> 生成向量 -> 发布版本
|
||||
```
|
||||
|
||||
当前后端实现健康/数据库就绪检查、来源与文档管理、知识增删改查、分页筛选、人工审核、历史快照及独立关键词检索;问答仍为占位接口。数据库由 Alembic 显式迁移并启用 pgvector,尚无向量字段、向量索引和 RAG。前端来源登记/查询已接入,实验页仍为静态原型,文档、审核、完整分页与检索页面需继续联调。
|
||||
当前后端实现健康/数据库就绪检查、来源与文档管理、知识增删改查、分页筛选、人工审核、历史快照、认证会话/角色权限及独立关键词检索;问答仍为占位接口。数据库由 Alembic 显式迁移并启用 pgvector,尚无向量字段、向量索引和 RAG。前端来源登记/查询已接入,实验页仍为静态原型,文档、审核、完整分页与检索页面需继续联调。
|
||||
|
||||
## 阶段 2 数据边界
|
||||
|
||||
@@ -27,7 +27,9 @@ FastAPI + Pydantic
|
||||
- 旧库首次升级自动接管基础表,保留条目与导入快照。没有完整审核记录的旧 `verified` 状态回到待审核。启动不自动迁移,缺失或不匹配的版本会明确报错。
|
||||
|
||||
## 安全边界
|
||||
当前无认证与权限控制,仅供本机开发;正式部署前须补充登录鉴权、接口限流、数据库迁移、密钥管理、日志脱敏、CORS 收敛和 HTTPS。
|
||||
新增 `users`、`auth_sessions` 与 `auth_rate_limits`。Argon2id 保存密码哈希,随机 Cookie 会话只保存摘要,角色逐次由数据库校验。来源白名单与会话 CSRF 保护写入;改密撤销所有会话。管理资源和检索历史均限管理员,公开检索只返回已核验资料。具体字段、来源、Cookie、预算和迁移见 [认证接口契约](AUTH_API.md)。
|
||||
|
||||
当前仍供本机开发与联调;完整生产部署、网关限流、访问审计、邮箱验证、账号恢复和前端权限页面仍待后续工程验收。
|
||||
|
||||
## 关键词检索基线(T06)
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# 认证与角色权限接口(Issue #5)
|
||||
|
||||
本分支实现 [Issue #5](https://gitea.kronecker.cc/saint/border-red-post-nalati/issues/5) 的七个接口。认证分支 `feat/backend-auth-sessions` 基于 PR #6 的已验证后端提交,迁移依次为 `0002_catalog → 0003_retrieval_runs → 0004_auth_sessions`;PR #6 仍须独立审阅,本分支没有自行合并它。
|
||||
|
||||
## 启动与管理员初始化
|
||||
|
||||
在 `backend/` 安装更新依赖并执行 `alembic upgrade head`,再运行:
|
||||
|
||||
```bash
|
||||
python -m app.manage create-admin --username local_admin
|
||||
```
|
||||
|
||||
命令通过终端隐藏输入和确认密码,不接受密码参数,不创建默认账号;已有用户名不会被升级为管理员或覆盖密码。生产密码请使用独立长口令,不把账号、密码、Cookie 或响应保存到仓库。迁移只新增表,不预置任何用户。
|
||||
|
||||
默认 `AUTH_COOKIE_SECURE=true`,必须通过 HTTPS 使用会话。本机 HTTP 联调时可在未入库的 `backend/.env` 显式设置 `APP_ENV=development` 和 `AUTH_COOKIE_SECURE=false`,保持服务只绑定回环地址。其他环境拒绝关闭 Secure;非开发/测试环境的认证与 CORS Origin 必须均为 HTTPS。不要把本机例外带到部署环境。
|
||||
|
||||
`AUTH_ALLOWED_ORIGINS` 独立控制写请求的来源,默认允许 `localhost/127.0.0.1` 的 5173、8000 端口;`CORS_ORIGINS` 控制前端跨域读取。两者均须填写完整 `scheme://host:port`,不含路径/尾斜杠/通配符。部署时按实际地址同时收敛这两项。代理层仅信任受控代理,不能任意接受外部转发 IP 头。
|
||||
|
||||
## 接口契约
|
||||
|
||||
| 接口 | 请求 | 成功响应与权限 |
|
||||
|---|---|---|
|
||||
| POST `/api/auth/register` | `username/password`,可选 `nickname/email` | 201 用户资料;仅创建 `user`,不自动登录 |
|
||||
| POST `/api/auth/login` | `username/password` | 200 用户资料及 `csrf_token`;设置会话 Cookie |
|
||||
| POST `/api/auth/logout` | 无正文 | 204;要求当前会话和 CSRF,作废该会话并清除 Cookie |
|
||||
| GET `/api/auth/me` | 无 | 200 用户资料及当前 `csrf_token`;要求有效会话 |
|
||||
| PATCH `/api/users/me` | `nickname` 和/或 `email` | 200 当前用户资料;会话与 CSRF 必需 |
|
||||
| POST `/api/auth/change-password` | `current_password/new_password` | 204;验证当前密码后撤销该用户全部会话并清除 Cookie,须重新登录 |
|
||||
| GET `/api/admin/users` | `limit` 默认/最大 100,`offset` 默认 0 | 200 用户数组,按 ID 升序;仅 `admin` |
|
||||
|
||||
所有输入拒绝未知字段。注册和资料修改均不接受 `role/id/password_hash`,不能修改他人资料或用户名。用户名去首尾空白并转小写,须为字母开头的 3–32 位 ASCII 字母、数字或下划线。新密码为 15–128 字符,不修剪、不截断;旧密码只作校验。昵称最长 80 字符且不能含控制字符;邮箱须为合法地址,最长 254 字符。昵称/邮箱的空字符串或 `null` 用于清空;邮箱尚未验证,不能用于登录、找回密码或唯一身份判定。
|
||||
|
||||
用户资料为 `{id, username, nickname, email, role, created_at}`;登录和 `/me` 额外返回 `csrf_token`。响应不包含密码哈希、会话 Cookie 值或内部凭据版本。示例中的 ID、时间和名字均为占位:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"username": "example_user",
|
||||
"nickname": null,
|
||||
"email": null,
|
||||
"role": "user",
|
||||
"created_at": "2026-10-16T00:00:00Z",
|
||||
"csrf_token": "<由服务端返回的当前会话 CSRF 值>"
|
||||
}
|
||||
```
|
||||
|
||||
错误使用 `detail`:参数 422、用户名冲突 409、登录错误/会话失效 401、权限/来源/CSRF 错误 403、当前密码错误 400、认证请求过多 429(`Retry-After: 300`)。参数错误不回显输入值,避免原样返回密码。认证及其他 `/api/` 响应均使用 `Cache-Control: no-store`。
|
||||
|
||||
## Cookie、CSRF 与授权
|
||||
|
||||
- Cookie 名 `nalati_session`,HttpOnly、Secure(默认)、SameSite=Lax、Path=/、不设置 Domain。服务端生成 256 位随机会话标识;数据库仅保存其 SHA-256 摘要、用户、凭据版本及到期时间。固定有效期 `AUTH_SESSION_SECONDS` 默认 28800 秒,可配置 300–604800 秒,不自动续期。
|
||||
- 登录轮换本浏览器旧会话;退出仅撤销当前会话;改密通过用户行锁、凭据版本递增与删除全部会话使旧凭据立即失效。授权每次从数据库读取当前角色,不信任前端角色或 Cookie 内的角色声明。
|
||||
- 登录/注册即检查精确匹配的 `Origin`,缺失或不可信均拒绝,避免登录 CSRF。已认证的 POST/PATCH/DELETE 等写请求还须带 `X-CSRF-Token`,值来自登录或 `/me`。该值由当前随机 Cookie 经独立 HMAC 派生,不能跨会话复用。后端不从请求正文、URL 或客户端角色获取权限。
|
||||
- `knowledge`、`sources`、`documents` 的全部读写及历史、管理员用户列表、检索历史列表/详情均要求 `admin`。因此旧匿名知识管理调用现在返回 401,普通用户返回 403。健康检查、公开的已核验关键词检索和当前占位问答保持公开;正式问答、实验接口接入时须重新明确各自授权。
|
||||
- 登录及注册共用每客户端地址 30 次/5 分钟预算;登录另有规范用户名 10 次/5 分钟预算,成功/失败都计数。计数通过 PostgreSQL 原子更新,跨进程/重启有效;仅保存带类型前缀的摘要,旧预算在下一次认证请求时清理。此处为基础防暴力尝试,不代替部署网关限流与监控。
|
||||
|
||||
前端 `src/services/auth.ts` 和 `src/services/session.ts` 提供真实 Cookie 请求封装:CSRF 只存内存,刷新后通过 `/me` 恢复;知识写入也使用同一封装。登录入口、路由守卫和权限页面交互仍由前端接入;远程 main 注册原型的最低密码提示须同步为 15 字符。当前 `dev` 页面没有完整登录流程,不能把 API 完成视作浏览器验收完成。
|
||||
|
||||
## 验证与回滚
|
||||
|
||||
在 `backend/` 执行 `ruff check .`、`ruff format --check .`、`pytest -q`;在 `frontend/` 执行 `npm run build`。测试使用隔离 PostgreSQL 和占位账号,覆盖权限拒绝、CSRF、输入越权、哈希、轮换/撤销/过期、改密、重启后会话恢复与退出持久化、预算、迁移及既有检索回归。测试库不代表 Docker 或浏览器验收。
|
||||
|
||||
备份后可配合旧后端执行 `alembic downgrade 0003_retrieval_runs`,删除用户、会话与认证预算表,保留知识和检索历史;账号与哈希恢复依赖备份。降级会撤除授权边界,旧服务只能按其原开发范围使用,不应用于公开部署。继续降至 `0002_catalog` 还会删除检索历史。
|
||||
|
||||
尚未实现邮箱验证、找回密码、MFA、角色管理 API、会话管理页面、访问审计和完整生产部署;没有把这些功能标记为完成。
|
||||
|
||||
实现依据:[argon2-cffi API](https://argon2-cffi.readthedocs.io/en/stable/api.html)、[OWASP 密码存储](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)、[OWASP 认证](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)、[OWASP CSRF 防护](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)。本项目使用 Argon2id(19 MiB、2 次迭代、并行度 1);会话与来源策略以本接口契约及代码为准。
|
||||
@@ -28,3 +28,10 @@
|
||||
状态:分支实现,待审阅。采用固定 Jieba 搜索分词及关闭 HMM 的词典模式,加入项目名称词,并使用固定停用词表。选择标题重复权重 2、正文权重 1 的 BM25 正值平滑 IDF 变体,不称为 BM25F;查询词去重,同分按知识 ID 排序。参数、分词标识、实际语料指纹及出处版本随每次结果保存,方便冻结后续实验配置。分词接口参考 [Jieba 官方说明](https://github.com/fxsjy/jieba),BM25 基础参考 [Stanford IR 教材](https://nlp.stanford.edu/IR-book/html/htmledition/okapi-bm25-a-non-binary-model-1.html);具体平滑公式与权重以本仓库实现和手算测试为准。
|
||||
|
||||
当前检索单元为已审核知识条目,在设置的容量内按请求计算,不提前实现向量切分/索引更新;超过容量明确失败。`retrieval_runs` 仅记录当时结果,不代表该条目之后仍然获得审核批准。网络搜索、模型选择和真实论文实验保持原阶段边界。
|
||||
|
||||
|
||||
## ADR-009 服务端会话与最小角色边界
|
||||
|
||||
状态:Issue #5 分支实现,待审阅。使用 Argon2id 密码哈希、数据库保存摘要的随机 Cookie 会话,以及独立 CSRF 请求头与显式 Origin 白名单;不把角色或会话凭据放入浏览器持久存储。普通注册只能创建 user,管理员仅由本机隐藏密码输入命令创建。知识/来源/文档管理及检索历史限 admin,公开检索继续只使用已核验依据。完整契约与实现依据见 [AUTH_API.md](AUTH_API.md)。
|
||||
|
||||
为改密和会话撤销使用用户行锁与凭据版本,认证预算使用数据库原子计数;不依赖单进程内存限流。现有前端写请求同步 Cookie/CSRF 封装,页面和路由联调仍待前端完成。本分支叠加 PR #6,后续审阅需关注迁移顺序及匿名管理调用变为 401 的行为变化。
|
||||
|
||||
@@ -52,3 +52,12 @@
|
||||
- 当前主机 Docker 服务仍未运行;本轮继续使用隔离 PostgreSQL 做接口与迁移测试,不把容器或浏览器端联调标记为完成。独立任务 Issue 仍须补齐。
|
||||
- 新增检索接口、中文分词和 BM25 基线、有效审核筛选、原文片段、来源与版本、语料指纹及检索历史;前端调用层同步类型与接口,页面接入由前端继续联调。
|
||||
- 分支验证:`ruff check .`、`ruff format --check .`、`pytest -q`;本轮 42 项 PostgreSQL/单元/真实 HTTP 测试通过,覆盖手算分数、标题权重、排序复现、审核边界、来源失效、容量拒绝、迁移兼容和服务重启后的检索历史。前端 `npm run build` 通过,包含新增检索调用类型的检查。全部使用占位资料,不是论文实验结果。
|
||||
|
||||
## 2026-10-16 Issue #5:认证与角色权限
|
||||
|
||||
- 按继续开发的授权,在 `feat/backend-auth-sessions` 推进 T10 的认证部分;叠加 PR #6(`8ac9dca`),不等待其合并,不自行批准或合并。PR #6 与本分支分别保持可追踪记录。
|
||||
- 实现注册、登录、退出、当前会话、本人资料修改、改密及管理员用户列表;注册拒绝角色字段,知识/来源/文档管理及检索历史均由服务端要求 admin。
|
||||
- 新增 `0004_auth_sessions`,保存 Argon2id 密码哈希、会话摘要与到期/凭据版本、共享认证请求预算;不预置用户,提供本机隐藏密码输入的管理员初始化命令。迁移和回滚保留知识及检索记录。
|
||||
- 默认 Secure/HttpOnly/SameSite Cookie;写操作校验明确 Origin 及当前会话 CSRF。改密使所有旧会话失效,退出持久化撤销;参数错误不回显密码。字段、来源、CSRF 和前端交接见 [认证接口契约](AUTH_API.md)。
|
||||
- 实际验证:`ruff check .`、`ruff format --check .` 通过;`pytest -q` **70 passed**,包含认证、授权、CSRF、输入越权、过期/轮换/撤销、限额、迁移及既有知识/检索回归;真实 HTTP 服务重启验证会话恢复和退出后不再有效。前端 `npm run build` 通过。
|
||||
- 当前仍使用隔离 PostgreSQL 做自动化验证;Docker 容器、前端登录/角色页面及真实浏览器端流程仍待验收。远程 main 的注册原型最低密码提示需要按本契约同步到 15 字符,并接入带 Cookie/CSRF 的调用层。没有宣称已完成前端集成或生产部署。
|
||||
|
||||
Reference in New Issue
Block a user