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.
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
// Base64-encoded binary, in either standard or URL-safe form. Decoded with
|
|
// crypto.fromBase64 which accepts either variant.
|
|
export type Base64 = string;
|
|
|
|
// The blob the server returns alongside the auth token after a successful
|
|
// login. Together with the user's password, this is everything quak needs
|
|
// to derive the master key, secret key, and auth token.
|
|
export interface KeyAttributes {
|
|
kekSalt: Base64;
|
|
encryptedKey: Base64;
|
|
keyDecryptionNonce: Base64;
|
|
publicKey: Base64;
|
|
encryptedSecretKey: Base64;
|
|
secretKeyDecryptionNonce: Base64;
|
|
memLimit: number;
|
|
opsLimit: number;
|
|
masterKeyEncryptedWithRecoveryKey?: Base64;
|
|
masterKeyDecryptionNonce?: Base64;
|
|
recoveryKeyEncryptedWithMasterKey?: Base64;
|
|
recoveryKeyDecryptionNonce?: Base64;
|
|
}
|
|
|
|
// Server-issued attributes needed to start an SRP-6a handshake. Returned
|
|
// from GET /users/srp/attributes?email=<email>.
|
|
export interface SRPAttributes {
|
|
srpUserID: string;
|
|
srpSalt: Base64;
|
|
memLimit: number;
|
|
opsLimit: number;
|
|
kekSalt: Base64;
|
|
isEmailMFAEnabled: boolean;
|
|
}
|
|
|
|
// 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 (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;
|
|
}
|
|
|
|
// Discriminated union returned by the high-level login functions to tell
|
|
// the caller what to do next.
|
|
export type LoginChallenge =
|
|
| { kind: "complete"; response: AuthorizationResponse }
|
|
| { kind: "totp"; sessionID: string }
|
|
| { kind: "passkey"; sessionID: string }
|
|
| { kind: "emailOTP" };
|