diff --git a/.changes/auth.md b/.changes/auth.md new file mode 100644 index 000000000..7aebb68e7 --- /dev/null +++ b/.changes/auth.md @@ -0,0 +1,5 @@ +--- +"nodejs-binding": patch +--- + +Change `Auth::{username, password}` to `Auth::basicAuthNamePwd`; \ No newline at end of file diff --git a/.changes/updateNodeAuth.md b/.changes/updateNodeAuth.md new file mode 100644 index 000000000..e38f06c0f --- /dev/null +++ b/.changes/updateNodeAuth.md @@ -0,0 +1,5 @@ +--- +"nodejs-binding": patch +--- + +Add `AccountManger::updateNodeAuth()`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9299e3fca..7166a4ee6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Default` to `FilterOptions`; - `ParticipationEventRegistrationOptions` type; +- `AccountManager::update_node_auth()`; +- `Message::UpdateNodeAuth`; ### Changed diff --git a/bindings/nodejs/lib/AccountManager.ts b/bindings/nodejs/lib/AccountManager.ts index 7d81fa894..2163e5a32 100644 --- a/bindings/nodejs/lib/AccountManager.ts +++ b/bindings/nodejs/lib/AccountManager.ts @@ -375,4 +375,14 @@ export class AccountManager { payload: { mnemonic }, }); } + + /** + * Update the authentication for the provided node. + */ + async updateNodeAuth(url: string, auth?: Auth): Promise { + await this.messageHandler.sendMessage({ + cmd: 'updateNodeAuth', + payload: { url, auth }, + }); + } } diff --git a/bindings/nodejs/lib/bindings.ts b/bindings/nodejs/lib/bindings.ts index c0bb46104..d2de5f340 100644 --- a/bindings/nodejs/lib/bindings.ts +++ b/bindings/nodejs/lib/bindings.ts @@ -5,13 +5,7 @@ import type { MessageHandler } from './MessageHandler'; // @ts-ignore: path is set to match runtime transpiled js path import addon = require('../../build/Release/index.node'); -const { - initLogger, - sendMessage, - messageHandlerNew, - listen, - destroy, -} = addon; +const { initLogger, sendMessage, messageHandlerNew, listen, destroy } = addon; const sendMessageAsync = ( message: string, diff --git a/bindings/nodejs/types/bridge/accountManager.ts b/bindings/nodejs/types/bridge/accountManager.ts index d033aa019..fe47e5f37 100644 --- a/bindings/nodejs/types/bridge/accountManager.ts +++ b/bindings/nodejs/types/bridge/accountManager.ts @@ -163,3 +163,8 @@ export type __VerifyMnemonicMessage__ = { cmd: 'verifyMnemonic'; payload: { mnemonic: string }; }; + +export type __UpdateNodeAuthMessage__ = { + cmd: 'updateNodeAuth'; + payload: { url: string; auth?: Auth }; +}; diff --git a/bindings/nodejs/types/bridge/index.ts b/bindings/nodejs/types/bridge/index.ts index 2a1e097c0..87108c156 100644 --- a/bindings/nodejs/types/bridge/index.ts +++ b/bindings/nodejs/types/bridge/index.ts @@ -84,6 +84,7 @@ import type { __StopBackgroundSyncMessage__, __StoreMnemonicMessage__, __VerifyMnemonicMessage__, + __UpdateNodeAuthMessage__, } from './accountManager'; export type __AccountMethod__ = @@ -179,4 +180,5 @@ export type __Message__ = | __StartBackgroundSyncMessage__ | __StopBackgroundSyncMessage__ | __StoreMnemonicMessage__ - | __VerifyMnemonicMessage__; + | __VerifyMnemonicMessage__ + | __UpdateNodeAuthMessage__; diff --git a/bindings/nodejs/types/network.ts b/bindings/nodejs/types/network.ts index a804af5e0..9b2df538e 100644 --- a/bindings/nodejs/types/network.ts +++ b/bindings/nodejs/types/network.ts @@ -9,8 +9,7 @@ export enum Network { /** Basic Auth or JWT */ export type Auth = { jwt?: string; - username?: string; - password?: string; + basicAuthNamePwd?: [string, string]; }; /** Information about the network and client */ diff --git a/documentation/docs/references/nodejs/api_ref.md b/documentation/docs/references/nodejs/api_ref.md index a5ccbb26e..d26197a7c 100644 --- a/documentation/docs/references/nodejs/api_ref.md +++ b/documentation/docs/references/nodejs/api_ref.md @@ -7,10 +7,6 @@ - [Account](classes/Account.md) - [AccountManager](classes/AccountManager.md) -### Functions - -- [initLogger](api_ref.md#initlogger) - ### Type Aliases - [AccountId](api_ref.md#accountid) @@ -90,24 +86,6 @@ - [LedgerDeviceType](enums/LedgerDeviceType.md) - [InclusionState](enums/InclusionState.md) -## Functions - -### initLogger - -▸ **initLogger**(`config`): `any` - -Function to create wallet logs - -#### Parameters - -| Name | Type | -| :------ | :------ | -| `config` | [`LoggerConfig`](interfaces/LoggerConfig.md) | - -#### Returns - -`any` - ## Type Aliases ### AccountId @@ -138,8 +116,7 @@ Basic Auth or JWT | Name | Type | | :------ | :------ | | `jwt?` | `string` | -| `username?` | `string` | -| `password?` | `string` | +| `basicAuthNamePwd?` | [`string`, `string`] | ___ diff --git a/documentation/docs/references/nodejs/classes/Account.md b/documentation/docs/references/nodejs/classes/Account.md index 8136bb9c3..0c180a834 100644 --- a/documentation/docs/references/nodejs/classes/Account.md +++ b/documentation/docs/references/nodejs/classes/Account.md @@ -48,6 +48,7 @@ The Account class. - [sendNativeTokens](Account.md#sendnativetokens) - [sendNft](Account.md#sendnft) - [sendOutputs](Account.md#sendoutputs) +- [setAlias](Account.md#setalias) - [signTransactionEssence](Account.md#signtransactionessence) - [submitAndStoreTransaction](Account.md#submitandstoretransaction) - [sync](Account.md#sync) @@ -888,6 +889,24 @@ The sent transaction. ___ +### setAlias + +▸ **setAlias**(`alias`): `Promise`<`void`\> + +Set the alias for the account + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `alias` | `string` | The account alias to set. | + +#### Returns + +`Promise`<`void`\> + +___ + ### signTransactionEssence ▸ **signTransactionEssence**(`preparedTransactionData`): `Promise`<[`SignedTransactionEssence`](../interfaces/SignedTransactionEssence.md)\> diff --git a/documentation/docs/references/nodejs/classes/AccountManager.md b/documentation/docs/references/nodejs/classes/AccountManager.md index b0bd60c1a..c5bdcafde 100644 --- a/documentation/docs/references/nodejs/classes/AccountManager.md +++ b/documentation/docs/references/nodejs/classes/AccountManager.md @@ -34,6 +34,7 @@ The AccountManager class. - [stopBackgroundSync](AccountManager.md#stopbackgroundsync) - [storeMnemonic](AccountManager.md#storemnemonic) - [verifyMnemonic](AccountManager.md#verifymnemonic) +- [updateNodeAuth](AccountManager.md#updatenodeauth) ## Methods @@ -503,3 +504,22 @@ Verify if a mnemonic is a valid BIP39 mnemonic. #### Returns `Promise`<`void`\> + +___ + +### updateNodeAuth + +▸ **updateNodeAuth**(`url`, `auth?`): `Promise`<`void`\> + +Update the authentication for the provided node. + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `url` | `string` | +| `auth?` | [`Auth`](../api_ref.md#auth) | + +#### Returns + +`Promise`<`void`\> diff --git a/src/account_manager/mod.rs b/src/account_manager/mod.rs index 84d77795d..8e1583819 100644 --- a/src/account_manager/mod.rs +++ b/src/account_manager/mod.rs @@ -9,7 +9,7 @@ use std::sync::{ Arc, }; -use iota_client::{secret::SecretManager, Client, NodeInfoWrapper}; +use iota_client::{secret::SecretManager, Client}; #[cfg(feature = "events")] use tokio::sync::Mutex; use tokio::sync::RwLock; @@ -122,55 +122,6 @@ impl AccountManager { self.secret_manager.clone() } - /// Sets the client options for all accounts and sets the new bech32_hrp for the addresses. - pub async fn set_client_options(&self, options: ClientOptions) -> crate::Result<()> { - log::debug!("[set_client_options]"); - - let mut client_options = self.client_options.write().await; - *client_options = options.clone(); - drop(client_options); - - let new_client = options.clone().finish()?; - - let mut accounts = self.accounts.write().await; - for account in accounts.iter_mut() { - account.update_account_with_new_client(new_client.clone()).await?; - } - - #[cfg(feature = "storage")] - { - // Update account manager data with new client options - let account_manager_builder = AccountManagerBuilder::from_account_manager(self) - .await - .with_client_options(options); - - self.storage_manager - .lock() - .await - .save_account_manager_data(&account_manager_builder) - .await?; - } - Ok(()) - } - - /// Get the used client options - pub async fn get_client_options(&self) -> ClientOptions { - self.client_options.read().await.clone() - } - - /// Get the node info - pub async fn get_node_info(&self) -> crate::Result { - let accounts = self.accounts.read().await; - - // Try to get the Client from the first account and only build the Client if we have no account - let node_info_wrapper = match &accounts.first() { - Some(account) => account.client.get_info().await?, - None => self.client_options.read().await.clone().finish()?.get_info().await?, - }; - - Ok(node_info_wrapper) - } - /// Get the balance of all accounts added together pub async fn balance(&self) -> crate::Result { let accounts = self.accounts.read().await; @@ -201,25 +152,6 @@ impl AccountManager { Ok(balance) } - /// Stop the background syncing of the accounts - pub async fn stop_background_syncing(&self) -> crate::Result<()> { - log::debug!("[stop_background_syncing]"); - // immediately return if not running - if self.background_syncing_status.load(Ordering::Relaxed) == 0 { - return Ok(()); - } - // send stop request - self.background_syncing_status.store(2, Ordering::Relaxed); - // wait until it stopped - while self.background_syncing_status.load(Ordering::Relaxed) != 0 { - #[cfg(target_family = "wasm")] - gloo_timers::future::TimeoutFuture::new(10).await; - #[cfg(not(target_family = "wasm"))] - tokio::time::sleep(std::time::Duration::from_millis(10)).await; - } - Ok(()) - } - /// Listen to wallet events, empty vec will listen to all events #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] diff --git a/src/account_manager/operations/background_syncing.rs b/src/account_manager/operations/background_syncing.rs index ea17fe710..332f6346d 100644 --- a/src/account_manager/operations/background_syncing.rs +++ b/src/account_manager/operations/background_syncing.rs @@ -72,4 +72,23 @@ impl AccountManager { }); Ok(()) } + + /// Stop the background syncing of the accounts + pub async fn stop_background_syncing(&self) -> crate::Result<()> { + log::debug!("[stop_background_syncing]"); + // immediately return if not running + if self.background_syncing_status.load(Ordering::Relaxed) == 0 { + return Ok(()); + } + // send stop request + self.background_syncing_status.store(2, Ordering::Relaxed); + // wait until it stopped + while self.background_syncing_status.load(Ordering::Relaxed) != 0 { + #[cfg(target_family = "wasm")] + gloo_timers::future::TimeoutFuture::new(10).await; + #[cfg(not(target_family = "wasm"))] + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + Ok(()) + } } diff --git a/src/account_manager/operations/client.rs b/src/account_manager/operations/client.rs new file mode 100644 index 000000000..7d58421fc --- /dev/null +++ b/src/account_manager/operations/client.rs @@ -0,0 +1,168 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashSet; + +use iota_client::{ + node_manager::node::{Node, NodeAuth, NodeDto}, + NodeInfoWrapper, Url, +}; + +use crate::{ + account_manager::{builder::AccountManagerBuilder, AccountManager}, + ClientOptions, +}; + +impl AccountManager { + /// Sets the client options for all accounts and sets the new bech32_hrp for the addresses. + pub async fn set_client_options(&self, options: ClientOptions) -> crate::Result<()> { + log::debug!("[set_client_options]"); + + let mut client_options = self.client_options.write().await; + *client_options = options.clone(); + drop(client_options); + + let new_client = options.clone().finish()?; + + let mut accounts = self.accounts.write().await; + for account in accounts.iter_mut() { + account.update_account_with_new_client(new_client.clone()).await?; + } + + #[cfg(feature = "storage")] + { + // Update account manager data with new client options + let account_manager_builder = AccountManagerBuilder::from_account_manager(self) + .await + .with_client_options(options); + + self.storage_manager + .lock() + .await + .save_account_manager_data(&account_manager_builder) + .await?; + } + Ok(()) + } + + /// Get the used client options. + pub async fn get_client_options(&self) -> ClientOptions { + self.client_options.read().await.clone() + } + + /// Get the node info. + pub async fn get_node_info(&self) -> crate::Result { + let accounts = self.accounts.read().await; + + // Try to get the Client from the first account and only build the Client if we have no account + let node_info_wrapper = match &accounts.first() { + Some(account) => account.client.get_info().await?, + None => self.client_options.read().await.clone().finish()?.get_info().await?, + }; + + Ok(node_info_wrapper) + } + + /// Update the authentication for a node. + pub async fn update_node_auth(&self, url: Url, auth: Option) -> crate::Result<()> { + log::debug!("[update_node_auth]"); + let mut client_options = self.client_options.write().await; + + if let Some(primary_node) = &client_options.node_manager_builder.primary_node { + let (node_url, disabled) = match &primary_node { + NodeDto::Url(node_url) => (node_url, false), + NodeDto::Node(node) => (&node.url, node.disabled), + }; + + if node_url == &url { + client_options.node_manager_builder.primary_node = Some(NodeDto::Node(Node { + url: url.clone(), + auth: auth.clone(), + disabled, + })); + } + } + + if let Some(primary_pow_node) = &client_options.node_manager_builder.primary_pow_node { + let (node_url, disabled) = match &primary_pow_node { + NodeDto::Url(node_url) => (node_url, false), + NodeDto::Node(node) => (&node.url, node.disabled), + }; + + if node_url == &url { + client_options.node_manager_builder.primary_pow_node = Some(NodeDto::Node(Node { + url: url.clone(), + auth: auth.clone(), + disabled, + })); + } + } + + if let Some(permanodes) = &client_options.node_manager_builder.permanodes { + let mut new_permanodes = HashSet::new(); + for node in permanodes.iter() { + let (node_url, disabled) = match &node { + NodeDto::Url(node_url) => (node_url, false), + NodeDto::Node(node) => (&node.url, node.disabled), + }; + + if node_url == &url { + new_permanodes.insert(NodeDto::Node(Node { + url: url.clone(), + auth: auth.clone(), + disabled, + })); + } else { + new_permanodes.insert(node.clone()); + } + } + client_options.node_manager_builder.permanodes = Some(new_permanodes); + } + + let mut new_nodes = HashSet::new(); + for node in client_options.node_manager_builder.nodes.iter() { + let (node_url, disabled) = match &node { + NodeDto::Url(node_url) => (node_url, false), + NodeDto::Node(node) => (&node.url, node.disabled), + }; + + if node_url == &url { + new_nodes.insert(NodeDto::Node(Node { + url: url.clone(), + auth: auth.clone(), + disabled, + })); + } else { + new_nodes.insert(node.clone()); + } + } + client_options.node_manager_builder.nodes = new_nodes; + + let new_client_options = client_options.clone(); + // Need to drop client_options here to prevent a deadlock + drop(client_options); + + #[cfg(feature = "storage")] + { + // Update account manager data with new client options + let account_manager_builder = AccountManagerBuilder::from_account_manager(self) + .await + .with_client_options(new_client_options.clone()); + + self.storage_manager + .lock() + .await + .save_account_manager_data(&account_manager_builder) + .await?; + } + + let new_client = new_client_options.finish()?; + + let mut accounts = self.accounts.write().await; + for account in accounts.iter_mut() { + account.update_account_with_new_client(new_client.clone()).await?; + } + + Ok(()) + } +} diff --git a/src/account_manager/operations/mod.rs b/src/account_manager/operations/mod.rs index bfd6481cb..094328b98 100644 --- a/src/account_manager/operations/mod.rs +++ b/src/account_manager/operations/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod account_recovery; pub(crate) mod address_generation; pub(crate) mod background_syncing; +pub(crate) mod client; pub(crate) mod get_account; #[cfg(feature = "ledger_nano")] pub(crate) mod ledger_nano; diff --git a/src/message_interface/message.rs b/src/message_interface/message.rs index 6552e1e80..6b41888f9 100644 --- a/src/message_interface/message.rs +++ b/src/message_interface/message.rs @@ -5,7 +5,6 @@ use std::fmt::{Debug, Formatter, Result}; #[cfg(feature = "stronghold")] use std::path::PathBuf; -use iota_client::{node_manager::node::NodeAuth, secret::GenerateAddressOptions}; use serde::{Deserialize, Serialize}; use super::account_method::AccountMethod; @@ -13,6 +12,7 @@ use super::account_method::AccountMethod; use crate::events::types::{WalletEvent, WalletEventType}; use crate::{ account::{operations::syncing::SyncOptions, types::AccountIdentifier}, + iota_client::{node_manager::node::NodeAuth, secret::GenerateAddressOptions, Url}, ClientOptions, }; @@ -216,6 +216,14 @@ pub enum Message { #[serde(rename = "eventTypes")] event_types: Vec, }, + /// Update the authentication for the provided node. + /// Expected response: [`Ok`](crate::message_interface::Response::Ok) + UpdateNodeAuth { + /// Node url + url: Url, + /// Authentication options + auth: Option, + }, } // Custom Debug implementation to not log secrets @@ -310,6 +318,7 @@ impl Debug for Message { } #[cfg(feature = "events")] Self::ClearListeners { event_types } => write!(f, "ClearListeners{{ event_types: {event_types:?} }}"), + Self::UpdateNodeAuth { url, auth: _ } => write!(f, "UpdateNodeAuth{{ url: {url}, auth: }}"), } } } diff --git a/src/message_interface/message_handler.rs b/src/message_interface/message_handler.rs index 98fa69c65..82822a663 100644 --- a/src/message_interface/message_handler.rs +++ b/src/message_interface/message_handler.rs @@ -355,6 +355,13 @@ impl WalletMessageHandler { }) .await } + Message::UpdateNodeAuth { url, auth } => { + convert_async_panics(|| async { + self.account_manager.update_node_auth(url, auth).await?; + Ok(Response::Ok(())) + }) + .await + } }; let response = match response { diff --git a/tests/account_manager.rs b/tests/account_manager.rs index 06abf8601..613a5edbd 100644 --- a/tests/account_manager.rs +++ b/tests/account_manager.rs @@ -17,7 +17,6 @@ use iota_wallet::{ ClientOptions, Result, }; -#[cfg(feature = "storage")] #[tokio::test] async fn update_client_options() -> Result<()> { let storage_path = "test-storage/update_client_options"; @@ -228,3 +227,30 @@ async fn account_manager_address_generation() -> Result<()> { common::tear_down(storage_path) } + +#[tokio::test] +async fn update_node_auth() -> Result<()> { + let storage_path = "test-storage/update_node_auth"; + common::setup(storage_path)?; + + let manager = common::make_manager(storage_path, None, Some(common::NODE_OTHER)).await?; + + let node_auth = iota_client::node_manager::node::NodeAuth { + jwt: Some("jwt".to_string()), + basic_auth_name_pwd: None, + }; + manager + .update_node_auth(Url::parse(common::NODE_OTHER).unwrap(), Some(node_auth.clone())) + .await?; + + let client_options = manager.get_client_options().await; + + let node = client_options.node_manager_builder.nodes.into_iter().next().unwrap(); + if let NodeDto::Node(node) = node { + assert_eq!(node.auth.expect("missing provided auth"), node_auth); + } else { + panic!("Wrong node dto"); + }; + + common::tear_down(storage_path) +}