账号找回
「忘记密码」入口走 Kratos recovery flow。整个过程是一个发码 → 验证 → 改密的三段式:
- 创建 recovery flow
- Kratos 把一次性恢复码发到已注册的邮箱 / 手机
- 用户提交恢复码,Kratos 把当前会话切换为「recovery 验证态」(privileged session)
- 在验证态下立即调用
createSettingFlow().password()改密
@tripo3d/auth 把前 3 步收敛成 createRecoveryFlow() 工厂下的 send / verify 两个方法,验证成功后会自动 initTokenized(),业务侧不用手动管 session。
三步流程
Step 1 · createRecoveryFlow()POST /self-service/recovery/browser,拿到带 csrf_token 的 flow 上下文Step 2 · recoveryFlow.send({ email | phone })Kratos 校验 identifier 已注册,然后下发一次性恢复码到邮箱/短信Step 3 · recoveryFlow.verify(code)提交恢复码;成功后 Kratos 写入 session cookie,SDK 自动 initTokenized() 进入「recovery 验证态」Step 4 · createSettingFlow().password(newPassword)在验证态下改密;此态下 Kratos 通常只允许改密,不允许改其他 traits
完整代码示例
把上述 3 步串起来一个最小可运行的 demo:
ts
import { TripoAuth } from '@tripo3d/auth';
const auth = new TripoAuth({ basePath: 'https://auth.tripo3d.ai' });
async function recoverPassword(email: string, newPassword: string) {
// 1. 创建 recovery flow
const recoveryFlow = await auth.createRecoveryFlow();
// 2. 让 Kratos 发码到注册邮箱
await recoveryFlow.send({ email });
// ↑ 这里通常会让 UI 进入「请查收邮件」状态,弹出输入框等用户输入
const code = await promptUserForCode(); // 由 UI 拿到用户输入
// 3. 提交恢复码;成功后 SDK 自动 initTokenized(),进入「recovery 验证态」
await recoveryFlow.verify(code);
// 4. 立即改密;此时 tokenized() 已经能拿到 session
const settingFlow = await auth.createSettingFlow();
await settingFlow.password(newPassword);
// 流程结束。建议引导用户重新走一次正常登录(以拿到完整权限的 session)
}手机号同理,把 { email } 换成 { phone: '+8613800138000' }即可:
ts
await recoveryFlow.send({ phone: '+8613800138000' });
await recoveryFlow.verify('123456');与登录态的区别
verify(code) 成功后拿到的 session 不等同于「正常登录」。Kratos 把它标为 privileged-but-restricted——仅允许执行密码重置这一件事。试图调用其他 settings 方法(改邮箱、绑定 OIDC 等)通常会被拒。
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
改密 (createSettingFlow.password) | 允许 | — | recovery 验证态的核心用途,Kratos 会接受并完成改密 |
改 traits (邮箱 / 手机) | 通常拒绝 | — | Kratos 会要求 privileged session,recovery 态不满足,返回 Response 错误 |
调用业务 API (tokenized) | 可以拿 token | — | 但后端可能对 recovery-state 的 token 加额外限制,具体看业务 IDP 策略 |
改密后是否还需重新登录 | 建议是 | — | Kratos 改密后会让旧 session 失效,SDK 内存里的 token 也应当作过期处理;引导用户走一次正常登录 |
API 参考
| 方法 | 签名 | 说明 |
|---|---|---|
createRecoveryFlow | () => Promise<RecoveryFlowHandle> | 创建 recovery flow,返回的对象持有 flowId 与 csrf_token |
recoveryFlow.send | (params: { email } | { phone }, transient_payload?) => Promise<void> | 让 Kratos 把恢复码下发到 identifier;identifier 必须已注册,否则 Flow 错误 |
recoveryFlow.verify | (code: string, transient_payload?) => Promise<void> | 提交恢复码;成功后 SDK 自动 initTokenized();失败抛 TripoAuthError |
params 字段
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
email | string | — | 已注册邮箱,与 phone 二选一 |
phone | string | — | 已注册的 E.164 手机号,与 email 二选一 |
code | string | — | 用户收到的恢复码;仅 verify() 需要,作为第一参数直接传字符串 |
transient_payload | Record<string, any> | — | 可选,转发到 webhook;send 与 verify 互不继承,需要的话两次都传 |
常见错误
identifier 未注册
send() 时如果邮箱 / 手机不存在,Kratos 通常不会直接报错(出于安全考虑——不暴露账号是否存在),而是返回成功响应,但实际不发码。前端 UX 通常是统一显示「若该邮箱已注册,你将收到一封邮件」。
如果 IDP 策略选择直接报错,SDK 会以 Flow 名抛出:
ts
try {
await recoveryFlow.send({ email: 'noone@nowhere' });
}
catch (error) {
const e = error as Error;
// 视后端策略,可能是「Could not find an account...」之类
console.warn(e.message);
}邮件未收到
最常见的非技术问题。前端能做的:
- 在
send()后展示倒计时,允许用户 60s 后重发 - 重发就是再调一次
recoveryFlow.send(),复用同一个 flow 实例(handleFlow 已就地更新 csrf_token)
ts
async function resend() {
await recoveryFlow.send({ email });
startCountdown(60);
}恢复码错误 / 多次失败
Kratos 默认对单个 flow 限制尝试次数(超出后整个 flow 作废)。错码会以 Flow 抛出:
ts
try {
await recoveryFlow.verify(userInput);
}
catch (error) {
const e = error as Error;
if (e.name === 'Flow') {
// 「The recovery code is invalid or has already been used」
// 或者「The flow expired, please retry」
if (e.message.includes('expired') || e.message.includes('invalid')) {
// 整个 flow 已不可用,需要 createRecoveryFlow() 重建并重发码
const newFlow = await auth.createRecoveryFlow();
await newFlow.send({ email });
}
else {
// 仅本次输错,直接重试
showToast('恢复码错误,请重新输入');
}
}
}改密阶段失败
recoveryFlow.verify 成功后立刻调 createSettingFlow().password(),中间不要插入耗时操作——recovery 验证态是短期的,Kratos 通常给几分钟窗口:
ts
await recoveryFlow.verify(code);
// ⚠️ 不要在这里 await 一个长时间的网络请求
// 直接进入改密
const settingFlow = await auth.createSettingFlow();
await settingFlow.password(newPassword);如果窗口已经关闭,改密会以 Response 错误抛出 privileged_session_required,这时只能让用户从头来一遍。