# 找回与设置密码

短信发送须先完成[滑块验证](CAPTCHA.md)，并在原发送请求中携带一次性 `captcha_token`；发布前同步升级相关客户端。

官网和原生桌面都可以不跳页完成密码管理。此权限默认关闭；管理员在 OIDC 客户端编辑页开启“直接找回 / 设置密码”。Web 同时要求自定义网页登录开关和 Origin；Native 同时要求原生登录开关。**密码事务与登录事务不能混用**，不能用短信改密证明领取登录授权码。

## 选择操作

| operation | 身份条件 | 手机号 | 成功结果 |
|---|---|---|---|
| reset | 无需已有用户登录；Web 后端仍 Basic | 用户输入 +86 E.164 | 不注册、不激活 pending_activation；仅更改 active 账号密码 |
| set | 有效的该 Client 用户 Access Token | 从 Token 对应账号确定；不能换成其他手机号 | 设置/更改该账号密码 |

两者都须本事务重新验证短信。验证前不根据账号存在与否改变发送结果；未注册手机号也走同一渠道和限流。短信验证后账号不可用返回统一失败。接口不能用于枚举完整手机号。

## 创建

Web：`POST /api/v1/web-auth/password-transactions`，后端 Web Basic，无 Origin、Cookie 或查询参数。

```json
{"client_id":"wyc-web","operation":"reset","browser_origin":"https://www.example.com"}
```

设置密码在请求体额外加入 `user_access_token`，由产品后端从当前业务会话读取。浏览器不能提交任意用户 Token 让后端代用。

Native：`POST /api/v1/native-auth/password-transactions`，JSON `{"client_id":"wyc-desktop","operation":"reset"}`。设置时 operation 为 set，头为 `Authorization: Bearer <该Client有效用户AccessToken>`，不在 JSON 传 Token。不接受浏览器头或 Cookie。

响应 HTTP 201：

```json
{"data":{"transaction_id":"ef1ff5ec-ecc3-449b-bd5c-951d0c349d67","transaction_token":"oVFXTKbI1uygV9ytwf3vUFt9iEEWFtebUUvmcmTXXDk","status":"waiting","expires_in":300,"expires_at":"2026-09-03T09:05:00Z","phone_masked":"+86****8000","operation":"set"}}
```

字段 client_id 最长 128；operation 只能 set/reset；browser_origin 是 Web 必填的登记来源；user_access_token 仅 Web set，最长 8192。后续请求只带事务凭据，不再发送用户 Access Token。reset 返回的 phone_masked 不包含用户信息。

## 发送、验证、完成、取消

下文 base 为创建 URL 加 `/{transaction_id}`。Web 浏览器使用 `Authorization: WebAuth <transaction_token>`、Origin 和 `credentials:omit`；Native 使用 `Authorization: Native <transaction_token>`。POST 一律 JSON；不接受未知字段、查询参数、Cookie。

| 方法 / URL | reset 请求体 | set 请求体 | 成功 |
|---|---|---|---|
| GET base | 无 | 无 | 200 data.status / expires_at / phone_masked；两秒串行轮询 |
| POST base/sms/send | `{"phone_e164":"+8613800138000"}` | `{}` | 201 accepted 或 202 unknown，retry_after=60 |
| POST base/sms/verify | `{"phone_e164":"+8613800138000","code":"123456"}` | `{"code":"123456"}` | 200 `{"data":{"status":"ready"}}` |
| POST base/complete | `{"new_password":"NewStrongPassword123"}` | 同左 | 200 `{"data":{"status":"completed","relogin_required":true}}` |
| POST base/cancel | `{}` | `{}` | 200 `{"data":{"status":"cancelled"}}` |

set 可省略手机号；如提交必须等于账号当前手机号。短信挑战绑定事务 UUID，不能用于另一事务、登录或其他用途。new_password 使用统一密码策略：10–128 字节，含英文字母与数字；Argon2id 存储，不记录原文。示例密码只是格式示例，真实用户须自行设置。

密码提交必须 ready。密码更新、中央会话撤销、Access/Refresh Token 撤销、未完成认证事务取消及 `user.sessions_revoked` Outbox 在同一数据库事务提交。回滚则都不生效。完成前再次验证账号状态、手机号、认证版本；set 再次验证源 Access Token 未过期或撤销。

成功后不自动登录，不返回授权码或新 Token。产品后端处理签名事件撤销自己的业务会话；官网示例也退出当前产品会话。只撤销中央状态不能保证尚未消费事件的产品业务会话立即退出。

## 错误与恢复

外层错误统一为 `{"error":{"code":"invalid_user_token","message":"用户 Token 无效、已过期或已撤销","request_id":"诊断编号"}}`。

除 [自定义 Web 错误表](CUSTOM_WEB.md) 外：401 invalid_user_token 表示需重新登录；422 password_policy 表示新密码不合策略；422 invalid_phone 表示 set 试图验证其他手机号；422 invalid_code 为验证码/账号不可用；409 transaction_unavailable 表示事务结束、过期、撤销或并发修改。Redis/数据库错误返回 503 storage_unavailable，不当作验证码错误。

Native 创建每 IP 30 次/5 分钟；Web 创建使用每 Client 600 次/分钟及后端 IP 1,200 次/分钟，与登录事务共用。完成每事务最多 5 次/5 分钟；短信共享全局限流。POST 没有自动重试。密码提交网络超时后结果未知：重新登录验证新密码，必要时重新发起找回；不得复用旧证明自动改第二次。忘记密码不会让账号解除管理员禁用。

## Go / Rust / Tauri

使用标准 HTTPS 客户端；事务 Token 放请求头，禁止 JS WebView 调 Native API。Tauri 前端调用 Rust command，由 Rust reqwest 直接请求平台；对 command 做窗口/能力白名单，不开放任意 URL 代理。新密码只在调用内存中存在；返回前清空 UI 输入。不要把业务 Service Secret 打进安装包。

Go 可使用 `http.NewRequestWithContext`，POST 内容 `json.Marshal`，超时 12 秒。Rust：

```rust
// HTTP 在 Rust 层，不是 WebView fetch；client 禁止自动重定向。
let client = reqwest::Client::builder()
    .timeout(std::time::Duration::from_secs(12))
    .redirect(reqwest::redirect::Policy::none()).build()?;
let response = client.post(format!("{issuer}/api/v1/native-auth/password-transactions/{id}/complete"))
    .header("Authorization", format!("Native {transaction_token}"))
    .json(&serde_json::json!({"new_password": new_password}))
    .send().await?;
// 检查 HTTP 状态及 error.code；不使用 unwrap，不把 body/Token 写日志。
```

仓库及发布包的 `examples/native-password/client.go` 提供完整 Go 适配器，包含创建、发送、验证、完成、取消、超时和安全错误；`password.rs` 提供 Rust/Tauri 适配示例（需在产品 Cargo 工程集成验证）。官网可运行样例位于 `examples/custom-web`，包括同样的密码流程。原生登录的验签、刷新和系统凭据保存见 [Native](NATIVE.md)。设计参考 [OWASP Forgot Password](https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html)。
