Skip to content

账号找回

「忘记密码」入口走 Kratos recovery flow。整个过程是一个发码 → 验证 → 改密的三段式:

  1. 创建 recovery flow
  2. Kratos 把一次性恢复码发到已注册的邮箱 / 手机
  3. 用户提交恢复码,Kratos 把当前会话切换为「recovery 验证态」(privileged session)
  4. 在验证态下立即调用 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 字段

名称 类型 默认值 说明
emailstring已注册邮箱,与 phone 二选一
phonestring已注册的 E.164 手机号,与 email 二选一
codestring用户收到的恢复码;仅 verify() 需要,作为第一参数直接传字符串
transient_payloadRecord<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,这时只能让用户从头来一遍。

下一步

基于 MIT 协议发布(内部使用)