Green: prefer TOTP via twoFactorSessionIDV2 when passkey also enrolled

beginLogin now checks twoFactorSessionID ?? twoFactorSessionIDV2 before
the passkey branch. Accounts with both passkeys and TOTP can now log in
from the CLI using their authenticator app.
This commit is contained in:
2026-06-10 11:05:36 -07:00
parent 0024631ef3
commit 9fd7b6a857
2 changed files with 15 additions and 4 deletions

View File

@@ -73,8 +73,14 @@ const srpHandshake = async (
srpClient.checkM2(Buffer.from(verifyResponse.srpM2, "base64"));
if (verifyResponse.twoFactorSessionID) {
return { kind: "totp", sessionID: verifyResponse.twoFactorSessionID };
// twoFactorSessionIDV2 is set (instead of twoFactorSessionID) when the
// account has BOTH passkeys and TOTP. Prefer TOTP: a CLI cannot perform
// a WebAuthn ceremony, and the user has a TOTP secret enrolled.
const totpSessionID =
verifyResponse.twoFactorSessionID ??
verifyResponse.twoFactorSessionIDV2;
if (totpSessionID) {
return { kind: "totp", sessionID: totpSessionID };
}
if (verifyResponse.passkeySessionID) {
return {

View File

@@ -34,13 +34,18 @@ export interface SRPAttributes {
// The body of a successful login response. Exactly one of the following
// situations applies, and the caller dispatches on the populated fields:
// - both keyAttributes and encryptedToken present: login is complete
// - twoFactorSessionID present: caller must submit a TOTP code
// - passkeySessionID present: caller must complete a passkey ceremony
// - twoFactorSessionID present: caller must submit a TOTP code (TOTP-only
// account)
// - passkeySessionID + twoFactorSessionIDV2 present: account has both
// passkeys and TOTP; the V2 field is set instead of twoFactorSessionID
// so that older clients keep using the passkey flow
// - only passkeySessionID present: caller must complete a passkey ceremony
export interface AuthorizationResponse {
id: number;
keyAttributes?: KeyAttributes;
encryptedToken?: Base64;
twoFactorSessionID?: string;
twoFactorSessionIDV2?: string;
passkeySessionID?: string;
}