summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2018-10-23 12:10:00 +0100
committerEmīls Piņķis <emils@mullvad.net>2018-10-23 12:10:00 +0100
commit39ed06de322bb29218920576a9b8ee0d37616eed (patch)
tree1e2298ca66876ef2369fbaadefa9fcf8483a8850
parentbef533403920cfc08a9f705cbbc1b5c27f6e6517 (diff)
parent8dc34dccda78800e4c78f7eada333f9430689574 (diff)
downloadmullvadvpn-39ed06de322bb29218920576a9b8ee0d37616eed.tar.xz
mullvadvpn-39ed06de322bb29218920576a9b8ee0d37616eed.zip
Merge branch 'parse-auth-failed'
-rw-r--r--Cargo.lock2
-rw-r--r--gui/packages/desktop/src/renderer/components/NotificationArea.js6
-rw-r--r--gui/packages/desktop/src/renderer/lib/auth-failure.js73
-rw-r--r--gui/packages/desktop/test/auth-failure.spec.js24
-rw-r--r--mullvad-cli/src/cmds/status.rs17
-rw-r--r--mullvad-types/Cargo.toml2
-rw-r--r--mullvad-types/src/auth_failed.rs103
-rw-r--r--mullvad-types/src/lib.rs6
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;