Files
deer-flow/docs/superpowers/specs/2026-04-04-auth-module-design.md
T
greatmengqi 27b66d6753 feat(auth): authentication module with multi-tenant isolation (RFC-001)
Introduce an always-on auth layer with auto-created admin on first boot,
multi-tenant isolation for threads/stores, and a full setup/login flow.

Backend
- JWT access tokens with `ver` field for stale-token rejection; bump on
  password/email change
- Password hashing, HttpOnly+Secure cookies (Secure derived from request
  scheme at runtime)
- CSRF middleware covering both REST and LangGraph routes
- IP-based login rate limiting (5 attempts / 5-min lockout) with bounded
  dict growth and X-Forwarded-For bypass fix
- Multi-worker-safe admin auto-creation (single DB write, WAL once)
- needs_setup + token_version on User model; SQLite schema migration
- Thread/store isolation by owner; orphan thread migration on first admin
  registration
- thread_id validated as UUID to prevent log injection
- CLI tool to reset admin password
- Decorator-based authz module extracted from auth core

Frontend
- Login and setup pages with SSR guard for needs_setup flow
- Account settings page (change password / email)
- AuthProvider + route guards; skips redirect when no users registered
- i18n (en-US / zh-CN) for auth surfaces
- Typed auth API client; parseAuthError unwraps FastAPI detail envelope

Infra & tooling
- Unified `serve.sh` with gateway mode + auto dep install
- Public PyPI uv.toml pin for CI compatibility
- Regenerated uv.lock with public index

Tests
- HTTP vs HTTPS cookie security tests
- Auth middleware, rate limiter, CSRF, setup flow coverage
2026-04-08 00:31:43 +08:00

229 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Auth Module Design Spec
**Date:** 2026-04-04
**Status:** Approved
**PR:** #1728
## Overview
DeerFlow 内置认证模块,始终强制,零配置启动。首次启动自动创建 admin,用户通过控制台日志获取初始密码,首次登录时设置真实邮箱和新密码。
## Design Decisions
| 决策 | 选择 | 拒绝的替代方案 | 理由 |
|------|------|---------------|------|
| Auth 模式 | 始终强制 | 渐进式 / 可关闭 | 无竞争窗口,无条件分支 |
| Admin 创建 | 启动时自动创建 + 随机密码 | Setup 页面自注册 / 环境变量注入 | 零配置 + 无竞争 |
| 密码发现 | 控制台日志 | 密码文件 | 无文件权限/删除/gitignore 问题 |
| 首次登录 | 强制 setup(改邮箱 + 改密码) | 可选修改 | 避免 admin@localhost 成为永久账号 |
| Setup 接口 | 扩展 change-password | 新增 /setup 端点 | YAGNI,功能完全覆盖 |
| needs_setup 存储 | DB 字段 | JWT payload / 文件 | Single source of truth |
| Auth 保护范围 | 全局 middleware + allowlist | 逐路由装饰器 | 漏加装饰器 = 漏洞 |
| 注册 | 开放(user 角色) | 默认关闭 / 邀请制 | 团队工具,低滥用风险 |
| Token 失效 | password_version 字段 | blocklist / secret rotation | 最简实现 |
## Data Model
### User 表变更
新增两个字段:
```sql
needs_setup BOOLEAN NOT NULL DEFAULT FALSE
token_version INTEGER NOT NULL DEFAULT 0
```
- `needs_setup`: 自动创建的 admin 为 True,完成 setup 后 False
- `token_version`: 每次改密码 +1,JWT 校验时比对
已有数据库升级:`ALTER TABLE users ADD COLUMN ... DEFAULT ...`,用 try/except 兼容。
### JWT Payload
```json
{
"sub": "user_id",
"ver": 0,
"exp": "...",
"iat": "..."
}
```
`ver` 对应 `User.token_version`,decode 时查 DB 比对,不匹配则 401。
## Startup Flow
```
Gateway lifespan 启动
count_users() == 0?
├─ YES → 创建 admin@deerflow.dev (needs_setup=True, 随机密码)
│ → 迁移无 user_id 的 thread 到 admin
│ → 控制台输出邮箱 + 密码
├─ NO, 有 needs_setup=True 的用户 → 日志提醒完成设置或用 reset_admin
└─ NO, 无 needs_setup → 正常启动
```
### 多进程安全
- SQLite UNIQUE 约束处理竞争
- 第二个实例 INSERT 失败 → 捕获 ValueError → 静默跳过
### SQLite WAL 模式
`_init_users_table()` 中执行 `PRAGMA journal_mode=WAL`(首次建表时设置,DB 级持久生效)。允许并发读 + 单写不阻塞读。
## Auth Enforcement
### 双层 Auth 架构
**第一层:全局 AuthMiddleware**`gateway/auth_middleware.py`fail-closed safety net
```python
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
if _is_public(request.url.path):
return await call_next(request)
if not request.cookies.get("access_token"):
return JSONResponse(401, ...)
return await call_next(request)
```
仅检查 cookie 是否存在,不验证签名。新 endpoint 即使忘加装饰器也不会裸奔。
**Public path 规则:**
- 前缀匹配:`/health`, `/docs`, `/redoc`, `/openapi.json`, `/api/v1/auth/`
- 其他所有路径默认需要 cookie
**第二层:`@require_auth` + `@require_permission` 装饰器**`gateway/authz.py`,细粒度控制)
验证 JWT 签名、用户存在性、token_version、resource:action 权限、owner_check。
两层职责不同、互不替代。
### CSRF
始终校验,无条件。Auth endpointlogin/register/logout)豁免。
## Login Flow
```
POST /api/v1/auth/login/local
验证密码 → 成功
检查 user.needs_setup
├─ True → 返回 {expires_in, needs_setup: true}
└─ False → 返回 {expires_in}
```
前端根据 `needs_setup` 决定跳转 `/setup` 还是 `/workspace`
## Setup Flow
```
/setup 页面(前端新增)
表单:邮箱 + 新密码 + 确认密码
POST /api/v1/auth/change-password
body: {current_password, new_password, new_email}
后端:更新邮箱 + 密码 + token_version++ + needs_setup=False
前端:跳转 /workspace
```
### SSR Guard
`getServerSideUser()` 返回的 `AuthResult` 增加 tag
```typescript
| { tag: "needs_setup"; user: User }
```
`workspace/layout.tsx`: `case "needs_setup": redirect("/setup")`
### change-password 接口扩展
新增可选字段 `new_email: EmailStr | None`。有值时同时更新邮箱(校验唯一性)。
如果 `user.needs_setup == True` 且提供了 `new_email`,成功后自动设 `needs_setup = False`
## Token Invalidation
改密码时 `token_version += 1`。JWT encode 时写入 `ver`
校验链路:`decode_token``TokenPayload` 包含 `ver``get_current_user_from_request` 查 DB → 比对 `user.token_version != payload.ver` → 401。
触发场景:
- 用户改密码
- admin 用 reset_admin CLI
- setup 流程完成
## Rate Limiting
登录端点 IP 级限速,内存计数器:
```python
_login_attempts: dict[str, tuple[int, float]] = {} # ip → (fail_count, lock_until)
```
- 同一 IP 连续 5 次失败 → 锁定 5 分钟
- 成功登录 → 重置计数
- 不引入 Redis,进程内 dict 够用
## Thread Migration
`_ensure_admin_user` 中,创建 admin 后同步执行:
- 扫描 Store 中所有 thread
- `metadata.user_id` 为空 → 写入 admin 的 user_id
- 日志记录迁移数量
在 lifespan 中同步执行(此时未接受请求),不存在并发问题。
## Password Recovery
```bash
python -m app.gateway.auth.reset_admin [--email user@example.com]
```
行为:
- 生成随机密码
- 更新密码 hash
- `token_version += 1`(旧 token 立即失效)
- `needs_setup = True`(下次登录走 setup 流程)
- 打印新密码到 stdout
## File Changes Summary
### Backend
| 文件 | 变更 |
|------|------|
| `auth/models.py` | User 加 `needs_setup`, `token_version` |
| `auth/jwt.py` | encode 加 `ver`TokenPayload 加 `ver` |
| `auth/repositories/sqlite.py` | DDL 加列 + ALTER TABLE 兼容 + WAL 模式 |
| `gateway/app.py` | `_ensure_admin_user` + thread 迁移 + AuthMiddleware |
| `gateway/auth_middleware.py` | 全局 auth middlewarecookie 存在性检查,fail-closed safety net |
| `gateway/deps.py` | decode 后校验 `token_version` |
| `gateway/routers/auth.py` | login 返回 `needs_setup`change-password 扩展 `new_email` + `needs_setup` + `token_version` |
| `gateway/routers/auth.py` | 登录限速 |
| `auth/reset_admin.py` | 设 `needs_setup=True` + `token_version++` |
### Frontend
| 文件 | 变更 |
|------|------|
| `core/auth/types.ts` | AuthResult 加 `needs_setup` tag |
| `core/auth/server.ts` | SSR guard 返回 `needs_setup` |
| `app/workspace/layout.tsx` | `needs_setup` → redirect `/setup` |
| `app/(auth)/setup/page.tsx` | 新页面:邮箱 + 新密码表单 |
## Deferred (Future Work)
- OAuth 登录(GitHub, Google
- 邮件验证 / 找回密码
- RBAC 细粒度权限
- CSRF token 定期轮换
- PostgreSQL 用户存储