Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nym-vpn-app/src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pub enum ErrorKey {
MixnetNoBandwidth,
// Some specific account management errors
AccountInvalidMnemonic,
AccountInvalidPrivateKey,
NoAccountStored,
NoDeviceStored,
ExistingAccount,
Expand Down
5 changes: 5 additions & 0 deletions nym-vpn-app/src-tauri/src/vpnd/account_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ impl From<lib::AccountCommandError> for BackendError {
ErrorKey::AccountInvalidMnemonic,
format!("invalid mnemonic: {e}"),
),
lib::AccountCommandError::InvalidPrivateKey(e) => BackendError::with_detail(
"invalid private key",
ErrorKey::AccountInvalidPrivateKey,
format!("invalid private key: {e}"),
),
lib::AccountCommandError::NyxdConnectionFailure(e) => {
BackendError::internal_with_detail("failed to connect to nyxd", e)
}
Expand Down
6 changes: 4 additions & 2 deletions nym-vpn-app/src-tauri/src/vpnd/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use super::{

use anyhow::Result;
use lib::UserAgent;
use nym_vpn_lib_types as lib;
use nym_vpn_lib_types::{self as lib};
use nym_vpn_proto::rpc_client::RpcClient;
use once_cell::sync::Lazy;
use std::{
Expand Down Expand Up @@ -390,7 +390,9 @@ impl VpndClient {
let mut vpnd = self.vpnd().await?;

let response = vpnd
.store_account(lib::StoreAccountRequest::Vpn { mnemonic })
.store_account(lib::StoreAccountRequest::Vpn {
secret: lib::LoginSecret::Mnemonic(mnemonic),
})
.await
.map_err(VpndError::RpcClient)
.inspect_err(|e| {
Expand Down
1 change: 1 addition & 0 deletions nym-vpn-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add custom DNS setting for mobile platforms (https://github.com/nymtech/nym-vpn-client/pull/4106)
- Login with private key in addition to mnemonic (https://github.com/nymtech/nym-vpn-client/pull/4117)

### Fixed

Expand Down
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.

3 changes: 3 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib-types/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum AccountCommandError {

#[error("invalid mnemonic: {0}")]
InvalidMnemonic(String),

#[error("invalid private key: {0}")]
InvalidPrivateKey(String),
}

impl AccountCommandError {
Expand Down
2 changes: 1 addition & 1 deletion nym-vpn-core/crates/nym-vpn-lib-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub use network::{
};
pub use rpc_requests::{
AccountBalanceResponse, AccountCommandResponse, Coin, DecentralisedObtainTicketbooksRequest,
ListGatewaysOptions, StoreAccountRequest,
ListGatewaysOptions, LoginSecret, StoreAccountRequest,
};
pub use service::{TargetState, VpnServiceConfig, VpnServiceInfo};
pub use socks5::{HttpRpcSettings, Socks5Settings, Socks5State, Socks5Status};
Expand Down
19 changes: 17 additions & 2 deletions nym-vpn-core/crates/nym-vpn-lib-types/src/rpc_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@ pub struct ListGatewaysOptions {
pub user_agent: Option<UserAgent>,
}

#[derive(zeroize::Zeroize)]
#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Enum))]
#[cfg_attr(
feature = "typescript-bindings",
derive(TS),
ts(export),
ts(export_to = "bindings.ts")
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "typescript-bindings", serde(rename_all = "camelCase"))]
pub enum LoginSecret {
Mnemonic(String),
PrivateKeyHex(String),
}

#[derive(zeroize::Zeroize)]
#[cfg_attr(feature = "uniffi-bindings", derive(uniffi::Enum))]
pub enum StoreAccountRequest {
Vpn { mnemonic: String },
Decentralised { mnemonic: String },
Vpn { secret: LoginSecret },
Decentralised { secret: LoginSecret },
}

impl std::fmt::Debug for StoreAccountRequest {
Expand Down
1 change: 1 addition & 0 deletions nym-vpn-core/crates/nym-vpn-lib-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ workspace = true

[dependencies]
async-trait.workspace = true
hex.workspace = true
ipnetwork.workspace = true
itertools.workspace = true
lazy_static.workspace = true
Expand Down
28 changes: 21 additions & 7 deletions nym-vpn-core/crates/nym-vpn-lib-uniffi/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use nym_offline_monitor::ConnectivityHandle;
use nym_vpn_account_controller::{AccountCommandSender, AccountStateReceiver, NyxdClient};
use nym_vpn_api_client::types::{Platform, VpnAccount};
use nym_vpn_lib::{new_user_agent, storage::VpnClientOnDiskStorage};
use nym_vpn_lib_types::{AccountControllerState, RegisterAccountResponse};
use nym_vpn_lib_types::{AccountControllerState, LoginSecret, RegisterAccountResponse};
use nym_vpn_network_config::Network;
use nym_vpn_store::{
account::Mnemonic,
Expand Down Expand Up @@ -198,14 +198,24 @@ pub(super) async fn update_account_state() -> Result<(), VpnError> {
.map_err(VpnError::from)
}

async fn parse_mnemonic(mnemonic: &str) -> Result<Mnemonic, VpnError> {
Mnemonic::parse(mnemonic).map_err(|err| VpnError::InvalidMnemonic {
fn parse_secret(secret: &LoginSecret) -> Result<Mnemonic, VpnError> {
let ret = match secret {
LoginSecret::Mnemonic(mnemonic) => Mnemonic::parse(mnemonic),
LoginSecret::PrivateKeyHex(private_key_hex) => {
let key_bytes =
hex::decode(private_key_hex).map_err(|err| VpnError::InvalidPrivateKey {
details: err.to_string(),
})?;
Mnemonic::from_entropy(&key_bytes)
}
};
ret.map_err(|err| VpnError::InvalidMnemonic {
details: err.to_string(),
})
}

pub(super) async fn login(mnemonic: &str) -> Result<(), VpnError> {
let mnemonic = parse_mnemonic(mnemonic).await?;
pub(super) async fn login(secret: &LoginSecret) -> Result<(), VpnError> {
let mnemonic = parse_secret(secret)?;
get_command_sender()
.await?
.store_account(mnemonic.into())
Expand Down Expand Up @@ -313,15 +323,19 @@ pub(crate) mod raw {
Ok(VpnClientOnDiskStorage::new(path))
}

pub(crate) async fn login_raw(mnemonic: &str, path: &str) -> Result<(), VpnError> {
let mnemonic = parse_mnemonic(mnemonic).await?;
async fn login_inner(mnemonic: Mnemonic, path: &str) -> Result<(), VpnError> {
get_account_by_mnemonic_raw(mnemonic.clone()).await?;
let storage = setup_account_storage(path).await?;
storage.store_account(mnemonic.into()).await?;
storage.init_keys(None).await?;
Ok(())
}

pub(crate) async fn login_raw(secret: &LoginSecret, path: &str) -> Result<(), VpnError> {
let mnemonic = parse_secret(secret)?;
login_inner(mnemonic, path).await
}

pub(crate) async fn create_account_raw(path: &str) -> Result<(), VpnError> {
let (_, mnemonic) = VpnAccount::generate_new().map_err(VpnError::internal)?;
let storage = setup_account_storage(path).await?;
Expand Down
4 changes: 4 additions & 0 deletions nym-vpn-core/crates/nym-vpn-lib-uniffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub enum VpnError {
#[error("failed to parse mnemonic with error: {details}")]
InvalidMnemonic { details: String },

#[error("failed to parse private key with error: {details}")]
InvalidPrivateKey { details: String },

#[error("invalid account storage path: {details}")]
InvalidAccountStoragePath { details: String },

Expand Down Expand Up @@ -120,6 +123,7 @@ impl From<AccountCommandError> for VpnError {
},
AccountCommandError::ExistingAccount => Self::ExistingAccount,
AccountCommandError::InvalidMnemonic(details) => Self::InvalidMnemonic { details },
AccountCommandError::InvalidPrivateKey(details) => Self::InvalidPrivateKey { details },
AccountCommandError::NyxdConnectionFailure(details) => {
Self::NyxdConnectionFailure { details }
}
Expand Down
10 changes: 5 additions & 5 deletions nym-vpn-core/crates/nym-vpn-lib-uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ use sentry::ClientInitGuard;
use tokio::{runtime::Runtime, sync::Mutex};

use nym_vpn_lib_types::{
AccountControllerState, EntryPoint, ExitPoint, Gateway, GatewayType, Network,
AccountControllerState, EntryPoint, ExitPoint, Gateway, GatewayType, LoginSecret, Network,
NetworkCompatibility, ParsedAccountLinks, RegisterAccountResponse, SystemMessage, TunnelEvent,
UserAgent,
};
Expand Down Expand Up @@ -271,8 +271,8 @@ pub fn getAccountLinksRaw(
/// Import the account mnemonic
#[allow(non_snake_case)]
#[uniffi::export]
pub fn login(mnemonic: String) -> Result<(), VpnError> {
RUNTIME.block_on(account::login(&mnemonic))
pub fn login(secret: LoginSecret) -> Result<(), VpnError> {
RUNTIME.block_on(account::login(&secret))
}

/// Generate the account mnemonic locally and store it.
Expand All @@ -293,8 +293,8 @@ pub fn registerAccount(args: AccountRegistrationArgs) -> Result<RegisterAccountR
/// This is a version that can be called when the account controller is not running.
#[allow(non_snake_case)]
#[uniffi::export]
pub fn loginRaw(mnemonic: String, path: String) -> Result<(), VpnError> {
RUNTIME.block_on(account::raw::login_raw(&mnemonic, &path))
pub fn loginRaw(secret: LoginSecret, path: String) -> Result<(), VpnError> {
RUNTIME.block_on(account::raw::login_raw(&secret, &path))
}

/// Generate the account mnemonic locally and store it.
Expand Down
12 changes: 10 additions & 2 deletions nym-vpn-core/crates/nym-vpn-proto/proto/nym_vpn_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ message AccountCommandError {
bool no_device_stored = 6;
bool existing_account = 7;
bool offline = 8;
string invalid_private_key = 9;
string invalid_mnemonic = 10;
string nyxd_connection_failure = 11;
string nyxd_query_failure = 12;
Expand Down Expand Up @@ -674,13 +675,20 @@ message StoreAccountRequest {
}
}

message LoginSecret {
oneof login_type {
string mnemonic = 1;
string private_key_hex = 2;
}
}

// VPN variant with mnemonic
message VpnAccountStoreRequest {
string mnemonic = 1;
LoginSecret secret = 1;
}

message DecentralisedAccountStoreRequest {
string mnemonic = 1;
LoginSecret secret = 1;
}

message DecentralisedObtainTicketbooksRequest {
Expand Down
8 changes: 8 additions & 0 deletions nym-vpn-core/crates/nym-vpn-proto/src/conversions/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ impl TryFrom<proto::AccountCommandError> for AccountCommandError {
proto::account_command_error::ErrorDetail::InvalidMnemonic(message) => {
Self::InvalidMnemonic(message)
}
proto::account_command_error::ErrorDetail::InvalidPrivateKey(message) => {
Self::InvalidPrivateKey(message)
}
proto::account_command_error::ErrorDetail::NyxdConnectionFailure(err) => {
Self::NyxdConnectionFailure(err)
}
Expand Down Expand Up @@ -132,6 +135,11 @@ impl From<AccountCommandError> for proto::AccountCommandError {
err,
)),
},
AccountCommandError::InvalidPrivateKey(err) => proto::AccountCommandError {
error_detail: Some(
proto::account_command_error::ErrorDetail::InvalidPrivateKey(err),
),
},
AccountCommandError::NyxdConnectionFailure(err) => proto::AccountCommandError {
error_detail: Some(
proto::account_command_error::ErrorDetail::NyxdConnectionFailure(err),
Expand Down
100 changes: 86 additions & 14 deletions nym-vpn-core/crates/nym-vpn-proto/src/conversions/vpnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,47 @@ impl TryFrom<proto::StoreAccountRequest> for StoreAccountRequest {

Ok(match request {
proto::store_account_request::Request::VpnAccountStore(account) => {
StoreAccountRequest::Vpn {
mnemonic: account.mnemonic,
match account
.secret
.ok_or(ConversionError::NoValueSet(
"StoreAccountRequest.request.secret",
))?
.login_type
.ok_or(ConversionError::NoValueSet(
"StoreAccountRequest.request.secret.login_type",
))? {
proto::login_secret::LoginType::Mnemonic(mnemonic) => {
nym_vpn_lib_types::StoreAccountRequest::Vpn {
secret: nym_vpn_lib_types::LoginSecret::Mnemonic(mnemonic),
}
}
proto::login_secret::LoginType::PrivateKeyHex(private_key_hex) => {
nym_vpn_lib_types::StoreAccountRequest::Vpn {
secret: nym_vpn_lib_types::LoginSecret::PrivateKeyHex(private_key_hex),
}
}
}
}
proto::store_account_request::Request::DecentralisedAccountStore(account) => {
nym_vpn_lib_types::StoreAccountRequest::Decentralised {
mnemonic: account.mnemonic,
match account
.secret
.ok_or(ConversionError::NoValueSet(
"StoreAccountRequest.request.secret",
))?
.login_type
.ok_or(ConversionError::NoValueSet(
"StoreAccountRequest.request.secret.login_type",
))? {
proto::login_secret::LoginType::Mnemonic(mnemonic) => {
nym_vpn_lib_types::StoreAccountRequest::Decentralised {
secret: nym_vpn_lib_types::LoginSecret::Mnemonic(mnemonic),
}
}
proto::login_secret::LoginType::PrivateKeyHex(private_key_hex) => {
nym_vpn_lib_types::StoreAccountRequest::Decentralised {
secret: nym_vpn_lib_types::LoginSecret::PrivateKeyHex(private_key_hex),
}
}
}
}
})
Expand All @@ -491,16 +525,54 @@ impl TryFrom<proto::StoreAccountRequest> for StoreAccountRequest {
impl From<StoreAccountRequest> for proto::StoreAccountRequest {
fn from(value: StoreAccountRequest) -> Self {
let request = match value {
StoreAccountRequest::Vpn { mnemonic } => {
proto::store_account_request::Request::VpnAccountStore(
proto::VpnAccountStoreRequest { mnemonic },
)
}
nym_vpn_lib_types::StoreAccountRequest::Decentralised { mnemonic } => {
proto::store_account_request::Request::DecentralisedAccountStore(
proto::DecentralisedAccountStoreRequest { mnemonic },
)
}
StoreAccountRequest::Vpn { secret } => match secret {
nym_vpn_lib_types::LoginSecret::Mnemonic(mnemonic) => {
proto::store_account_request::Request::VpnAccountStore(
proto::VpnAccountStoreRequest {
secret: Some(proto::LoginSecret {
login_type: Some(proto::login_secret::LoginType::Mnemonic(
mnemonic,
)),
}),
},
)
}
nym_vpn_lib_types::LoginSecret::PrivateKeyHex(private_key_hex) => {
proto::store_account_request::Request::VpnAccountStore(
proto::VpnAccountStoreRequest {
secret: Some(proto::LoginSecret {
login_type: Some(proto::login_secret::LoginType::PrivateKeyHex(
private_key_hex,
)),
}),
},
)
}
},
nym_vpn_lib_types::StoreAccountRequest::Decentralised { secret } => match secret {
nym_vpn_lib_types::LoginSecret::Mnemonic(mnemonic) => {
proto::store_account_request::Request::DecentralisedAccountStore(
proto::DecentralisedAccountStoreRequest {
secret: Some(proto::LoginSecret {
login_type: Some(proto::login_secret::LoginType::Mnemonic(
mnemonic,
)),
}),
},
)
}
nym_vpn_lib_types::LoginSecret::PrivateKeyHex(private_key_hex) => {
proto::store_account_request::Request::DecentralisedAccountStore(
proto::DecentralisedAccountStoreRequest {
secret: Some(proto::LoginSecret {
login_type: Some(proto::login_secret::LoginType::PrivateKeyHex(
private_key_hex,
)),
}),
},
)
}
},
};

proto::StoreAccountRequest {
Expand Down
Loading
Loading