diff options
| author | David Lönnhager <david.l@mullvad.net> | 2022-11-30 10:29:45 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2022-11-30 10:29:45 +0100 |
| commit | d424bdbedeb1c8e44b693eac02d169e11b7ac92a (patch) | |
| tree | 40c6dfe3f82333d6884da8cc81308317e49e9a48 | |
| parent | c3a18445c5265cee590d20894eb9135d2c05b3d6 (diff) | |
| parent | efb49034ab60a27d341a1f03dcda5418363202ed (diff) | |
| download | mullvadvpn-d424bdbedeb1c8e44b693eac02d169e11b7ac92a.tar.xz mullvadvpn-d424bdbedeb1c8e44b693eac02d169e11b7ac92a.zip | |
Merge branch 'refactor-auth-failed-error'
| -rw-r--r-- | mullvad-cli/src/format.rs | 22 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 3 | ||||
| -rw-r--r-- | mullvad-management-interface/proto/management_interface.proto | 9 | ||||
| -rw-r--r-- | mullvad-management-interface/src/types/conversions/states.rs | 63 | ||||
| -rw-r--r-- | mullvad-types/src/auth_failed.rs | 95 |
5 files changed, 127 insertions, 65 deletions
diff --git a/mullvad-cli/src/format.rs b/mullvad-cli/src/format.rs index 693f5c9c7e..b4aa96a719 100644 --- a/mullvad-cli/src/format.rs +++ b/mullvad-cli/src/format.rs @@ -1,4 +1,4 @@ -use mullvad_types::{location::GeoIpLocation, states::TunnelState}; +use mullvad_types::{auth_failed::AuthFailed, location::GeoIpLocation, states::TunnelState}; use talpid_types::{ net::{Endpoint, TunnelEndpoint}, tunnel::ErrorState, @@ -166,6 +166,26 @@ fn print_error_state(error_state: &ErrorState) { println!("Blocked: {}", cause); println!("Your kernel might be terribly out of date or missing nftables"); } + talpid_types::tunnel::ErrorStateCause::AuthFailed(Some(auth_failed)) => { + println!( + "Blocked: Authentication with remote server failed: {}", + get_auth_failed_message(AuthFailed::from(auth_failed.as_str())) + ); + } cause => println!("Blocked: {}", cause), } } + +const fn get_auth_failed_message(auth_failed: AuthFailed) -> &'static str { + const INVALID_ACCOUNT_MSG: &str = "You've logged in with an account number that is not valid. Please log out and try another one."; + const EXPIRED_ACCOUNT_MSG: &str = "You have no more VPN time left on this account. Please log in on our website to buy more credit."; + const TOO_MANY_CONNECTIONS_MSG: &str = "This account has too many simultaneous connections. Disconnect another device or try connecting again shortly."; + const UNKNOWN_MSG: &str = "Unknown error."; + + match auth_failed { + AuthFailed::InvalidAccount => INVALID_ACCOUNT_MSG, + AuthFailed::ExpiredAccount => EXPIRED_ACCOUNT_MSG, + AuthFailed::TooManyConnections => TOO_MANY_CONNECTIONS_MSG, + AuthFailed::Unknown => UNKNOWN_MSG, + } +} diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index c6875d3401..3ea3cdbd27 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -41,6 +41,7 @@ use mullvad_relay_selector::{ }; use mullvad_types::{ account::{AccountData, AccountToken, VoucherSubmission}, + auth_failed::AuthFailed, device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent}, location::GeoIpLocation, relay_constraints::{BridgeSettings, BridgeState, ObfuscationSettings, RelaySettingsUpdate}, @@ -1099,7 +1100,7 @@ where } else if self.get_target_tunnel_type() == Some(TunnelType::Wireguard) { log::debug!("Entering blocking state since the account is out of time"); self.send_tunnel_command(TunnelCommand::Block(ErrorStateCause::AuthFailed( - None, + Some(AuthFailed::ExpiredAccount.as_str().to_string()), ))) } } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 5789728fce..cd9141981c 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -126,6 +126,13 @@ message ErrorState { SPLIT_TUNNEL_ERROR = 8; } + enum AuthFailedError { + UNKNOWN = 0; + INVALID_ACCOUNT = 1; + EXPIRED_ACCOUNT = 2; + TOO_MANY_CONNECTIONS = 3; + } + enum GenerationError { NO_MATCHING_RELAY = 0; NO_MATCHING_BRIDGE_RELAY = 1; @@ -149,7 +156,7 @@ message ErrorState { FirewallPolicyError blocking_error = 2; // AUTH_FAILED - string auth_fail_reason = 3; + AuthFailedError auth_failed_error = 3; // TUNNEL_PARAMETER_ERROR GenerationError parameter_error = 4; // SET_FIREWALL_POLICY_ERROR diff --git a/mullvad-management-interface/src/types/conversions/states.rs b/mullvad-management-interface/src/types/conversions/states.rs index c462e77aa6..c4f5a4de50 100644 --- a/mullvad-management-interface/src/types/conversions/states.rs +++ b/mullvad-management-interface/src/types/conversions/states.rs @@ -1,4 +1,6 @@ -use crate::types::{conversions::option_from_proto_string, proto, FromProtobufTypeError}; +#[cfg(windows)] +use crate::types::conversions::option_from_proto_string; +use crate::types::{proto, FromProtobufTypeError}; impl From<mullvad_types::states::TunnelState> for proto::TunnelState { fn from(state: mullvad_types::states::TunnelState) -> Self { @@ -101,14 +103,14 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState { } }, blocking_error: error_state.block_failure().map(map_firewall_error), - auth_fail_reason: if let talpid_tunnel::ErrorStateCause::AuthFailed( - reason, - ) = error_state.cause() - { - reason.clone().unwrap_or_default() - } else { - "".to_string() - }, + auth_failed_error: mullvad_types::auth_failed::AuthFailed::try_from( + error_state.cause(), + ) + .ok() + .map(|auth_failed| { + i32::from(proto::error_state::AuthFailedError::from(auth_failed)) + }) + .unwrap_or(0i32), parameter_error: if let talpid_tunnel::ErrorStateCause::TunnelParameterError(reason) = error_state.cause() @@ -147,6 +149,42 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState { } } +impl From<mullvad_types::auth_failed::AuthFailed> for proto::error_state::AuthFailedError { + fn from(auth_failed: mullvad_types::auth_failed::AuthFailed) -> Self { + use mullvad_types::auth_failed::AuthFailed; + use proto::error_state; + match auth_failed { + AuthFailed::InvalidAccount => error_state::AuthFailedError::InvalidAccount, + AuthFailed::ExpiredAccount => error_state::AuthFailedError::ExpiredAccount, + AuthFailed::TooManyConnections => error_state::AuthFailedError::TooManyConnections, + AuthFailed::Unknown => error_state::AuthFailedError::Unknown, + } + } +} + +fn try_auth_failed_from_i32( + auth_failed_error: i32, +) -> Result<mullvad_types::auth_failed::AuthFailed, FromProtobufTypeError> { + proto::error_state::AuthFailedError::from_i32(auth_failed_error) + .map(mullvad_types::auth_failed::AuthFailed::from) + .ok_or(FromProtobufTypeError::InvalidArgument( + "invalid auth failed error", + )) +} + +impl From<proto::error_state::AuthFailedError> for mullvad_types::auth_failed::AuthFailed { + fn from(auth_failed: proto::error_state::AuthFailedError) -> Self { + use mullvad_types::auth_failed::AuthFailed; + use proto::error_state; + match auth_failed { + error_state::AuthFailedError::InvalidAccount => AuthFailed::InvalidAccount, + error_state::AuthFailedError::ExpiredAccount => AuthFailed::ExpiredAccount, + error_state::AuthFailedError::TooManyConnections => AuthFailed::TooManyConnections, + error_state::AuthFailedError::Unknown => AuthFailed::Unknown, + } + } +} + impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState { type Error = FromProtobufTypeError; @@ -205,15 +243,16 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState { Some(proto::ErrorState { cause, blocking_error, - auth_fail_reason, + auth_failed_error, parameter_error, policy_error, }), })) => { let cause = match proto::error_state::Cause::from_i32(cause) { Some(proto::error_state::Cause::AuthFailed) => { - talpid_tunnel::ErrorStateCause::AuthFailed(option_from_proto_string( - auth_fail_reason, + let auth_failed = try_auth_failed_from_i32(auth_failed_error)?; + talpid_tunnel::ErrorStateCause::AuthFailed(Some( + auth_failed.as_str().to_string(), )) } Some(proto::error_state::Cause::Ipv6Unavailable) => { diff --git a/mullvad-types/src/auth_failed.rs b/mullvad-types/src/auth_failed.rs index fd8ff2b7b5..795bbc24e3 100644 --- a/mullvad-types/src/auth_failed.rs +++ b/mullvad-types/src/auth_failed.rs @@ -1,66 +1,63 @@ use lazy_static::lazy_static; use regex::Regex; -use std::fmt; +use talpid_types::tunnel::ErrorStateCause; -/// Used by frontends to parse [`talpid_types::tunnel::ErrorStateCause::AuthFailed`], which may be -/// returned in [`talpid_types::tunnel::ErrorStateCause`] when there is a failure to authenticate +/// Used to parse [`talpid_types::tunnel::ErrorStateCause::AuthFailed`], which may be returned +/// in [`talpid_types::tunnel::ErrorStateCause`] when there is a failure to authenticate /// with a remote server. #[derive(Debug)] -pub struct AuthFailed { - reason: AuthFailedInner, -} - -#[derive(Debug)] -enum AuthFailedInner { +pub enum AuthFailed { InvalidAccount, ExpiredAccount, - TooManyConnectons, - Unknown(String, String), + TooManyConnections, + Unknown, } -// These strings should match up with gui/packages/desktop/src/renderer/lib/auth-failure.js -const INVALID_ACCOUNT_MSG: &str = "You've logged in with an account number that is not valid. Please log out and try another one."; -const EXPIRED_ACCOUNT_MSG: &str = "You have no more VPN time left on this account. Please log in on our website to buy more credit."; -const TOO_MANY_CONNECTIONS_MSG: &str = "This account has too many simultaneous connections. Disconnect another device or try connecting again shortly."; - -impl<'a> From<&'a str> for AuthFailedInner { - fn from(reason: &'a str) -> AuthFailedInner { - use self::AuthFailedInner::*; +impl<'a> From<&'a str> for AuthFailed { + fn from(reason: &'a str) -> AuthFailed { + use AuthFailed::*; match parse_string(reason) { - Some(("INVALID_ACCOUNT", _)) => InvalidAccount, - Some(("EXPIRED_ACCOUNT", _)) => ExpiredAccount, - Some(("TOO_MANY_CONNECTIONS", _)) => TooManyConnectons, - Some((unknown_reason, message)) => { + Some("INVALID_ACCOUNT") => InvalidAccount, + Some("EXPIRED_ACCOUNT") => ExpiredAccount, + Some("TOO_MANY_CONNECTIONS") => TooManyConnections, + Some(fail_id) => { log::warn!( - "Received AUTH_FAILED message with unknown reason: {}", - reason + "Received AUTH_FAILED message with unknown failure ID: {}", + fail_id ); - Unknown(unknown_reason.to_string(), message.to_string()) + Unknown } None => { log::warn!("Received invalid AUTH_FAILED message: {}", reason); - Unknown("UNKNOWN".to_string(), reason.to_string()) + Unknown } } } } -impl<'a> From<&'a str> for AuthFailed { - fn from(reason: &'a str) -> AuthFailed { - AuthFailed { - reason: AuthFailedInner::from(reason), +impl AuthFailed { + pub fn as_str(&self) -> &'static str { + use AuthFailed::*; + match self { + InvalidAccount => "[INVALID_ACCOUNT]", + ExpiredAccount => "[EXPIRED_ACCOUNT]", + TooManyConnections => "[TOO_MANY_CONNECTIONS]", + Unknown => "[Unknown]", } } } -impl fmt::Display for AuthFailed { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::AuthFailedInner::*; - match self.reason { - InvalidAccount => write!(f, "{}", INVALID_ACCOUNT_MSG), - ExpiredAccount => write!(f, "{}", EXPIRED_ACCOUNT_MSG), - TooManyConnectons => write!(f, "{}", TOO_MANY_CONNECTIONS_MSG), - Unknown(_, ref reason) => write!(f, "{}", reason), +#[derive(Debug)] +pub struct UnexpectedErrorStateCause(()); + +impl TryFrom<&ErrorStateCause> for AuthFailed { + type Error = UnexpectedErrorStateCause; + + fn try_from(cause: &ErrorStateCause) -> Result<Self, Self::Error> { + match cause { + ErrorStateCause::AuthFailed(Some(reason)) => Ok(AuthFailed::from(reason.as_str())), + ErrorStateCause::AuthFailed(None) => Ok(AuthFailed::Unknown), + _ => Err(UnexpectedErrorStateCause(())), } } } @@ -68,16 +65,14 @@ impl fmt::Display for AuthFailed { // Expects to take a string like "[INVALID_ACCOUNT] This is not a valid Mullvad account". // The example input string would be split into: // * "INVALID_ACCOUNT" - the ID of the failure reason. -// * "This is not a valid Mullvad account" - the human readable message of the failure reason. +// * "This is not a valid Mullvad account" - human-readable message (ignored). // In the case that the message has preceeding whitespace, it will be trimmed. -fn parse_string(reason: &str) -> Option<(&str, &str)> { +fn parse_string(reason: &str) -> Option<&str> { lazy_static! { - static ref REASON_REGEX: Regex = Regex::new(r"^\[(\w+)\]\s*(.*)$").unwrap(); + static ref REASON_REGEX: Regex = Regex::new(r"^\[(\w+)\]\s*").unwrap(); } let captures = REASON_REGEX.captures(reason)?; - let reason = captures.get(1).map(|m| m.as_str())?; - let message = captures.get(2).map(|m| m.as_str())?; - Some((reason, message)) + captures.get(1).map(|m| m.as_str()) } #[cfg(test)] @@ -87,15 +82,15 @@ mod tests { #[test] fn test_parsing() { let tests = vec![ - (Some(("INVALID_ACCOUNT", "This is not a valid Mullvad account" )), + (Some("INVALID_ACCOUNT"), "[INVALID_ACCOUNT] This is not a valid Mullvad account"), - (Some(("EXPIRED_ACCOUNT", "This account has no time left")), + (Some("EXPIRED_ACCOUNT"), "[EXPIRED_ACCOUNT] This account has no time left"), - (Some(("TOO_MANY_CONNECTIONS", "This Mullvad account is already used by the maximum number of simultaneous connections")), + (Some("TOO_MANY_CONNECTIONS"), "[TOO_MANY_CONNECTIONS] This Mullvad account is already used by the maximum number of simultaneous connections"), (None, "[Incomplete String"), - (Some(("REASON_REASON", "")), "[REASON_REASON]"), - (Some(("REASON_REASON", "A")), "[REASON_REASON]A"), + (Some("REASON_REASON"), "[REASON_REASON]"), + (Some("REASON_REASON"), "[REASON_REASON]A"), (None, "incomplete]"), (None, ""), ]; |
