Skip to content

Commit

Permalink
Add support for system messages, feature flags, accounts (#1503)
Browse files Browse the repository at this point in the history
* Extend network discovery system

Extend network discovery system and add support for pulling from the
vpn-api:

- System messages
- Feature flags
- Account links
  • Loading branch information
octol authored Nov 11, 2024
1 parent f8d863a commit 9173ecd
Show file tree
Hide file tree
Showing 32 changed files with 1,585 additions and 356 deletions.
2 changes: 2 additions & 0 deletions nym-vpn-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,9 @@ where
match self.account_storage.load_account().await {
Ok(account) => {
tracing::debug!("Our account id: {}", account.id());
self.account_state.set_mnemonic(MnemonicState::Stored).await;
self.account_state
.set_mnemonic(MnemonicState::Stored { id: account.id() })
.await;
Some(account)
}
Err(err) => {
Expand Down Expand Up @@ -428,7 +430,7 @@ where
}

self.account_state
.set_account(AccountState::from(account_summary.account.status))
.set_account(AccountState::from(account_summary.account))
.await;

self.account_state
Expand Down
41 changes: 29 additions & 12 deletions nym-vpn-core/crates/nym-vpn-account-controller/src/shared_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use std::{fmt, sync::Arc, time::Duration};

use nym_vpn_api_client::response::{
NymVpnAccountStatusResponse, NymVpnAccountSummarySubscription, NymVpnDeviceStatus,
NymVpnSubscriptionStatus,
NymVpnAccountResponse, NymVpnAccountStatusResponse, NymVpnAccountSummarySubscription,
NymVpnDeviceStatus, NymVpnSubscriptionStatus,
};
use serde::Serialize;
use tokio::sync::MutexGuard;
Expand Down Expand Up @@ -103,10 +103,15 @@ impl SharedAccountState {

pub(crate) async fn is_ready_to_register_device(&self) -> ReadyToRegisterDevice {
let state = self.lock().await.clone();
if state.mnemonic != Some(MnemonicState::Stored) {
if !state
.mnemonic
.map(|m| matches!(m, MnemonicState::Stored { .. }))
.unwrap_or(false)
{
return ReadyToRegisterDevice::NoMnemonicStored;
}
if state.account != Some(AccountState::Active) {
// if state.account.map(|a| !a.is_active()).unwrap_or(false) {
return ReadyToRegisterDevice::AccountNotActive;
}
if state.subscription != Some(SubscriptionState::Active) {
Expand Down Expand Up @@ -190,7 +195,7 @@ pub enum MnemonicState {
NotStored,

// The recovery phrase is stored locally
Stored,
Stored { id: String },
}

#[derive(Debug, Clone, PartialEq, Serialize)]
Expand Down Expand Up @@ -239,9 +244,21 @@ pub enum DeviceState {
}

impl AccountStateSummary {
pub fn account_id(&self) -> Option<String> {
match &self.mnemonic {
Some(MnemonicState::Stored { id }) => Some(id.clone()),
_ => None,
}
}

// If we are ready right right now.
fn is_ready_now(&self) -> ReadyToConnect {
if self.mnemonic != Some(MnemonicState::Stored) {
if !self
.mnemonic
.as_ref()
.map(|m| matches!(m, MnemonicState::Stored { .. }))
.unwrap_or(false)
{
return ReadyToConnect::NoMnemonicStored;
}
if self.account != Some(AccountState::Active) {
Expand All @@ -265,14 +282,14 @@ impl AccountStateSummary {
fn is_ready(&self) -> Option<ReadyToConnect> {
match self.mnemonic {
Some(MnemonicState::NotStored) => return Some(ReadyToConnect::NoMnemonicStored),
Some(MnemonicState::Stored) => {}
Some(MnemonicState::Stored { .. }) => {}
None => return None,
}
match self.account {
Some(AccountState::NotRegistered) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Inactive) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::DeleteMe) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Active) => {}
Some(AccountState::Inactive { .. }) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::DeleteMe { .. }) => return Some(ReadyToConnect::AccountNotActive),
Some(AccountState::Active { .. }) => {}
None => return None,
}
match self.subscription {
Expand Down Expand Up @@ -318,9 +335,9 @@ fn debug_or_unknown(state: Option<&impl fmt::Debug>) -> String {
.unwrap_or_else(|| "Unknown".to_string())
}

impl From<NymVpnAccountStatusResponse> for AccountState {
fn from(status: NymVpnAccountStatusResponse) -> Self {
match status {
impl From<NymVpnAccountResponse> for AccountState {
fn from(account: NymVpnAccountResponse) -> Self {
match account.status {
NymVpnAccountStatusResponse::Active => AccountState::Active,
NymVpnAccountStatusResponse::Inactive => AccountState::Inactive,
NymVpnAccountStatusResponse::DeleteMe => AccountState::DeleteMe,
Expand Down
13 changes: 13 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib/src/platform/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use std::{path::PathBuf, str::FromStr, sync::Arc, time::Duration};

use nym_vpn_account_controller::{AccountCommand, ReadyToConnect, SharedAccountState};
use nym_vpn_api_client::types::VpnApiAccount;
use nym_vpn_store::{keys::KeyStore, mnemonic::MnemonicStorage};
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
use tokio_util::sync::CancellationToken;
Expand Down Expand Up @@ -184,6 +185,18 @@ pub(super) async fn is_account_mnemonic_stored(path: &str) -> Result<bool, VpnEr
})
}

pub(super) async fn get_account_id(path: &str) -> Result<String, VpnError> {
let storage = setup_account_storage(path)?;
storage
.load_mnemonic()
.await
.map(VpnApiAccount::from)
.map(|account| account.id())
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

pub(super) async fn remove_account_mnemonic(path: &str) -> Result<bool, VpnError> {
// TODO: remove the mnemonic by sending a command to the account controller instead of directly
// interacting with the storage.
Expand Down
58 changes: 56 additions & 2 deletions nym-vpn-core/crates/nym-vpn-lib/src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ use crate::{
TunnelStateMachine, TunnelType,
},
uniffi_custom_impls::{
AccountStateSummary, BandwidthStatus, ConnectionStatus, EntryPoint, ExitPoint,
GatewayMinPerformance, GatewayType, Location, NetworkEnvironment, TunStatus, UserAgent,
AccountLinks, AccountStateSummary, BandwidthStatus, ConnectionStatus, EntryPoint,
ExitPoint, GatewayMinPerformance, GatewayType, Location, NetworkEnvironment, SystemMessage,
TunStatus, UserAgent,
},
};

Expand Down Expand Up @@ -148,6 +149,59 @@ async fn fetch_environment(network_name: &str) -> Result<NetworkEnvironment, Vpn
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn fetchSystemMessages(network_name: &str) -> Result<Vec<SystemMessage>, VpnError> {
RUNTIME.block_on(fetch_system_messages(network_name))
}

async fn fetch_system_messages(network_name: &str) -> Result<Vec<SystemMessage>, VpnError> {
nym_vpn_network_config::Network::fetch(network_name)
.map(|network| {
network
.nym_vpn_network
.system_messages
.into_current_iter()
.map(SystemMessage::from)
.collect()
})
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn fetchAccountLinks(
account_store_path: &str,
network_name: &str,
locale: &str,
) -> Result<AccountLinks, VpnError> {
RUNTIME.block_on(fetch_account_links(
account_store_path,
network_name,
locale,
))
}

async fn fetch_account_links(
path: &str,
network_name: &str,
locale: &str,
) -> Result<AccountLinks, VpnError> {
let account_id = account::get_account_id(path).await?;
nym_vpn_network_config::Network::fetch(network_name)
.and_then(|network| {
network
.nym_vpn_network
.try_into_parsed_links(locale, &account_id)
})
.map(AccountLinks::from)
.map_err(|err| VpnError::InternalError {
details: err.to_string(),
})
}

#[allow(non_snake_case)]
#[uniffi::export]
pub fn storeAccountMnemonic(mnemonic: String, path: String) -> Result<(), VpnError> {
Expand Down
79 changes: 75 additions & 4 deletions nym-vpn-core/crates/nym-vpn-lib/src/uniffi_custom_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only

use std::{
collections::HashMap,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
path::PathBuf,
str::FromStr,
Expand Down Expand Up @@ -258,13 +259,15 @@ impl UniffiCustomTypeConverter for OffsetDateTime {
pub struct NetworkEnvironment {
pub nym_network: NymNetworkDetails,
pub nym_vpn_network: NymVpnNetwork,
pub feature_flags: Option<FeatureFlags>,
}

impl From<nym_vpn_network_config::Network> for NetworkEnvironment {
fn from(network: nym_vpn_network_config::Network) -> Self {
NetworkEnvironment {
nym_network: network.nym_network.network.into(),
nym_vpn_network: network.nym_vpn_network.into(),
feature_flags: network.feature_flags.map(FeatureFlags::from),
}
}
}
Expand Down Expand Up @@ -375,6 +378,38 @@ impl From<nym_vpn_network_config::NymVpnNetwork> for NymVpnNetwork {
}
}

#[derive(uniffi::Record)]
pub struct FeatureFlags {
pub flags: HashMap<String, FlagValue>,
}

#[derive(uniffi::Enum)]
pub enum FlagValue {
Value(String),
Group(HashMap<String, String>),
}

impl From<nym_vpn_network_config::FeatureFlags> for FeatureFlags {
fn from(value: nym_vpn_network_config::FeatureFlags) -> Self {
FeatureFlags {
flags: value
.flags
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}

impl From<nym_vpn_network_config::feature_flags::FlagValue> for FlagValue {
fn from(value: nym_vpn_network_config::feature_flags::FlagValue) -> Self {
match value {
nym_vpn_network_config::feature_flags::FlagValue::Value(v) => FlagValue::Value(v),
nym_vpn_network_config::feature_flags::FlagValue::Group(g) => FlagValue::Group(g),
}
}
}

#[derive(uniffi::Record)]
pub struct Location {
pub two_letter_iso_country_code: String,
Expand Down Expand Up @@ -656,7 +691,7 @@ impl From<nym_vpn_account_controller::shared_state::MnemonicState> for MnemonicS
nym_vpn_account_controller::shared_state::MnemonicState::NotStored => {
MnemonicState::NotStored
}
nym_vpn_account_controller::shared_state::MnemonicState::Stored => {
nym_vpn_account_controller::shared_state::MnemonicState::Stored { .. } => {
MnemonicState::Stored
}
}
Expand All @@ -677,11 +712,13 @@ impl From<nym_vpn_account_controller::shared_state::AccountState> for AccountSta
nym_vpn_account_controller::shared_state::AccountState::NotRegistered => {
AccountState::NotRegistered
}
nym_vpn_account_controller::shared_state::AccountState::Inactive => {
nym_vpn_account_controller::shared_state::AccountState::Inactive { .. } => {
AccountState::Inactive
}
nym_vpn_account_controller::shared_state::AccountState::Active => AccountState::Active,
nym_vpn_account_controller::shared_state::AccountState::DeleteMe => {
nym_vpn_account_controller::shared_state::AccountState::Active { .. } => {
AccountState::Active
}
nym_vpn_account_controller::shared_state::AccountState::DeleteMe { .. } => {
AccountState::DeleteMe
}
}
Expand Down Expand Up @@ -739,3 +776,37 @@ impl From<nym_vpn_account_controller::shared_state::DeviceState> for DeviceState
}
}
}

#[derive(uniffi::Record, Clone, PartialEq)]
pub struct SystemMessage {
pub name: String,
pub message: String,
pub properties: HashMap<String, String>,
}

impl From<nym_vpn_network_config::SystemMessage> for SystemMessage {
fn from(value: nym_vpn_network_config::SystemMessage) -> Self {
SystemMessage {
name: value.name,
message: value.message,
properties: value.properties.into_inner(),
}
}
}

#[derive(uniffi::Record, Clone, PartialEq)]
pub struct AccountLinks {
pub sign_up: String,
pub sign_in: String,
pub account: String,
}

impl From<nym_vpn_network_config::ParsedAccountLinks> for AccountLinks {
fn from(value: nym_vpn_network_config::ParsedAccountLinks) -> Self {
AccountLinks {
sign_up: value.sign_up.to_string(),
sign_in: value.sign_in.to_string(),
account: value.account.to_string(),
}
}
}
2 changes: 2 additions & 0 deletions nym-vpn-core/crates/nym-vpn-network-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ nym-config.workspace = true
serde.workspace = true
serde_json.workspace = true
tempfile.workspace = true
time = { workspace = true, features = ["serde-human-readable"] }
tokio = { workspace = true, features = ["time", "macros"] }
tokio-util.workspace = true
tracing.workspace = true
url = { workspace = true, features = ["serde"] }
futures-util = "0.3"

[build-dependencies]
serde_json.workspace = true
3 changes: 3 additions & 0 deletions nym-vpn-core/crates/nym-vpn-network-config/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ fn default_mainnet_discovery() {
network_name: "{}".to_string(),
nym_api_url: "{}".parse().expect("Failed to parse NYM API URL"),
nym_vpn_api_url: "{}".parse().expect("Failed to parse NYM VPN API URL"),
account_management: Default::default(),
feature_flags: Default::default(),
system_messages: Default::default(),
}}
}}
}}
Expand Down
Loading

0 comments on commit 9173ecd

Please sign in to comment.