diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-10-23 12:10:00 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2018-10-23 12:10:00 +0100 |
| commit | 39ed06de322bb29218920576a9b8ee0d37616eed (patch) | |
| tree | 1e2298ca66876ef2369fbaadefa9fcf8483a8850 | |
| parent | bef533403920cfc08a9f705cbbc1b5c27f6e6517 (diff) | |
| parent | 8dc34dccda78800e4c78f7eada333f9430689574 (diff) | |
| download | mullvadvpn-39ed06de322bb29218920576a9b8ee0d37616eed.tar.xz mullvadvpn-39ed06de322bb29218920576a9b8ee0d37616eed.zip | |
Merge branch 'parse-auth-failed'
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/NotificationArea.js | 6 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/lib/auth-failure.js | 73 | ||||
| -rw-r--r-- | gui/packages/desktop/test/auth-failure.spec.js | 24 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 17 | ||||
| -rw-r--r-- | mullvad-types/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-types/src/auth_failed.rs | 103 | ||||
| -rw-r--r-- | mullvad-types/src/lib.rs | 6 |
8 files changed, 228 insertions, 5 deletions
diff --git a/Cargo.lock b/Cargo.lock index fb18677e07..b518ba0a83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1044,8 +1044,10 @@ version = "0.1.0" dependencies = [ "chrono 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "mullvad-paths 0.1.0", + "regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.71 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/gui/packages/desktop/src/renderer/components/NotificationArea.js b/gui/packages/desktop/src/renderer/components/NotificationArea.js index 8fff84f375..df8c63706c 100644 --- a/gui/packages/desktop/src/renderer/components/NotificationArea.js +++ b/gui/packages/desktop/src/renderer/components/NotificationArea.js @@ -11,6 +11,7 @@ import { NotificationOpenLinkAction, } from './NotificationBanner'; +import { AuthFailure } from '../lib/auth-failure'; import type { BlockReason, TunnelStateTransition } from '../lib/daemon-rpc'; import type { VersionReduxState } from '../redux/version/reducers'; @@ -34,10 +35,7 @@ type State = NotificationAreaPresentation & { function getBlockReasonMessage(blockReason: BlockReason): string { switch (blockReason.reason) { case 'auth_failed': { - const details = - blockReason.details || - 'Check that the account is valid, has time left and not too many connections'; - return `Authentication failed: ${details}`; + return new AuthFailure(blockReason.details).show(); } case 'ipv6_unavailable': return 'Could not configure IPv6, please enable it on your system or disable it in the app'; diff --git a/gui/packages/desktop/src/renderer/lib/auth-failure.js b/gui/packages/desktop/src/renderer/lib/auth-failure.js new file mode 100644 index 0000000000..d012e3f826 --- /dev/null +++ b/gui/packages/desktop/src/renderer/lib/auth-failure.js @@ -0,0 +1,73 @@ +// @flow + +import log from 'electron-log'; + +export type AuthFailureKind = + | 'INVALID_ACCOUNT' + | 'EXPIRED_ACCOUNT' + | 'TOO_MANY_CONNECTIONS' + | 'UNKNOWN'; + +// These strings should match up with mullvad-types/src/auth_failed.rs +const GENERIC_FAILURE_MSG = 'Account authentication failed'; +const INVALID_ACCOUNT_MSG = + "You've logged in with an account number that is not valid. Please log out and try another one."; +const EXPIRED_ACCOUNT_MSG = + '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 = + 'This account has too many simultaneous connections. Disconnect another device or try connecting again shortly.'; + +export class AuthFailure { + _reasonId: AuthFailureKind; + _message: string; + + constructor(reason: ?string) { + if (!reason) { + log.error('Received invalid auth_failed reason: ', reason); + this._reasonId = 'UNKNOWN'; + this._message = GENERIC_FAILURE_MSG; + return; + } + + const results = /^\[(\w+)\]\s*(.*)$/.exec(reason); + + if (!results || results.length < 3) { + log.error(`Received invalid auth_failed message - "${reason}"`); + this._reasonId = 'UNKNOWN'; + this._message = reason; + return; + } + + const id_string = results[1]; + this._reasonId = strToFailureKind(id_string); + this._message = results[2] || GENERIC_FAILURE_MSG; + } + + show(): string { + switch (this._reasonId) { + case 'INVALID_ACCOUNT': + return INVALID_ACCOUNT_MSG; + case 'EXPIRED_ACCOUNT': + return EXPIRED_ACCOUNT_MSG; + case 'TOO_MANY_CONNECTIONS': + return TOO_MANY_CONNECTIONS_MSG; + case 'UNKNOWN': + return this._message; + + default: + throw new Error(`Invalid reason ID: ${(this._reasonId: empty)}`); + } + } +} + +export function strToFailureKind(id: string): AuthFailureKind { + switch (id) { + case 'INVALID_ACCOUNT': + case 'EXPIRED_ACCOUNT': + case 'TOO_MANY_CONNECTIONS': + return id; + default: + log.error(`Received unknown auth_failed message id - ${id}`); + return 'UNKNOWN'; + } +} diff --git a/gui/packages/desktop/test/auth-failure.spec.js b/gui/packages/desktop/test/auth-failure.spec.js new file mode 100644 index 0000000000..86bb97b13c --- /dev/null +++ b/gui/packages/desktop/test/auth-failure.spec.js @@ -0,0 +1,24 @@ +import { AuthFailure } from '../src/renderer/lib/auth-failure'; +describe('auth_failed parsing', () => { + it('invalid line parsing works', () => { + const auth_msg = new AuthFailure('invalid auth_failed message'); + expect(auth_msg._reasonId).to.be.eql('UNKNOWN'); + expect(auth_msg.show()).to.be.eql('invalid auth_failed message'); + }); + + it('valid unknown works', () => { + const auth_msg = new AuthFailure('[valid_unknown] Message'); + expect(auth_msg._reasonId).to.be.eql('UNKNOWN'); + expect(auth_msg.show()).to.be.eql('Message'); + }); + + it('valid known works', () => { + const auth_msg = new AuthFailure('[INVALID_ACCOUNT] Invalid account'); + expect(auth_msg._reasonId).to.be.eql('INVALID_ACCOUNT'); + }); + + it('empty message works', () => { + const auth_msg = new AuthFailure('[INVALID_ACCOUNT]'); + expect(auth_msg._reasonId).to.be.eql('INVALID_ACCOUNT'); + }); +}); diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index d25a591726..b1a05bfd07 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -4,6 +4,8 @@ use Command; use Result; use mullvad_ipc_client::DaemonRpcClient; +use mullvad_types::auth_failed::AuthFailed; +use talpid_types::tunnel::BlockReason; use talpid_types::tunnel::TunnelStateTransition::{self, *}; pub struct Status; @@ -44,7 +46,7 @@ impl Command for Status { fn print_state(state: &TunnelStateTransition) { print!("Tunnel status: "); match state { - Blocked(reason) => println!("Blocked ({})", reason), + Blocked(reason) => print_blocked_reason(reason), Connected(_) => println!("Connected"), Connecting(_) => println!("Connecting..."), Disconnected => println!("Disconnected"), @@ -52,6 +54,19 @@ fn print_state(state: &TunnelStateTransition) { } } +fn print_blocked_reason(reason: &BlockReason) { + match reason { + BlockReason::AuthFailed(ref auth_failure) => { + let auth_failure_str = auth_failure + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("Account authentication failed"); + println!("Blocked: {}", AuthFailed::from_str(auth_failure_str)); + } + other => println!("Blocked: {}", other), + } +} + fn print_location(rpc: &mut DaemonRpcClient) -> Result<()> { let location = rpc.get_current_location()?; let city_and_country = if let Some(city) = location.city { diff --git a/mullvad-types/Cargo.toml b/mullvad-types/Cargo.toml index 74d5645859..0f67cb1eb7 100644 --- a/mullvad-types/Cargo.toml +++ b/mullvad-types/Cargo.toml @@ -12,6 +12,8 @@ serde = "1.0" serde_json = "1.0" error-chain = "0.12" log = "0.4" +regex = "1" +lazy_static = "1.1.0" talpid-types = { path = "../talpid-types" } mullvad-paths = { path = "../mullvad-paths" } diff --git a/mullvad-types/src/auth_failed.rs b/mullvad-types/src/auth_failed.rs new file mode 100644 index 0000000000..d53de2d136 --- /dev/null +++ b/mullvad-types/src/auth_failed.rs @@ -0,0 +1,103 @@ +use regex::Regex; + +#[derive(Debug)] +pub struct AuthFailed { + reason: AuthFailedInner, +} + +#[derive(Debug)] +enum AuthFailedInner { + InvalidAccount, + ExpiredAccount, + TooManyConnectons, + Unknown(String, String), +} + +// 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 AuthFailedInner { + fn from_str(input: &str) -> AuthFailedInner { + use self::AuthFailedInner::*; + match parse_string(input) { + Some(("INVALID_ACCOUNT", _)) => InvalidAccount, + Some(("EXPIRED_ACCOUNT", _)) => ExpiredAccount, + Some(("TOO_MANY_CONNECTIONS", _)) => TooManyConnectons, + Some((unknown_reason, message)) => { + warn!( + "Received AUTH_FAILED message with unknown reason: {}", + input + ); + Unknown(unknown_reason.to_string(), message.to_string()) + } + None => { + warn!("Received invalid AUTH_FAILED message: {}", input); + Unknown("UNKNOWN".to_string(), input.to_string()) + } + } + } +} + +impl AuthFailed { + pub fn from_str(reason: &str) -> AuthFailed { + AuthFailed { + reason: AuthFailedInner::from_str(reason), + } + } +} + +impl ::std::fmt::Display for AuthFailed { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::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), + } + } +} + +// 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. +// In the case that the message has preceeding whitespace, it will be trimmed. +fn parse_string<'a>(reason: &'a str) -> Option<(&'a str, &'a str)> { + lazy_static! { + 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)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parsing() { + let tests = vec![ + (Some(("INVALID_ACCOUNT", "This is not a valid Mullvad account" )), + "[INVALID_ACCOUNT] This is not a valid Mullvad account"), + (Some(("EXPIRED_ACCOUNT", "This account has no time left")), + "[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")), + "[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"), + (None, "incomplete]"), + (None, ""), + ]; + + for (expected_output, input) in tests.iter() { + assert_eq!(*expected_output, parse_string(input)); + } + } + +} diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index ff7628b9ad..749da41353 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -7,6 +7,7 @@ //! the License, or (at your option) any later version. extern crate chrono; +extern crate regex; extern crate serde; #[macro_use] extern crate serde_derive; @@ -20,7 +21,12 @@ extern crate log; #[macro_use] extern crate error_chain; +#[macro_use] +extern crate lazy_static; + + pub mod account; +pub mod auth_failed; pub mod location; pub mod relay_constraints; pub mod relay_list; |
