Files
border-red-post-nalati/docs/AUTH_API.md
T

7.2 KiB
Raw Blame History

认证与角色权限接口(Issue #5)

本分支实现 Issue #5 的七个接口。认证分支 feat/backend-auth-sessions 基于 PR #6 的已验证后端提交,迁移依次为 0002_catalog → 0003_retrieval_runs → 0004_auth_sessions;PR #6 仍须独立审阅,本分支没有自行合并它。

启动与管理员初始化

在 backend/ 安装更新依赖并执行 alembic upgrade head,再运行:

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、时间和名字均为占位:

{
  "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、OWASP 密码存储、OWASP 认证、OWASP CSRF 防护。本项目使用 Argon2id(19 MiB、2 次迭代、并行度 1);会话与来源策略以本接口契约及代码为准。