diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5d42a470fc..f6c4e06fa1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Line wrap the file at 100 chars. Th ## [Unreleased] ### Added +- Add account UUID to verbose 'mullvad account get -v' output. + #### Android - Add support for all screen orientations. diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index c8765ec2b2b1..d9c3d9e21bdb 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -1,6 +1,5 @@ #![deny(rust_2018_idioms)] -use chrono::{offset::Utc, DateTime}; #[cfg(target_os = "android")] use futures::channel::mpsc; use futures::Stream; @@ -8,7 +7,7 @@ use hyper::Method; #[cfg(target_os = "android")] use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ - account::{AccountToken, VoucherSubmission}, + account::{AccountData, AccountToken, VoucherSubmission}, version::AppVersion, }; use proxy::ApiConnectionMode; @@ -382,15 +381,10 @@ impl AccountsProxy { Self { handle } } - pub fn get_expiry( + pub fn get_data( &self, account: AccountToken, - ) -> impl Future, rest::Error>> { - #[derive(serde::Deserialize)] - struct AccountExpiryResponse { - expiry: DateTime, - } - + ) -> impl Future> { let service = self.handle.service.clone(); let factory = self.handle.factory.clone(); async move { @@ -399,8 +393,7 @@ impl AccountsProxy { .expected_status(&[StatusCode::OK]) .account(account)?; let response = service.request(request).await?; - let account: AccountExpiryResponse = response.deserialize().await?; - Ok(account.expiry) + response.deserialize().await } } diff --git a/mullvad-cli/src/cmds/account.rs b/mullvad-cli/src/cmds/account.rs index 3b3d12b153a7..229c2a9aa0bf 100644 --- a/mullvad-cli/src/cmds/account.rs +++ b/mullvad-cli/src/cmds/account.rs @@ -106,18 +106,24 @@ impl Account { match state { DeviceState::LoggedIn(device) => { - println!("Mullvad account: {}", device.account_token); - println!("Device name : {}", device.device.pretty_name()); - if verbose { - println!("Device id : {}", device.device.id); - println!("Device pubkey : {}", device.device.pubkey); - println!("Device created : {}", device.device.created,); - } - let expiry = rpc.get_account_data(device.account_token).await?; + println!("{:<20}{}", "Mullvad account:", device.account_token); + + let data = rpc.get_account_data(device.account_token).await?; println!( - "Expires at : {}", - expiry.expiry.with_timezone(&chrono::Local), + "{:<20}{}", + "Expires at:", + data.expiry.with_timezone(&chrono::Local) ); + if verbose { + println!("{:<20}{}", "Account id:", data.id); + } + + println!("{:<20}{}", "Device name:", device.device.pretty_name()); + if verbose { + println!("{:<20}{}", "Device id:", device.device.id); + println!("{:<20}{}", "Device pubkey:", device.device.pubkey); + println!("{:<20}{}", "Device created:", device.device.created,); + } } DeviceState::LoggedOut => { println!("{NOT_LOGGED_IN_MESSAGE}"); diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs index 188fa9a1d7ac..70d491b4c6bb 100644 --- a/mullvad-daemon/src/device/mod.rs +++ b/mullvad-daemon/src/device/mod.rs @@ -1100,11 +1100,18 @@ impl AccountManager { Ok(self.fetch_device_config(old_config)) } - fn expiry_call(&self) -> Result, Error>>, Error> { + fn expiry_call( + &self, + ) -> Result, Error>>, Error> { let old_config = self.data.device().ok_or(Error::NoDevice)?; let account_token = old_config.account_token.clone(); let account_service = self.account_service.clone(); - Ok(async move { account_service.check_expiry_2(account_token).await }) + Ok(async move { + account_service + .get_data_2(account_token) + .await + .map(|data| data.expiry) + }) } fn needs_validation(&mut self) -> bool { diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs index a56cf10b4829..580d993edc5a 100644 --- a/mullvad-daemon/src/device/service.rs +++ b/mullvad-daemon/src/device/service.rs @@ -1,11 +1,11 @@ use std::{future::Future, time::Duration}; -use chrono::{DateTime, Utc}; +use chrono::Utc; use futures::future::{abortable, AbortHandle}; #[cfg(target_os = "android")] use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken}; use mullvad_types::{ - account::{AccountToken, VoucherSubmission}, + account::{AccountData, AccountToken, VoucherSubmission}, device::{Device, DeviceId}, wireguard::WireguardData, }; @@ -284,23 +284,23 @@ impl AccountService { ) } - pub async fn check_expiry(&self, token: AccountToken) -> Result, rest::Error> { + pub async fn get_data(&self, token: AccountToken) -> Result { let proxy = self.proxy.clone(); let api_handle = self.api_availability.clone(); let result = retry_future( - move || proxy.get_expiry(token.clone()), + move || proxy.get_data(token.clone()), move |result| should_retry(result, &api_handle), RETRY_ACTION_STRATEGY, ) .await; - if handle_expiry_result_inner(&result, &self.api_availability) { + if handle_account_data_result(&result, &self.api_availability) { self.initial_check_abort_handle.abort(); } result } - pub async fn check_expiry_2(&self, token: AccountToken) -> Result, Error> { - self.check_expiry(token).await.map_err(map_rest_error) + pub async fn get_data_2(&self, token: AccountToken) -> Result { + self.get_data(token).await.map_err(map_rest_error) } pub async fn submit_voucher( @@ -385,9 +385,9 @@ pub fn spawn_account_service( }; let future_generator = move || { - let expiry_fut = api_availability.when_online(accounts_proxy.get_expiry(token.clone())); + let expiry_fut = api_availability.when_online(accounts_proxy.get_data(token.clone())); let api_availability_copy = api_availability.clone(); - async move { handle_expiry_result_inner(&expiry_fut.await, &api_availability_copy) } + async move { handle_account_data_result(&expiry_fut.await, &api_availability_copy) } }; let should_retry = move |state_was_updated: &bool| -> bool { !*state_was_updated }; retry_future(future_generator, should_retry, RETRY_BACKOFF_STRATEGY).await; @@ -401,16 +401,16 @@ pub fn spawn_account_service( } } -fn handle_expiry_result_inner( - result: &Result, rest::Error>, +fn handle_account_data_result( + result: &Result, api_availability: &ApiAvailabilityHandle, ) -> bool { match result { - Ok(_expiry) if *_expiry >= chrono::Utc::now() => { + Ok(_data) if _data.expiry >= chrono::Utc::now() => { api_availability.resume_background(); true } - Ok(_expiry) => { + Ok(_data) => { api_availability.pause_background(); true } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 19e1d02dfb44..72f5a871906b 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -1445,12 +1445,8 @@ where ) { let account = self.account_manager.account_service.clone(); tokio::spawn(async move { - let result = account.check_expiry(account_token).await; - Self::oneshot_send( - tx, - result.map(|expiry| AccountData { expiry }), - "account data", - ); + let result = account.get_data(account_token).await; + Self::oneshot_send(tx, result, "account data"); }); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 97507ca8a2c6..eb3aa6bba120 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -105,7 +105,10 @@ service ManagementService { message UUID { string value = 1; } -message AccountData { google.protobuf.Timestamp expiry = 1; } +message AccountData { + string id = 1; + google.protobuf.Timestamp expiry = 2; +} message AccountHistory { google.protobuf.StringValue token = 1; } diff --git a/mullvad-management-interface/src/types/conversions/account.rs b/mullvad-management-interface/src/types/conversions/account.rs index 82b3ebfb49b8..c98166ba8084 100644 --- a/mullvad-management-interface/src/types/conversions/account.rs +++ b/mullvad-management-interface/src/types/conversions/account.rs @@ -37,6 +37,7 @@ impl TryFrom for VoucherSubmission { impl From for types::AccountData { fn from(data: AccountData) -> Self { types::AccountData { + id: data.id, expiry: Some(types::Timestamp { seconds: data.expiry.timestamp(), nanos: 0, @@ -56,6 +57,7 @@ impl TryFrom for AccountData { chrono::NaiveDateTime::from_timestamp_opt(expiry.seconds, expiry.nanos as u32).unwrap(); Ok(AccountData { + id: data.id, expiry: chrono::Utc.from_utc_datetime(&ndt), }) } diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs index 7adb7fbffa80..bb59f14fb173 100644 --- a/mullvad-types/src/account.rs +++ b/mullvad-types/src/account.rs @@ -3,12 +3,15 @@ use chrono::{offset::Utc, DateTime}; use jnix::{FromJava, IntoJava}; use serde::{Deserialize, Serialize}; -/// Identifier used to identify a Mullvad account. +/// Account identifier used for authentication. pub type AccountToken = String; -/// Identifier used to authenticate a Mullvad account. +/// Temporary authorization token derived from a Mullvad account. pub type AccessToken = String; +/// Account identifier (not used for authentication). +pub type AccountId = String; + /// The payment token returned by initiating a google play purchase. /// In the API this is called the `obfuscated_id`. #[cfg(target_os = "android")] @@ -19,6 +22,8 @@ pub type PlayPurchasePaymentToken = String; #[cfg_attr(target_os = "android", derive(IntoJava))] #[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))] pub struct AccountData { + #[cfg_attr(target_os = "android", jnix(skip))] + pub id: AccountId, #[cfg_attr(target_os = "android", jnix(map = "|expiry| expiry.to_string()"))] pub expiry: DateTime, }