Files
deer-flow/docs/superpowers/specs/2026-04-04-auth-module-design.md
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

6.9 KiB
Raw Permalink Blame History

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 表变更

新增两个字段:

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

{
  "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 架构

第一层:全局 AuthMiddlewaregateway/auth_middleware.pyfail-closed safety net

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

| { 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_tokenTokenPayload 包含 verget_current_user_from_request 查 DB → 比对 user.token_version != payload.ver → 401。

触发场景:

  • 用户改密码
  • admin 用 reset_admin CLI
  • setup 流程完成

Rate Limiting

登录端点 IP 级限速,内存计数器:

_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

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 加 verTokenPayload 加 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_setupchange-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 用户存储