From 92c6dffb5a413771b4535db89fa3355a6c462395 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 13 Oct 2026 20:22:55 +0200 Subject: [PATCH] feat(api): name response code 2028 as Blocked Proton answers a blocked sign-in on auth/v4 with HTTP 422 and code 2028, "feature is disabled, rate-limited or blocked because of abuse". Its Error text says the app version is no longer supported, which is wrong for a current client. The code fell through to Unknown, so a client could not tell it from a real OutdatedApp and showed the misleading text. --- crates/proton-sdk/src/api.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/proton-sdk/src/api.rs b/crates/proton-sdk/src/api.rs index c09e2ea..ce77978 100644 --- a/crates/proton-sdk/src/api.rs +++ b/crates/proton-sdk/src/api.rs @@ -129,6 +129,11 @@ pub enum ResponseCode { InvalidValue = 2001, NotEnoughPermissions = 2011, NotEnoughPermissionsToGrantPermissions = 2026, + /// The feature is disabled, rate-limited or blocked for abuse. Arrives with + /// HTTP 422. On `auth/v4` it is a temporary block on the sign-in, and its + /// `Error` text claims the app version is no longer supported even when + /// it is. + Blocked = 2028, InvalidEncryptedIdFormat = 2061, AlreadyExists = 2500, DoesNotExist = 2501, @@ -175,6 +180,7 @@ impl ResponseCode { 2001 => Self::InvalidValue, 2011 => Self::NotEnoughPermissions, 2026 => Self::NotEnoughPermissionsToGrantPermissions, + 2028 => Self::Blocked, 2061 => Self::InvalidEncryptedIdFormat, 2500 => Self::AlreadyExists, 2501 => Self::DoesNotExist, @@ -246,6 +252,17 @@ mod tests { assert_eq!(envelope.code, ResponseCode::HumanVerificationRequired); } + /// A blocked sign-in claims the app is outdated. Only the code tells it + /// apart from a real [`ResponseCode::OutdatedApp`]. + #[test] + fn a_blocked_login_is_not_an_outdated_app() { + let envelope: ApiResponse = serde_json::from_str( + r#"{"Code": 2028, "Error": "This version of the app is no longer supported, please update to continue using the app"}"#, + ) + .unwrap(); + assert_eq!(envelope.code, ResponseCode::Blocked); + } + #[test] fn the_challenge_is_read_off_the_details_object() { let hv = gated_error()