Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RustLoom 开发手册

本手册是 RustLoom 开发规范的单一事实来源,面向参与设计、开发、测试和 发布的贡献者。README 只提供项目入口,具体规则应在本手册中维护。

RustLoom 当前已经具备:

  • Axum HTTP 服务和 Tokio 运行时;
  • PostgreSQL 连接池;
  • 由 SQLx 管理的可逆数据库迁移;
  • 进程存活与数据库就绪检查;
  • 邮箱注册、密码登录、当前设备退出和只读 Profile;
  • PostgreSQL 服务端 Session 与安全 Cookie;
  • 自动化代码检查和文档构建。

身份能力仍是安全 MVP,不包含邮箱验证、密码找回、限流、权限或公网生产加固。

设计原则

  • 先交付可验证的最小能力,再增加抽象。
  • 所有数据库结构变化必须进入版本化 migration。
  • 对外接口和错误码必须稳定、可测试、可记录。
  • 文档与代码在同一 PR 中同步更新。
  • 开发环境可以便利,生产默认必须安全。

快速开始

环境要求

  • Rust stable,支持 Rust 2024 Edition
  • Docker 和 Docker Compose

初始化环境变量并安装锁定版本的开发依赖:

make init
make install

启动服务

make db
make db-migrate
make dev

应用在宿主机运行,PostgreSQL 在 Compose 中运行。默认地址是 127.0.0.1:3000,默认数据库连接为 postgres://rustloom:rustloom@127.0.0.1:5432/rustloom。

make init 只在 .env 不存在时从 .env.example 创建文件,不覆盖已有配置。 make db 启动并等待当前数据服务健康;默认服务列表为 postgres,以后新增 Redis 等 Compose 数据服务时同步扩展 DATA_SERVICES。make db-migrate 会先确保数据 服务已启动,再执行 forward-only migration。make dev 包含前述数据库准备步骤, 并以 Rust dev profile 启动应用。

验证服务:

curl -i http://127.0.0.1:3000/health
curl -i http://127.0.0.1:3000/ready

注册并读取 Profile:

curl -i -c .cookies \
  -H 'content-type: application/json' \
  -d '{"email":"user@example.com","password":"correct horse 电池 staple","display_name":"Shawn"}' \
  http://127.0.0.1:3000/api/v1/auth/register
curl -i -b .cookies http://127.0.0.1:3000/api/v1/profile

停止数据库但保留数据:

docker compose stop postgres

删除本地数据库数据是破坏性操作,只能在明确需要重建开发环境时执行:

docker compose down --volumes

环境变量

变量必填默认值用途
DATABASE_URL是无PostgreSQL 连接字符串
DATABASE_MAX_CONNECTIONS否10连接池最大连接数
DATABASE_ACQUIRE_TIMEOUT_SECONDS否2获取数据库连接的最大等待秒数
SESSION_COOKIE_SECURE否false是否给 Session Cookie 设置 Secure;生产必须为 true
SERVER_ADDRESS否127.0.0.1:3000HTTP 监听地址

应用启动时会读取仓库根目录的 .env。.env 不得提交,新增变量时必须同步 更新 .env.example 和本页。

应用架构

RustLoom 保持单个 Cargo package 和单体二进制,不建立 workspace 或微服务。

模块职责

  • main:加载环境、建立数据库连接、启动 Session 清理任务、绑定端口并服务。
  • config:解析和校验环境变量,不包含业务逻辑。
  • database:创建 PostgreSQL 连接池。
  • app:组合 Router、共享状态和 Session middleware。
  • identity:输入校验、身份 handler、认证提取器、用户查询和密码服务。
  • error:把内部错误转换为稳定的对外 JSON 错误。

identity 内部按真实职责分为 handler/validation、store 和 password,但没有 repository trait 或通用 service 抽象。只有出现第二个使用者时才提取接口。

身份请求路径

HTTP request
  -> Session middleware
  -> Axum identity handler / AuthenticatedUser extractor
  -> input validation
  -> password blocking task and/or SQLx user store
  -> PostgreSQL users + tower_sessions.session
  -> stable JSON response and optional Set-Cookie

注册在用户写入成功后建立 Session;登录在密码校验成功后轮换 Session ID; Profile 的认证提取器同时检查闲置有效性、7 天绝对期限和用户是否仍然存在。

数据库与进程边界

应用启动必须成功连接 PostgreSQL;连接失败时进程以非零状态退出。/ready 在运行时检查数据库查询能力。

Migration 是部署前显式步骤。应用启动不调用 sqlx migrate run,也不允许 Session Store 自行创建表。过期 Session 清理只删除数据,不改变 schema。

数据库设计

首个数据库目标是 PostgreSQL 18。开发环境固定使用 postgres:18.4-alpine, 生产环境应持续安装 PostgreSQL 18 的最新补丁版本。

users

users 是首版全局登录用户表:

字段类型约束
iduuid主键,由应用生成
emailvarchar(254)去除首尾空白并转小写,全局唯一
display_namevarchar(80)去除首尾空白,长度 1–80
password_hashtext只保存密码散列,不允许空值
created_attimestamptz创建时间
updated_attimestamptz最近更新时间

首版不建立角色、权限、Tenant、Profile 扩展表或软删除字段。查询 Profile 时 必须显式选择公开字段,不能使用 SELECT * 把 password_hash 带入响应模型。

tower_sessions.session

Session Store 使用独立 schema:

字段类型用途
idtext不透明 Session ID
databyteaSession 序列化数据
expiry_datetimestamptz过期时间和清理索引

该结构与 tower-sessions SQLx Store 兼容,但表由仓库 migration 创建。应用代码 不得调用 Store 自带的 migrate()。应用每小时清理过期记录;Session 闲置 12 小时失效,并由应用层额外执行 7 天绝对期限。

时间与标识

  • 数据库存储统一使用 timestamptz 和 UTC。
  • 对外时间使用 RFC 3339。
  • 用户 ID 使用 UUID,不把自增序号暴露为公共身份。

数据库迁移

SQLx migration 是数据库结构版本的唯一来源。SQLx 在数据库的 _sqlx_migrations 表记录版本、执行时间和 checksum。

创建 migration

sqlx migrate add <short_description>

每个 migration 只生成一个 .sql 文件。名称使用简短的英文 snake_case, SQL 必须能在空数据库上按版本顺序执行。

本地工作流

sqlx migrate info
make db-migrate

开发者提交前必须在空数据库执行 migration,并再次执行 sqlx migrate run 确认没有待执行项。

Forward-only 规则

  • 已合并或已在共享环境执行的 migration 不得修改、重命名或删除。
  • 发现历史 migration 有问题时新增前向修复 migration。
  • 不创建 .down.sql,也不执行 sqlx migrate revert。
  • 应用版本回滚不得反向修改数据库结构;新旧版本必须通过兼容窗口共同支持当前 schema。
  • 删除列、表或数据前必须在 PR 中明确数据保留和恢复方案。
  • 应用启动、Session Store 初始化和测试辅助代码都不得绕过 migration 建表。

部署顺序

备份与兼容性检查
  -> sqlx migrate info
  -> sqlx migrate run
  -> 启动新应用版本
  -> 验证 /health 和 /ready

需要零停机时,migration 必须遵循 expand-and-contract:先增加兼容结构,完成 代码迁移和数据回填后,再在后续版本删除旧结构。

HTTP API 规范

业务 API 使用 /api/v1 前缀和 JSON。健康检查是运维接口,不加版本前缀。

身份 API

POST /api/v1/auth/register

请求:

{
  "email": "user@example.com",
  "password": "correct horse 电池 staple",
  "display_name": "Shawn"
}

成功时返回 201 Created、Profile,并设置登录 Cookie:

{
  "id": "019c9122-e715-7ea3-89d4-f1f046e365f4",
  "email": "user@example.com",
  "display_name": "Shawn",
  "created_at": "2026-07-24T12:00:00Z"
}

邮箱保存前去除首尾空白并转为小写;显示名称去除首尾空白。注册成功即登录。

POST /api/v1/auth/login

请求:

{
  "email": "user@example.com",
  "password": "correct horse 电池 staple"
}

成功时返回 200 OK 和 Profile,并轮换当前 Session ID。邮箱不存在与密码错误 统一返回 401 INVALID_CREDENTIALS。

POST /api/v1/auth/logout

删除当前设备 Session、清除 Cookie,返回 204 No Content。其他设备的 Session 不受影响;未登录时重复退出仍返回 204。

GET /api/v1/profile

返回当前用户的只读 Profile。无有效 Session、Session 闲置过期或达到绝对期限 时返回 401 AUTHENTICATION_REQUIRED。

输入规则

  • 邮箱去除首尾空白后转小写,全局唯一,最长 254 个字符。
  • 显示名称去除首尾空白,长度 1–80 个 Unicode 字符,不允许控制字符。
  • 密码长度 15–128 个 Unicode 字符,允许空格、符号和 Unicode,不静默截断。
  • 不可解析的 JSON 返回 400 INVALID_REQUEST。
  • 字段校验失败返回 422 VALIDATION_ERROR,具体字段位于 error.fields。

Session Cookie 名为 rustloom_session,属性为 HttpOnly、SameSite=Lax 和 Path=/,不设置 Domain。开发环境允许 Secure=false;HTTPS 生产环境必须 设置 SESSION_COOKIE_SECURE=true。

使用 curl 保留 Cookie:

curl -i -c .cookies \
  -H 'content-type: application/json' \
  -d '{"email":"user@example.com","password":"correct horse 电池 staple","display_name":"Shawn"}' \
  http://127.0.0.1:3000/api/v1/auth/register

curl -i -b .cookies http://127.0.0.1:3000/api/v1/profile

.cookies 可能包含有效登录凭据,只能作为本地临时文件,不得提交。

运维接口

GET /health

只检查 HTTP 进程是否运行,不访问依赖:

HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8

ok

GET /ready

执行轻量 PostgreSQL 查询。成功返回 200:

{"status":"ready"}

数据库不可用时返回 503 DATABASE_UNAVAILABLE。

错误格式

{
  "error": {
    "code": "STABLE_MACHINE_CODE",
    "message": "safe public message"
  }
}
HTTPcode含义
400INVALID_REQUEST请求 JSON 无法解析
401INVALID_CREDENTIALS登录凭据无效
401AUTHENTICATION_REQUIRED缺少有效 Session
409EMAIL_ALREADY_REGISTERED邮箱已注册
422VALIDATION_ERROR输入字段不符合规则
500INTERNAL_ERROR内部错误,响应不包含底层原因
503DATABASE_UNAVAILABLE就绪检查无法查询数据库

code 是稳定、可测试的机器标识。客户端应结合 HTTP 状态处理错误,不依赖 message 文本。

安全边界

当前身份功能是安全 MVP,不代表服务已经完成公网生产加固。

密码

  • 密码只保存 Argon2id PHC 格式散列,每条凭据使用独立随机 salt。
  • 参数固定为 m=19456 KiB、t=2、p=1,符合当前项目采用的 OWASP 最低配置。
  • 散列和校验通过 Tokio blocking task 执行,不阻塞异步运行时线程。
  • 密码长度按 Unicode 字符计算,为 15–128;允许空格、符号和 Unicode。
  • 用户不存在时仍执行一次 Argon2id 运算;不存在与密码错误返回相同响应。

如果以后修改参数,登录校验必须继续兼容已有 PHC 字符串,并设计登录后升级 散列的路径。

Session

  • 浏览器只保存不透明 Session ID,数据位于 PostgreSQL。
  • Cookie 名为 rustloom_session,设置 HttpOnly、SameSite=Lax、Path=/, 不设置 Domain。
  • 登录和注册会轮换 Session ID,防止 Session fixation。
  • 每台设备使用独立 Session,退出只删除当前设备记录。
  • Session 闲置 12 小时过期;每次有效认证请求刷新活动时间。
  • 无论是否活跃,Session 创建 7 天后绝对失效。
  • 应用每小时删除一次已过期的数据库记录。
  • Session 表只能由 SQLx migration 创建,代码禁止调用 Store 的 migrate()。

本地 HTTP 开发使用 SESSION_COOKIE_SECURE=false。生产环境在 HTTPS 下必须使用:

SESSION_COOKIE_SECURE=true

数据和错误

  • .env、生产连接串、Cookie 文件和数据库密码不得提交。
  • SQL 使用绑定参数,公开 Profile 模型不含 password_hash。
  • 数据库、Session 与密码服务的底层错误只写入服务端标准错误流;客户端仅收到 稳定错误码。
  • 服务未开放 CORS,身份 API 按同源浏览器应用设计。

尚未覆盖

首版明确不包含:

  • 注册和登录限流、安全审计与异常登录检测;
  • 邮箱验证、密码找回和密码修改;
  • CSRF Token、内容安全策略与可信代理配置;
  • 角色、权限、管理员、Tenant 和第三方登录;
  • 全设备 Session 列表与一键退出。

在补齐限流、审计、邮箱验证策略、CSRF 威胁评估、TLS 和部署安全审查之前, 不得把身份 API 描述为公网生产就绪。

测试与质量门禁

本地检查

先启动 PostgreSQL 并执行 migration:

make db-migrate

然后运行完整检查:

cargo fmt --check
cargo check --all-targets
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
mdbook test docs
mdbook build docs
git diff --check

数据库测试

  • 集成测试使用 DATABASE_URL 指向专用测试数据库。
  • CI 在 PostgreSQL 18 服务上先运行 migration,再执行测试。
  • migration 必须验证空数据库首次应用和重复执行时没有待执行项。
  • 测试不得依赖个人数据库中已有的数据。

HTTP 测试

Router 由 library crate 构建,测试直接发送 HTTP request 并断言状态码、 Content-Type 和响应体。只有验证真实监听、端口冲突或外部协议时才启动服务 进程。

身份集成测试必须使用真实 PostgreSQL,并至少覆盖:

  • 注册、字段规范化、重复和并发重复邮箱;
  • Argon2id 参数及数据库中不出现明文密码;
  • 登录错误一致性和成功后的 Session ID 轮换;
  • 注册后 Profile、多设备 Session 和当前设备退出;
  • 闲置与绝对过期、未知或损坏 Cookie;
  • Cookie 的 HttpOnly、SameSite、Path 和环境化 Secure。

CI

CI workflow 对 pull request 和 main push 执行 Rust、migration 和集成测试。 Developer Guide workflow 验证 mdBook;只有 main 的成功构建可以部署 GitHub Pages。

贡献流程

开始工作

  1. 更新远端引用并从最新 origin/main 创建 codex/<description> 分支。
  2. 确认工作树没有混入其他任务的修改。
  3. 大功能先明确产品边界、非目标和验收标准。
  4. 数据库变化先设计 migration 的升级、前向修复和兼容路径。

实现要求

  • 保持 PR 单一目的,不提前实现后续阶段。
  • 只暂存本次任务的文件。
  • 修改行为时同步测试和开发手册。
  • 新增依赖必须说明用途,关闭不需要的默认 feature。
  • 不提交 .env、数据库数据目录、构建产物或文档输出。

Pull request

PR 描述至少包括:

  • 改了什么以及为什么;
  • 对开发者或调用方的影响;
  • migration、兼容窗口和前向修复方式;
  • 安全边界与明确非目标;
  • 实际运行的验证命令。

默认创建 Draft PR。只有本地检查和 GitHub CI 全部成功、PR 可合并且文档已同步 时,才能转为 Ready。