# 星线账户接口接入

账户网页接口使用同站 Cookie、精确来源和 CSRF 校验。它们适用于账户前端；第三方业务登录应使用[开放平台](https://document.thestarline.cn/open-platform/)的 OIDC，不要求用户向第三方提交账户密码。资料依据 2026 年 10 月 23 日 `identity/server/` 源码整理，介绍请求顺序、字段与失败边界。

## 建立请求上下文

首先调用 `GET /api/csrf`，保留服务端下发的 CSRF Cookie，并读取返回的 `csrfToken`。后续浏览器写请求同时携带 Cookie、正确 `Origin` 和 `x-csrf-token`。CSRF Cookie 使用 Strict SameSite，期限 3,600 秒；刷新页面后可以重新取得值。请求被来源检查拒绝时，反复更换验证码不会解决问题。

```js
// 在账户站同源页面执行；captchaProof 来自当前 CAPTCHA 验证。
const { csrfToken } = await fetch('/api/csrf', {
  credentials: 'same-origin'
}).then(r => r.json());
const response = await fetch('/api/auth/email-code', {
  method: 'POST', credentials: 'same-origin',
  headers: { 'content-type': 'application/json', 'x-csrf-token': csrfToken },
  body: JSON.stringify({ identifier: 'reader@example.invalid', captcha: captchaProof })
});
const result = await response.json();
```

示例邮箱是合成数据。CAPTCHA 结构以账户前端及 `identity/server/captcha.ts` 为准，不用占位字符串模拟通过。服务端校验 Host 是否属于配置来源；生产要求 HTTPS。普通写请求的正文必须是 JSON 对象。OIDC 协议和签名开放 API 有专门认证机制，小程序只有 inspect、decide 使用单次证明的例外，不应把这些例外扩散到其他路由。

## 注册顺序

| 方法与路径 | 主要输入 | 后续动作 |
| --- | --- | --- |
| `POST /api/register/start` | `email`、`captcha` | 保存 `transactionId`，等待邮箱验证码 |
| `POST /api/register/email/send` | `transactionId`、`captcha` | 仅用于重发，不延长客户端自己计算的期限 |
| `POST /api/register/email/verify` | `transactionId`、`code` | 继续手机号和密码步骤 |
| `POST /api/register/phone/send` | `transactionId`、`phone`、`captcha` | 向事务中的手机号发码 |
| `POST /api/register/phone/verify` | `transactionId`、`code` | 验证当前注册事务 |
| `POST /api/register/password` | `transactionId`、`password` | 设置符合策略的密码 |
| `POST /api/register/complete` | `transactionId` | 以服务端返回确定注册完成 |
| `POST /api/register/phone/start` | `phone`、`captcha` | 开始独立手机号注册流程 |

邮箱注册需要完成邮箱、手机号、密码三项条件。独立手机号注册使用 phone/start 后的事务进行 phone/verify，再 complete；不要额外调用邮箱流程制造第二个账户。注册事务受浏览器上下文约束，不能从另一设备领取。流程未完成返回 `REGISTRATION_INCOMPLETE`，事务失效返回 `INVALID_TRANSACTION`；应按服务端状态回退或重新开始。

密码按 Unicode 码点计 12–128 个字符，同时 UTF-8 编码不超过 512 字节；服务端使用 Argon2id。邮箱会清理并转为小写，手机号按服务端支持范围规范化。客户端可以提前提示格式，但不能跳过服务端结果或把 HTTP 200 的发送受理视为联系方式验证。

## 登录、风险与会话

`POST /api/auth/login` 接受 `method`。密码方式提交 `method:"password"`、`identifier`、`password`；验证码方式提交 `method:"email_code"` 或 `"phone_code"`，以及 `identifier`、`transactionId`、`code`。对应发送入口为 `/api/auth/email-code`、`/api/auth/phone-code`，均需要 CAPTCHA。手机发码还可带服务端给出的关联 `transactionId`。

服务端可能要求追加风险验证，继续调用 `POST /api/auth/risk/send`（`transactionId`、`captcha`）和 `/api/auth/risk/verify`（`transactionId`、`code`）。客户端应保留原事务，不在追加验证过程中重新创建并行登录。未知账户、冷却及部分不可投递状态使用统一发送响应，以减少账户枚举；实际投递基础设施完全不可用时仍可返回 503。

成功后使用 `GET /api/auth/me` 确认身份。返回包含 `user`、当前 `session` 的 `id`、`method`、`createdAt`、`expiresAt`，以及管理与认证能力标志。Cookie 由服务端设置，响应不会把原始会话令牌作为网页 JSON 字段公开。`POST /api/auth/logout` 退出当前 Account 会话；其他产品独立存储的会话需要各自处理。

会话响应中的时间是服务端记录，客户端应按到期时间显示状态，并在受保护操作返回未认证时重新验证。仅隐藏页面、删除某个前端变量或关闭浏览器标签不等于完成服务端会话撤销；网络异常时应保留明确的未确认结果。

## 安全设置、资料与授权

| 操作 | 路由与关键字段 | 约束 |
| --- | --- | --- |
| 查看安全概况 | `GET /api/security` | 返回本人会话与安全事件等 |
| 重新验证 | `POST /api/security/reauth` | 根据 `method` 提交证明，可能继续短信验证；成功才取得 `grantId` |
| 改密码 | `POST /api/security/password`，`grantId,password` | 短时授权不可重复消费 |
| 联系方式变更 | `POST /api/security/contact/send`，`grantId,kind,target,captcha`；随后 `/verify` 提交 `transactionId,code` | kind 仅邮箱或手机号 |
| 撤销会话 | `POST /api/security/sessions/revoke`，`grantId,sessionId`，或 `grantId,target:"others"` | 两个目标字段不能同时使用 |
| 资料更新 | `POST /api/profile`，`displayName` 及可选 `avatar` | 正文字节上限 400,000；不允许额外字段 |
| 资料头像 | `GET /api/profile/avatar` | 当前账户私有，禁止按他人 ID 构造公开地址 |

`avatar` 为 `{mime,data}`，data 是标准 Base64；`avatar:null` 表示删除。更新资料时 `displayName` 应明确传递名称或 null，不能依靠省略字段模拟部分 PATCH。通行密钥的 options/verify 接口位于 `identity/server/protocols.ts`，使用 WebAuthn 原生响应与一次性挑战；不要自行拼装“认证成功”对象。实名认证申请的开关、材料和审核能力与普通资料更新独立。

## 错误、重试与观测

错误格式为 `{error:{code,message},requestId}`。日志可以记录 requestId、路由、时间和错误码，不能记录验证码、Cookie、密码、外部身份 code 或材料正文。

| 状态与代码 | 典型触发 | 处理方式 |
| --- | --- | --- |
| 400 `INVALID_INPUT` / `INVALID_CODE` | 字段无效，验证码错误、过期或已使用 | 修正输入；不要无限重试同一码 |
| 400 `INVALID_TRANSACTION` | 事务到期、上下文或凭据改变、单次证明已用 | 重新开始相应验证 |
| 401 `UNAUTHENTICATED` / `INVALID_CREDENTIALS` | 会话或登录证明无效 | 清理页面认证状态，重新登录 |
| 403 `CSRF_FAILED` / `FORBIDDEN` | Cookie 与头不匹配、来源或权限不符 | 先修复上下文或权限 |
| 403 `RISK_BLOCKED` / `ACCOUNT_BANNED` | 频率风险或账户限制 | 遵循提示，不切换标识规避 |
| 409 `CONFLICT` | 唯一数据冲突 | 刷新当前状态后处理 |
| 503 `DELIVERY_UNAVAILABLE` / `SERVICE_UNAVAILABLE` | 投递未配置或依赖异常 | 有界退避；明确保留“结果未确认” |

限额有全局、来源、目标与路由不同桶，默认窗口 600 秒、基础额度 20；不能把该值解释为全站所有用户合计每十分钟 20 次。发送冷却与接口限额同样存在。发生写请求超时时，先读取当前事务或安全设置核实，不自动重复提交注销、改密等一次性操作。

联调应分别覆盖完整注册、错误验证码与重放、风险追加验证、CSRF、跨浏览器事务、封禁、会话到期、改密后的授权变化、头像边界和投递失败。源码测试、构建成功只能验证其覆盖范围；生产配置、真实投递和第三方平台回调需要单独回执。服务组成与存储要求见[技术概览](https://document.thestarline.cn/account/overview.md)。
