密码流程
@tripo3d/auth 把"密码"作为一种 method 复用在三条 flow 上:登录、注册、改密。三者共用同一组 password() 子方法,差别仅在调用前用哪个工厂创建 flow:
createLoginFlow().password()— 已存在用户登录createSignupFlow().password()— 新用户注册并自动登录createSettingFlow().password()— 已登录态(或 recovery 验证态)修改密码
底层都是 Kratos POST /self-service/{login|registration|settings}/flows/{flowId} ,SDK 替你处理 csrf_token、错误归一化与 JWT 缓存。
登录(已存在用户)
最常见的入口。需要先 createLoginFlow() 拿到一个 flow 上下文,再调 password({ email | phone, password }):
import { TripoAuth } from '@tripo3d/auth';
const auth = new TripoAuth({ basePath: 'https://auth.tripo3d.ai' });
try {
const loginFlow = await auth.createLoginFlow();
await loginFlow.password({
email: 'a@b.c',
password: '••••••',
});
// 此时 SDK 已自动调用 initTokenized(),JWT 已写入内存缓存
const token = await auth.tokenized();
}
catch (error) {
const e = error as Error;
if (e.name === 'Flow') {
// 表单错误:identifier 不存在 / 密码错误 / 账号锁定等
console.warn('登录失败:', e.message);
}
}identifier 由调用方显式给出(email 或 phone),SDK 不做隐式判断。手机号同理:
await loginFlow.password({ phone: '+8613800138000', password: '••••••' });请求形态
调用 loginFlow.password() 后,实际发出的请求大致如下:
POST /self-service/login/flows?flow={flowId}
Content-Type: application/json
Cookie: ory_kratos_session=...
{
"csrf_token": "...",
"method": "password",
"identifier": "a@b.c",
"password": "••••••",
"transient_payload": null
}成功响应携带 session ,Cookie 由浏览器自动写入,SDK 紧接着调 toSession({ tokenizeAs: 'default_jwt' }) 拿 JWT。
错误形态
handleFlow 会把响应里的错误归一化成 TripoAuthError,带 name 区分来源:
| error.name | 来自 | 说明 |
|---|---|---|
Flow | response.ui.messages 或 ui.nodes[].messages 中 type=error 的第一条 | 密码错误、账号被锁、identifier 不存在(后端策略)等表单语义错误 |
Response | response.error.message | Kratos HTTP 层错误(如 410 flow 过期、429 频控) |
Unknown | 请求异常或非 JSON 响应 | 网络断、CORS 失败等 |
flow 状态会在响应回来时就地更新——同一个 loginFlow 对象可以直接重试:
try {
await loginFlow.password({ email, password: wrong });
}
catch {
// flowId 与 csrf_token 已经被替换为响应里的最新值,直接再调即可
await loginFlow.password({ email, password: correct });
}注册(新用户)
createSignupFlow().password() 的签名与登录一致,但语义不同:Kratos 会创建一个新的 identity 并自动建立 session,所以同样会触发 initTokenized()。
const signupFlow = await auth.createSignupFlow();
await signupFlow.password({
email: 'newuser@b.c',
password: '••••••',
});
// 注册即登录,token 已可用
const token = await auth.tokenized();与登录的差异
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
identity | 新建 / 已存在 | — | 注册要求 identifier 在租户内不存在,否则 Flow 错误返回「该邮箱已注册」 |
traits | 注册写入 | — | 注册请求体里携带 traits: { email } 或 { phone },直接写入 identity 的 traits |
session | 自动建立 | — | 注册成功 = 登录成功,无需额外调 createLoginFlow |
手机号注册的特殊性
虽然 signupFlow.password() 的 TS 签名允许 { phone, password },但实际能否成功取决于 Kratos schema:Tripo 后端手机号必须先发码验证才能完成注册,直接走 password 通常会被拒。手机号注册推荐使用验证码流程:
// 推荐写法:phone 走 code,而不是 password
const signupFlow = await auth.createSignupFlow();
await signupFlow.send({ phone: '+8613800138000' });
await signupFlow.code({ phone: '+8613800138000', code: '123456' });改密(已登录态)
修改密码走 settings flow,前置条件是已存在 session。两种典型来源:
- 用户已登录(
tokenized()能拿到 token) - 通过
createRecoveryFlow().verify(code)拿到的 recovery 验证态(详见 账号找回)
// 1. 确保有 session
const token = await auth.tokenized();
if (!token) throw new Error('未登录,请先登录');
// 2. 创建 settings flow 并提交新密码
const settingFlow = await auth.createSettingFlow();
await settingFlow.password('NewP@ssw0rd2026');createSettingFlow() 当前只暴露 password 子方法,改邮箱/手机号需走 verification 流程或后端单独 API。
Recovery 联动
recovery flow verify(code) 成功后,SDK 会自动 initTokenized(),这时虽然没有「常规登录」,但 Ory 会发放一个有限制的 session(privileged session),允许调用 createSettingFlow().password() 改密:
const recoveryFlow = await auth.createRecoveryFlow();
await recoveryFlow.send({ email: 'forgot@b.c' });
await recoveryFlow.verify('123456');
// ↑ 进入 recovery 验证态
const settingFlow = await auth.createSettingFlow();
await settingFlow.password('NewP@ssw0rd');
// ↑ Kratos 接受改密,但通常拒绝改 traits完整流程见 账号找回。
API 参考
| 方法 | 签名 | 说明 |
|---|---|---|
createLoginFlow | (returnTo?: string) => Promise<LoginFlowHandle> | 创建登录 flow,可选传入 returnTo 用于 OIDC 跳回 |
createSignupFlow | () => Promise<SignupFlowHandle> | 创建注册 flow |
createSettingFlow | () => Promise<SettingFlowHandle> | 创建改密 / 设置 flow,需要已存在 session |
loginFlow.password | (params, transient_payload?) => Promise<void> | params 为 { email, password } 或 { phone, password };成功后自动 initTokenized() |
signupFlow.password | (params, transient_payload?) => Promise<void> | 同上,但创建新 identity;成功后自动登录 |
settingFlow.password | (password: string, transient_payload?) => Promise<void> | 直接传新密码字符串;无返回值,失败抛错 |
params 详细字段
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
email | string | — | 邮箱地址,与 phone 二选一 |
phone | string | — | E.164 格式手机号(如「+8613800138000」),与 email 二选一 |
password必填 | string | — | 明文密码,Kratos 后端校验强度 |
transient_payload | Record<string, any> | — | 可选,转发到 Kratos webhook,常用于埋点 / 来源记录;不写入 traits |
常见错误
弱密码
Kratos 默认开启 haveibeenpwned + 长度校验,弱密码会以 name='Flow' 抛出:
try {
await signupFlow.password({ email, password: '123456' });
}
catch (error) {
const e = error as Error;
// e.message 形如「The password must be longer than 8 characters」
// 或「This password has been found in data breaches and must not be used」
showToast(e.message);
}identifier 已存在(注册场景)
注册时如果邮箱已注册,Kratos 会返回 Flow 错误。建议在调 password() 前先用 checkIdentifier() 判断:
const { flowType } = await auth.checkIdentifier('a@b.c');
if (flowType === 'login') {
// 已注册,引导到登录页
}
else {
const signupFlow = await auth.createSignupFlow();
await signupFlow.password({ email: 'a@b.c', password: '••••••' });
}Session 过期(改密场景)
settings flow 要求 session 处于 privileged 状态(通常需要近期登录过)。过期时 Kratos 会要求重新认证,SDK 抛出 Response 错误:
try {
const settingFlow = await auth.createSettingFlow();
await settingFlow.password('NewP@ssw0rd');
}
catch (error) {
const e = error as Error;
if (e.name === 'Response' && e.message.includes('privileged')) {
// 引导用户重新登录后再来改密
redirect('/login?next=/settings/password');
}
}