docs: design autumn registration sync
This commit is contained in:
@@ -0,0 +1,185 @@
|
|||||||
|
# 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 的 `id`、`email`、`name` 同步到 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_KEY` 和 `AUTUMN_FREE_PLAN_ID` 必须存在。
|
||||||
|
- `AUTUMN_API_URL` 可选;未配置时使用 Autumn SDK/API 默认地址。
|
||||||
|
|
||||||
|
实现阶段不得为 secret 或 plan id 设置假默认值。
|
||||||
|
|
||||||
|
## 数据映射
|
||||||
|
|
||||||
|
Better Auth user 到 Autumn customer:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
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` 扩展为 `organization` 或 `user_and_organization`。本次只做用户级 customer。
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
|
||||||
|
```text
|
||||||
|
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。
|
||||||
|
- 错误日志包含 `event`、`userId`、`status`、`code`、`message`、`autumnApiUrlConfigured`。
|
||||||
|
- 错误日志不得包含 `AUTUMN_SECRET_KEY`、Cookie、Authorization header、密码、验证码或完整请求体。
|
||||||
|
- 缺少 Autumn 必需 env 时,在同步任务中记录配置错误;测试和部署检查应尽早发现该问题。
|
||||||
|
|
||||||
|
后续补偿建议:
|
||||||
|
|
||||||
|
- 增加一个只读扫描脚本,按 `cfw-auth` 用户表重新调用 `customers.getOrCreate`。
|
||||||
|
- 或新增 outbox/队列,把失败事件持久化并重试。
|
||||||
|
|
||||||
|
这些补偿不属于本次 spec 的实现范围。
|
||||||
|
|
||||||
|
## 配置与部署
|
||||||
|
|
||||||
|
`cfw-auth` 需要配置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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`,并包含 `email`、`name`、`autoEnablePlanId`。
|
||||||
|
- Autumn 返回非 2xx 或抛出异常时不向上抛到注册 handler。
|
||||||
|
|
||||||
|
### Worker/集成测试
|
||||||
|
|
||||||
|
- mock Autumn fetch 后执行 `/api/auth/sign-up/email`,确认注册成功且触发一次 customer 同步。
|
||||||
|
- mock Autumn 500 后执行注册,确认注册响应仍成功,且错误日志不含 secret。
|
||||||
|
- 保持现有 `/healthz` 和 `/internal/session` 404 测试,防止本次集成扩大公开边界。
|
||||||
|
|
||||||
|
### 验收命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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,但接口语义仍保持 `getOrCreate` 和 `autoEnablePlanId`。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
采用 Better Auth hook + Worker background task + Autumn `customers.getOrCreate` 的方案。注册成功后的 Autumn 同步是异步副作用,不改变 `cfw-auth` 的公开 API 边界,不把套餐和试用规则复制到认证服务。
|
||||||
Reference in New Issue
Block a user