mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-08 16:23:12 +00:00
Merge pull request #228 from logos-blockchain/arjentix/wallet-basic-auth
Add basic auth to wallet
This commit is contained in:
commit
f6e2bcaad7
@ -30,16 +30,25 @@ use crate::{
|
|||||||
pub struct SequencerClient {
|
pub struct SequencerClient {
|
||||||
pub client: reqwest::Client,
|
pub client: reqwest::Client,
|
||||||
pub sequencer_addr: String,
|
pub sequencer_addr: String,
|
||||||
|
pub basic_auth: Option<(String, Option<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SequencerClient {
|
impl SequencerClient {
|
||||||
pub fn new(sequencer_addr: String) -> Result<Self> {
|
pub fn new(sequencer_addr: String) -> Result<Self> {
|
||||||
|
Self::new_with_auth(sequencer_addr, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_auth(
|
||||||
|
sequencer_addr: String,
|
||||||
|
basic_auth: Option<(String, Option<String>)>,
|
||||||
|
) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: Client::builder()
|
client: Client::builder()
|
||||||
//Add more fiedls if needed
|
//Add more fiedls if needed
|
||||||
.timeout(std::time::Duration::from_secs(60))
|
.timeout(std::time::Duration::from_secs(60))
|
||||||
.build()?,
|
.build()?,
|
||||||
sequencer_addr,
|
sequencer_addr,
|
||||||
|
basic_auth,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,13 +60,16 @@ impl SequencerClient {
|
|||||||
let request =
|
let request =
|
||||||
rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload);
|
rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload);
|
||||||
|
|
||||||
let call_builder = self.client.post(&self.sequencer_addr);
|
let mut call_builder = self.client.post(&self.sequencer_addr);
|
||||||
|
|
||||||
|
if let Some((username, password)) = &self.basic_auth {
|
||||||
|
call_builder = call_builder.basic_auth(username, password.as_deref());
|
||||||
|
}
|
||||||
|
|
||||||
let call_res = call_builder.json(&request).send().await?;
|
let call_res = call_builder.json(&request).send().await?;
|
||||||
|
|
||||||
let response_vall = call_res.json::<Value>().await?;
|
let response_vall = call_res.json::<Value>().await?;
|
||||||
|
|
||||||
// TODO: Actually why we need separation of `result` and `error` in rpc response?
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct SequencerRpcResponse {
|
pub struct SequencerRpcResponse {
|
||||||
|
|||||||
@ -263,6 +263,7 @@ mod tests {
|
|||||||
seq_poll_max_retries: 10,
|
seq_poll_max_retries: 10,
|
||||||
seq_block_poll_max_amount: 100,
|
seq_block_poll_max_amount: 100,
|
||||||
initial_accounts: create_initial_accounts(),
|
initial_accounts: create_initial_accounts(),
|
||||||
|
basic_auth: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,13 @@ impl WalletSubcommand for ConfigSubcommand {
|
|||||||
"initial_accounts" => {
|
"initial_accounts" => {
|
||||||
println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts);
|
println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts);
|
||||||
}
|
}
|
||||||
|
"basic_auth" => {
|
||||||
|
if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth {
|
||||||
|
println!("{basic_auth}");
|
||||||
|
} else {
|
||||||
|
println!("Not set");
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unknown field");
|
println!("Unknown field");
|
||||||
}
|
}
|
||||||
@ -99,6 +106,9 @@ impl WalletSubcommand for ConfigSubcommand {
|
|||||||
wallet_core.storage.wallet_config.seq_block_poll_max_amount =
|
wallet_core.storage.wallet_config.seq_block_poll_max_amount =
|
||||||
value.parse()?;
|
value.parse()?;
|
||||||
}
|
}
|
||||||
|
"basic_auth" => {
|
||||||
|
wallet_core.storage.wallet_config.basic_auth = Some(value.parse()?);
|
||||||
|
}
|
||||||
"initial_accounts" => {
|
"initial_accounts" => {
|
||||||
anyhow::bail!("Setting this field from wallet is not supported");
|
anyhow::bail!("Setting this field from wallet is not supported");
|
||||||
}
|
}
|
||||||
@ -141,6 +151,9 @@ impl WalletSubcommand for ConfigSubcommand {
|
|||||||
"initial_accounts" => {
|
"initial_accounts" => {
|
||||||
println!("List of initial accounts' keys(both public and private)");
|
println!("List of initial accounts' keys(both public and private)");
|
||||||
}
|
}
|
||||||
|
"basic_auth" => {
|
||||||
|
println!("Basic authentication credentials for sequencer HTTP requests");
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unknown field");
|
println!("Unknown field");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use crate::{
|
|||||||
token::TokenProgramAgnosticSubcommand,
|
token::TokenProgramAgnosticSubcommand,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
helperfunctions::fetch_config,
|
helperfunctions::{fetch_config, merge_auth_config},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
@ -69,7 +69,7 @@ pub enum OverCommand {
|
|||||||
|
|
||||||
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
/// To execute commands, env var NSSA_WALLET_HOME_DIR must be set into directory with config
|
||||||
///
|
///
|
||||||
/// All account adresses must be valid 32 byte base58 strings.
|
/// All account addresses must be valid 32 byte base58 strings.
|
||||||
///
|
///
|
||||||
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
|
/// All account account_ids must be provided as {privacy_prefix}/{account_id},
|
||||||
/// where valid options for `privacy_prefix` is `Public` and `Private`
|
/// where valid options for `privacy_prefix` is `Public` and `Private`
|
||||||
@ -79,6 +79,9 @@ pub struct Args {
|
|||||||
/// Continious run flag
|
/// Continious run flag
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub continuous_run: bool,
|
pub continuous_run: bool,
|
||||||
|
/// Basic authentication in the format `user` or `user:password`
|
||||||
|
#[arg(long)]
|
||||||
|
pub auth: Option<String>,
|
||||||
/// Wallet command
|
/// Wallet command
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<OverCommand>,
|
pub command: Option<OverCommand>,
|
||||||
@ -94,7 +97,15 @@ pub enum SubcommandReturnValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
|
pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValue> {
|
||||||
|
execute_subcommand_with_auth(command, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_subcommand_with_auth(
|
||||||
|
command: Command,
|
||||||
|
auth: Option<String>,
|
||||||
|
) -> Result<SubcommandReturnValue> {
|
||||||
let wallet_config = fetch_config().await?;
|
let wallet_config = fetch_config().await?;
|
||||||
|
let wallet_config = merge_auth_config(wallet_config, auth)?;
|
||||||
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
|
let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?;
|
||||||
|
|
||||||
let subcommand_ret = match command {
|
let subcommand_ret = match command {
|
||||||
@ -160,7 +171,11 @@ pub async fn execute_subcommand(command: Command) -> Result<SubcommandReturnValu
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_continuous_run() -> Result<()> {
|
pub async fn execute_continuous_run() -> Result<()> {
|
||||||
|
execute_continuous_run_with_auth(None).await
|
||||||
|
}
|
||||||
|
pub async fn execute_continuous_run_with_auth(auth: Option<String>) -> Result<()> {
|
||||||
let config = fetch_config().await?;
|
let config = fetch_config().await?;
|
||||||
|
let config = merge_auth_config(config, auth)?;
|
||||||
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
|
let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -179,7 +194,12 @@ pub async fn execute_continuous_run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_setup(password: String) -> Result<()> {
|
pub async fn execute_setup(password: String) -> Result<()> {
|
||||||
|
execute_setup_with_auth(password, None).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_setup_with_auth(password: String, auth: Option<String>) -> Result<()> {
|
||||||
let config = fetch_config().await?;
|
let config = fetch_config().await?;
|
||||||
|
let config = merge_auth_config(config, auth)?;
|
||||||
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
|
let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?;
|
||||||
|
|
||||||
wallet_core.store_persistent_data().await?;
|
wallet_core.store_persistent_data().await?;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use key_protocol::key_management::{
|
use key_protocol::key_management::{
|
||||||
KeyChain,
|
KeyChain,
|
||||||
key_tree::{
|
key_tree::{
|
||||||
@ -6,6 +8,49 @@ use key_protocol::key_management::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BasicAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for BasicAuth {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.username)?;
|
||||||
|
if let Some(password) = &self.password {
|
||||||
|
write!(f, ":{password}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for BasicAuth {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parse = || {
|
||||||
|
let mut parts = s.splitn(2, ':');
|
||||||
|
let username = parts.next()?;
|
||||||
|
let password = parts.next().filter(|p| !p.is_empty());
|
||||||
|
if parts.next().is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((username, password))
|
||||||
|
};
|
||||||
|
|
||||||
|
let (username, password) = parse().ok_or_else(|| {
|
||||||
|
anyhow::anyhow!("Invalid auth format. Expected 'user' or 'user:password'")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
username: username.to_string(),
|
||||||
|
password: password.map(|p| p.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct InitialAccountDataPublic {
|
pub struct InitialAccountDataPublic {
|
||||||
pub account_id: String,
|
pub account_id: String,
|
||||||
@ -143,6 +188,8 @@ pub struct WalletConfig {
|
|||||||
pub seq_block_poll_max_amount: u64,
|
pub seq_block_poll_max_amount: u64,
|
||||||
/// Initial accounts for wallet
|
/// Initial accounts for wallet
|
||||||
pub initial_accounts: Vec<InitialAccountData>,
|
pub initial_accounts: Vec<InitialAccountData>,
|
||||||
|
/// Basic authentication credentials
|
||||||
|
pub basic_auth: Option<BasicAuth>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WalletConfig {
|
impl Default for WalletConfig {
|
||||||
@ -154,6 +201,7 @@ impl Default for WalletConfig {
|
|||||||
seq_tx_poll_max_blocks: 5,
|
seq_tx_poll_max_blocks: 5,
|
||||||
seq_poll_max_retries: 5,
|
seq_poll_max_retries: 5,
|
||||||
seq_block_poll_max_amount: 100,
|
seq_block_poll_max_amount: 100,
|
||||||
|
basic_auth: None,
|
||||||
initial_accounts: {
|
initial_accounts: {
|
||||||
let init_acc_json = r#"
|
let init_acc_json = r#"
|
||||||
[
|
[
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|||||||
use crate::{
|
use crate::{
|
||||||
HOME_DIR_ENV_VAR,
|
HOME_DIR_ENV_VAR,
|
||||||
config::{
|
config::{
|
||||||
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
||||||
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
|
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -89,6 +89,23 @@ pub async fn fetch_config() -> Result<WalletConfig> {
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse CLI auth string and merge with config auth, prioritizing CLI
|
||||||
|
pub fn merge_auth_config(
|
||||||
|
mut config: WalletConfig,
|
||||||
|
cli_auth: Option<String>,
|
||||||
|
) -> Result<WalletConfig> {
|
||||||
|
if let Some(auth_str) = cli_auth {
|
||||||
|
let cli_auth_config: BasicAuth = auth_str.parse()?;
|
||||||
|
|
||||||
|
if config.basic_auth.is_some() {
|
||||||
|
println!("Warning: CLI auth argument takes precedence over config basic-auth");
|
||||||
|
}
|
||||||
|
|
||||||
|
config.basic_auth = Some(cli_auth_config);
|
||||||
|
}
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch data stored at home
|
/// Fetch data stored at home
|
||||||
///
|
///
|
||||||
/// File must be created through setup beforehand.
|
/// File must be created through setup beforehand.
|
||||||
|
|||||||
@ -47,7 +47,14 @@ pub struct WalletCore {
|
|||||||
|
|
||||||
impl WalletCore {
|
impl WalletCore {
|
||||||
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
|
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
|
||||||
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
let basic_auth = config
|
||||||
|
.basic_auth
|
||||||
|
.as_ref()
|
||||||
|
.map(|auth| (auth.username.clone(), auth.password.clone()));
|
||||||
|
let client = Arc::new(SequencerClient::new_with_auth(
|
||||||
|
config.sequencer_addr.clone(),
|
||||||
|
basic_auth,
|
||||||
|
)?);
|
||||||
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
||||||
|
|
||||||
let PersistentStorage {
|
let PersistentStorage {
|
||||||
@ -69,7 +76,14 @@ impl WalletCore {
|
|||||||
config: WalletConfig,
|
config: WalletConfig,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
|
let basic_auth = config
|
||||||
|
.basic_auth
|
||||||
|
.as_ref()
|
||||||
|
.map(|auth| (auth.username.clone(), auth.password.clone()));
|
||||||
|
let client = Arc::new(SequencerClient::new_with_auth(
|
||||||
|
config.sequencer_addr.clone(),
|
||||||
|
basic_auth,
|
||||||
|
)?);
|
||||||
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
let tx_poller = TxPoller::new(config.clone(), client.clone());
|
||||||
|
|
||||||
let storage = WalletChainStore::new_storage(config, password)?;
|
let storage = WalletChainStore::new_storage(config, password)?;
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{CommandFactory as _, Parser as _};
|
use clap::{CommandFactory as _, Parser as _};
|
||||||
use tokio::runtime::Builder;
|
use tokio::runtime::Builder;
|
||||||
use wallet::cli::{Args, OverCommand, execute_continuous_run, execute_setup, execute_subcommand};
|
use wallet::cli::{
|
||||||
|
Args, OverCommand, execute_continuous_run_with_auth, execute_setup_with_auth,
|
||||||
|
execute_subcommand_with_auth,
|
||||||
|
};
|
||||||
|
|
||||||
pub const NUM_THREADS: usize = 2;
|
pub const NUM_THREADS: usize = 2;
|
||||||
|
|
||||||
@ -10,7 +13,6 @@ pub const NUM_THREADS: usize = 2;
|
|||||||
// file path?
|
// file path?
|
||||||
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
|
// TODO #172: Why it requires config as env var while sequencer_runner accepts as
|
||||||
// argument?
|
// argument?
|
||||||
// TODO #171: Running pinata doesn't give output about transaction hash and etc.
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let runtime = Builder::new_multi_thread()
|
let runtime = Builder::new_multi_thread()
|
||||||
.worker_threads(NUM_THREADS)
|
.worker_threads(NUM_THREADS)
|
||||||
@ -26,13 +28,15 @@ fn main() -> Result<()> {
|
|||||||
if let Some(over_command) = args.command {
|
if let Some(over_command) = args.command {
|
||||||
match over_command {
|
match over_command {
|
||||||
OverCommand::Command(command) => {
|
OverCommand::Command(command) => {
|
||||||
let _output = execute_subcommand(command).await?;
|
let _output = execute_subcommand_with_auth(command, args.auth).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
OverCommand::Setup { password } => execute_setup(password).await,
|
OverCommand::Setup { password } => {
|
||||||
|
execute_setup_with_auth(password, args.auth).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if args.continuous_run {
|
} else if args.continuous_run {
|
||||||
execute_continuous_run().await
|
execute_continuous_run_with_auth(args.auth).await
|
||||||
} else {
|
} else {
|
||||||
let help = Args::command().render_long_help();
|
let help = Args::command().render_long_help();
|
||||||
println!("{help}");
|
println!("{help}");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user