Files
cfw-auth/docs/superpowers/specs/2026-07-02-cfw-auth-autumn-registration-sync-design.md

7.5 KiB
Raw Blame History

cfw-auth 注册同步 Autumn 设计

背景

cfw-auth 当前只暴露 Better Auth 的 /api/auth/* 认证面。注册、登录、账号中心、OpenAPI reference 都经由 Better Auth handler 处理Hono 只负责 CORS、Worker waitUntil 背景任务承接和请求耗时日志。

本次目标是在用户注册成功后,把用户身份同步到 coding/autumn,并让 Autumn 为该用户自动创建 customer、分配免费套餐、开启套餐配置内的试用并持续同步基础资料。认证系统不拥有套餐语义Autumn 仍是计费、权益、试用和套餐规则的唯一 owner。

目标

  • 邮箱注册成功后自动创建或获取 Autumn customer。
  • 注册时把 Better Auth user 的 idemailname 同步到 Autumn。
  • 通过 Autumn 的 autoEnablePlanId 启用免费套餐。
  • 试用由 Autumn plan/product 配置决定,cfw-auth 不硬编码试用时长、是否需要卡、价格或权益。
  • Autumn 同步失败不阻塞用户注册。
  • 保持 cfw-auth 的公开 HTTP 边界仍为 /api/auth/*,不新增自定义注册 wrapper。

非目标

  • 不在 cfw-auth 实现套餐、价格、权益、usage tracking 或 checkout。
  • 不新增独立队列、outbox 表或补偿后台服务。补偿机制可作为后续阶段。
  • 不改变 Better Auth schema除非实现阶段发现官方 hook 需要最小字段支持。
  • 不修改 coding/autumn 的产品配置;免费套餐和试用配置应在 Autumn 侧准备好。
  • 不把 Autumn customer 状态写回 cfw-auth 用户表作为注册成功条件。

推荐方案

使用 Better Auth 的 databaseHooks.user.create.after 作为注册成功后的同步触发点,并通过当前 Worker runtime 传入的 waitUntil 执行后台任务:

  1. Better Auth 成功创建 user。
  2. user.create.after 收到创建后的 user。
  3. hook 构造 Autumn customer payload。
  4. 通过 runtime.waitUntil 调用 Autumn customers.getOrCreate
  5. customers.getOrCreate 使用 user id 作为稳定 customer id并传入 autoEnablePlanId
  6. Autumn 返回成功后不改变 Better Auth 响应;失败只记录结构化错误。

这样可以保持认证可用性优先Autumn 或网络短暂故障不会导致用户无法注册。

组件

src/auth.ts

继续负责 Better Auth 实例构造。新增的职责只有把注册后 hook 接到 Autumn 同步函数上。

设计要求:

  • hook 只处理 user create 后的副作用,不拦截注册输入。
  • hook 内不直接展开复杂 fetch 逻辑,而是调用独立模块。
  • 如果 runtime.waitUntil 存在,使用后台任务;如果没有,则可以直接 await 同步函数,便于测试和非 Worker 场景。

src/autumn.ts

新增 Autumn 同步边界模块。

职责:

  • 解析 Autumn 相关 env。
  • 判断同步是否启用。
  • 将 Better Auth user 映射为 Autumn customer payload。
  • 调用 Autumn API。
  • 规范化错误日志所需的可公开字段。

该模块不应依赖 Hono context、Better Auth handler 或数据库 adapter。它只接收 env 和 user-like 对象,便于单元测试。

src/env.ts

新增显式环境变量类型:

  • AUTUMN_SECRET_KEY?: string
  • AUTUMN_API_URL?: string
  • AUTUMN_FREE_PLAN_ID?: string
  • AUTUMN_REGISTER_SYNC_ENABLED?: string

同步启用规则:

  • AUTUMN_REGISTER_SYNC_ENABLED=false 时跳过同步。
  • 启用同步时,AUTUMN_SECRET_KEYAUTUMN_FREE_PLAN_ID 必须存在。
  • AUTUMN_API_URL 可选;未配置时使用 Autumn SDK/API 默认地址。

实现阶段不得为 secret 或 plan id 设置假默认值。

数据映射

Better Auth user 到 Autumn customer

{
  customerId: user.id,
  email: user.email,
  name: user.name,
  autoEnablePlanId: env.AUTUMN_FREE_PLAN_ID,
  metadata: {
    source: "cfw-auth",
    authProvider: "better-auth"
  }
}

customerId 使用 Better Auth user id避免邮箱变更导致 Autumn 侧身份漂移。邮箱和姓名作为可更新资料同步给 Autumn。

如果后续需要支持组织计费,应另起 spec把 customer scope 从 user 扩展为 organizationuser_and_organization。本次只做用户级 customer。

数据流

POST /api/auth/sign-up/email
  -> Hono CORS
  -> createAuth(env, runtime)
  -> Better Auth handler
  -> Better Auth 创建 user
  -> databaseHooks.user.create.after
  -> syncAutumnCustomerOnRegistration(env, user)
  -> Autumn customers.getOrCreate
       customerId = Better Auth user id
       autoEnablePlanId = configured free plan id
  -> Better Auth 原注册响应返回给客户端

试用启用不由 cfw-auth 直接调用单独 API。Autumn 在 customer 创建并自动启用 plan 时,根据 plan/product 内的 free trial 配置处理试用。

错误处理

Autumn 同步失败必须 fail-open

  • Better Auth 注册响应不因 Autumn 失败而变为 500。
  • 错误日志包含 eventuserIdstatuscodemessageautumnApiUrlConfigured
  • 错误日志不得包含 AUTUMN_SECRET_KEY、Cookie、Authorization header、密码、验证码或完整请求体。
  • 缺少 Autumn 必需 env 时,在同步任务中记录配置错误;测试和部署检查应尽早发现该问题。

后续补偿建议:

  • 增加一个只读扫描脚本,按 cfw-auth 用户表重新调用 customers.getOrCreate
  • 或新增 outbox/队列,把失败事件持久化并重试。

这些补偿不属于本次 spec 的实现范围。

配置与部署

cfw-auth 需要配置:

AUTUMN_SECRET_KEY=<Autumn server secret>
AUTUMN_FREE_PLAN_ID=free
AUTUMN_API_URL=<optional local or production Autumn API URL>
AUTUMN_REGISTER_SYNC_ENABLED=true

生产环境不允许依赖开发默认值。实现阶段应更新 TODO.md 或运维文档,列出这些变量的用途和是否必需。

测试

单元测试

  • env 缺少 AUTUMN_SECRET_KEY 时同步函数返回配置错误或记录错误。
  • env 缺少 AUTUMN_FREE_PLAN_ID 时同步函数不发请求并记录错误。
  • AUTUMN_REGISTER_SYNC_ENABLED=false 时不发请求。
  • payload 使用 user.id 作为 customerId,并包含 emailnameautoEnablePlanId
  • Autumn 返回非 2xx 或抛出异常时不向上抛到注册 handler。

Worker/集成测试

  • mock Autumn fetch 后执行 /api/auth/sign-up/email,确认注册成功且触发一次 customer 同步。
  • mock Autumn 500 后执行注册,确认注册响应仍成功,且错误日志不含 secret。
  • 保持现有 /healthz/internal/session 404 测试,防止本次集成扩大公开边界。

验收命令

pnpm typecheck
pnpm test
pnpm ready

如果实现阶段引入 autumn-js 依赖,还应确认 Worker bundle 能通过 Wrangler 本地启动或 dry-run 检查。

风险与取舍

  • fail-open 会产生短暂的 Auth 已注册但 Autumn 未同步状态。当前阶段接受这个取舍,因为认证可用性优先。
  • 不持久化失败事件意味着自动重试能力有限。后续如果注册量或计费一致性要求提高,应补 outbox/队列。
  • 使用 user id 作为 customer id 会让用户级计费简单稳定,但不覆盖组织级 billing。组织级 billing 需要独立设计。
  • 如果直接引入 autumn-js 增加 Worker bundle 或兼容风险,实现阶段可改用最小 fetch 调用 Autumn HTTP API但接口语义仍保持 getOrCreateautoEnablePlanId

决策

采用 Better Auth hook + Worker background task + Autumn customers.getOrCreate 的方案。注册成功后的 Autumn 同步是异步副作用,不改变 cfw-auth 的公开 API 边界,不把套餐和试用规则复制到认证服务。