feat: add passkey-first login and management flow

This commit is contained in:
Shuai
2026-03-31 00:59:50 +08:00
parent 1184cb8d9a
commit 0f6da7d147
16 changed files with 799 additions and 6 deletions
+33
View File
@@ -4,6 +4,7 @@ import {
getProfile,
loadSession,
loginWithPassword,
loginWithPasskey,
refreshAccessToken,
recoverTwoFactor,
registerAccount,
@@ -46,6 +47,11 @@ export type PasswordLoginResult =
| { kind: 'totp'; pendingTotp: PendingTotp }
| { kind: 'error'; message: string };
export type PasskeyLoginResult =
| { kind: 'success'; login: CompletedLogin }
| { kind: 'totp' }
| { kind: 'error'; message: string };
export interface RecoverTwoFactorResult {
login: CompletedLogin | null;
newRecoveryCode: string | null;
@@ -359,3 +365,30 @@ export async function performUnlock(
}
return { ...refreshedSession, ...keys };
}
export async function performPasskeyLogin(email: string, totpCode?: string): Promise<PasskeyLoginResult> {
const token = await loginWithPasskey(email, totpCode);
if ('access_token' in token && token.access_token) {
const normalizedEmail = String(email || '').trim().toLowerCase();
const baseSession: SessionState = {
accessToken: token.access_token,
refreshToken: token.refresh_token,
email: normalizedEmail,
symEncKey: token.VaultKeys?.symEncKey,
symMacKey: token.VaultKeys?.symMacKey,
};
const tempFetch = createAuthedFetch(() => baseSession, () => {});
const profile = buildTransientProfile(token, normalizedEmail);
return {
kind: 'success',
login: {
session: baseSession,
profile,
profilePromise: getProfile(tempFetch),
},
};
}
const tokenError = token as { TwoFactorProviders?: unknown; error_description?: string; error?: string };
if (tokenError.TwoFactorProviders) return { kind: 'totp' };
return { kind: 'error', message: tokenError.error_description || tokenError.error || 'Passkey login failed' };
}