feat(account,session): resume from derived key passphrases and report second-factor methods

`AccountClient::key_passphrases` is now public, and
`AccountClient::with_key_passphrases` / `ProtonDriveClient::with_key_passphrases`
build a client from the per-key passphrases alone. A client that persists a
session can store those instead of the mailbox password, which on a
single-password account is the login password itself.

`ProtonApiSession::second_factor_methods` exposes the login's 2FA bitmask
(TOTP, FIDO2), so a client without WebAuthn can tell an account that only has
a security key apart from a wrong code.
This commit is contained in:
nvrl committed 2026-10-06 13:59:10 +02:00
1 parent 1bd5fe8a07
commit 6eb8b206a7
3 files changed
+155 -11

No files matched your search

+26 -1
View File
@@ -508,9 +508,34 @@ impl ProtonDriveClient {
mailbox_password: impl Into<Vec<u8>>,
key_salts: Vec<KeySalt>,
) -> Self {
Self::with_account(
session,
AccountClient::with_key_salts(session, mailbox_password, key_salts),
)
}
/// Build a Drive client whose key chain unlocks from per-key passphrases
/// already derived, with no mailbox password at all.
///
/// For a client that persists a session: storing the derived passphrases
/// ([`AccountClient::key_passphrases`], captured right after login) in
/// place of the mailbox password means the store never holds the
/// password itself — which, on a single-password account, is the login
/// password. They unlock the keys all the same, so keep them as secret.
pub fn with_key_passphrases(
session: &ProtonApiSession,
key_passphrases: HashMap<String, Vec<u8>>,
) -> Self {
Self::with_account(
session,
AccountClient::with_key_passphrases(session, key_passphrases),
)
}
fn with_account(session: &ProtonApiSession, account: AccountClient) -> Self {
Self {
http: session.http().with_base_route("drive/"),
account: AccountClient::with_key_salts(session, mailbox_password, key_salts),
account,
cache: Arc::new(Mutex::new(DriveCache::default())),
entities: DriveEntityCache::new(InMemoryCacheRepository::shared()),
telemetry: NoopTelemetry::shared(),