taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 1a6b822592fb2b8879e1afa23fa41204ad65b1a2
parent 9bcaedd00e7f52bd798944896801322b3920f803
Author: Antoine A <>
Date:   Thu,  9 Jan 2025 12:13:58 +0100

magnet-bank: idempotent setup

Diffstat:
MCargo.lock | 64+++++++++++++++++++++++++++++++++++++---------------------------
Mwire-gateway/magnet-bank/Cargo.toml | 1+
Mwire-gateway/magnet-bank/src/keys.rs | 82++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mwire-gateway/magnet-bank/src/magnet.rs | 4++--
Mwire-gateway/magnet-bank/src/magnet/error.rs | 6+++---
Mwire-gateway/magnet-bank/src/main.rs | 3++-
6 files changed, 84 insertions(+), 76 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -367,9 +367,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "9560b07a799281c7e0958b9296854d6fafd4c5f31444a7e5bb1ad6dde5ccf1bd" dependencies = [ "clap_builder", "clap_derive", @@ -377,9 +377,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "874e0dd3eb68bf99058751ac9712f622e61e6f393a94f7128fa26e3f02f5c7cd" dependencies = [ "anstream", "anstyle", @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -1539,9 +1539,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "listenfd" @@ -1587,6 +1587,7 @@ dependencies = [ "hmac", "jiff", "p256", + "passterm", "percent-encoding", "rand_core", "reqwest", @@ -1596,7 +1597,7 @@ dependencies = [ "sha1", "spki", "taler-common", - "thiserror 2.0.9", + "thiserror 2.0.10", "tokio", "tracing", "tracing-subscriber", @@ -1862,6 +1863,15 @@ dependencies = [ ] [[package]] +name = "passterm" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "150ca2316c7813c688677784f20bb0a9efab639415ae1961869863ee99a81e51" +dependencies = [ + "libc", +] + +[[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2245,9 +2255,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" dependencies = [ "bitflags", "errno", @@ -2603,7 +2613,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.9", + "thiserror 2.0.10", "tokio", "tokio-stream", "tracing", @@ -2684,7 +2694,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.9", + "thiserror 2.0.10", "tracing", "whoami", ] @@ -2721,7 +2731,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.9", + "thiserror 2.0.10", "tracing", "whoami", ] @@ -2846,7 +2856,7 @@ dependencies = [ "sqlx", "taler-common", "test-utils", - "thiserror 2.0.9", + "thiserror 2.0.10", "tokio", "tracing", "tracing-subscriber", @@ -2870,7 +2880,7 @@ dependencies = [ "serde_with", "sqlx", "tempfile", - "thiserror 2.0.9", + "thiserror 2.0.10", "tracing", "url", ] @@ -2912,11 +2922,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "a3ac7f54ca534db81081ef1c1e7f6ea8a3ef428d2fc069097c079443d24124d3" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.10", ] [[package]] @@ -2932,9 +2942,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "9e9465d30713b56a37ede7185763c3492a91be2f5fa68d958c44e41ab9248beb" dependencies = [ "proc-macro2", "quote", @@ -3019,9 +3029,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -3036,9 +3046,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", diff --git a/wire-gateway/magnet-bank/Cargo.toml b/wire-gateway/magnet-bank/Cargo.toml @@ -15,6 +15,7 @@ form_urlencoded = "1.2" percent-encoding = "2.3" serde_urlencoded = "0.7.1" anyhow = "1.0" +passterm = "2.0" taler-common = { path = "../../common/taler-common" } serde_json = { workspace = true, features = ["raw_value"] } jiff = { workspace = true, features = ["serde"] } diff --git a/wire-gateway/magnet-bank/src/keys.rs b/wire-gateway/magnet-bank/src/keys.rs @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::io::{BufRead, ErrorKind}; +use std::io::ErrorKind; use p256::ecdsa::SigningKey; use taler_common::{json_file, types::base32::Base32}; @@ -22,7 +22,10 @@ use tracing::info; use crate::{ config::MagnetConfig, - magnet::{AuthClient, Token, TokenAuth}, + magnet::{ + error::{ApiError, MagnetError}, + AuthClient, Token, TokenAuth, + }, }; #[derive(Default, Debug, serde::Deserialize, serde::Serialize)] @@ -53,21 +56,14 @@ pub async fn setup(cfg: MagnetConfig) -> Result<(), anyhow::Error> { // TODO how to do it in a generic way ? // TODO Ask MagnetBank if they could support out-of-band configuration println!( - "Login at {}?oauth_token={}\nEnter the result url>", + "Login at {}?oauth_token={}", client.join("/NetBankOAuth/authtoken.xhtml"), token_request.key ); - // TODO better prompting - let prompt = std::io::stdin() - .lock() - .lines() - .next() - .expect("Missing auth URL line") - .expect("Reading auth URL prompt"); - let auth_url = reqwest::Url::parse(&prompt).expect("Auth URL malformed"); + let auth_url = passterm::prompt_password_tty(Some("Enter the result URL>"))?; + let auth_url = reqwest::Url::parse(&auth_url)?; let token_auth: TokenAuth = - serde_urlencoded::from_str(auth_url.query().unwrap_or_default()) - .expect("Auth URL malformed"); + serde_urlencoded::from_str(auth_url.query().unwrap_or_default())?; assert_eq!(token_request.key, token_auth.oauth_token); let access_token = client.token_access(&token_request, &token_auth).await?; @@ -79,40 +75,40 @@ pub async fn setup(cfg: MagnetConfig) -> Result<(), anyhow::Error> { info!("Setup Strong Customer Authentication"); // TODO find a proper way to check if SCA is required without trigerring SCA.GLOBAL_FEATURE_NOT_ENABLED - let perform_sca = false; - if perform_sca { - let request = client.request_sms_code().await?; - println!( - "A SCA code have been sent through {} to {}\nEnter the code>", - request.channel, - request.sent_to.join(", ") - ); - // TODO better prompting - let prompt = std::io::stdin() - .lock() - .lines() - .next() - .expect("Missing SCA code line") - .expect("Reading SCA code prompt"); - client.perform_sca(&prompt).await?; + let request = client.request_sms_code().await?; + println!( + "A SCA code have been sent through {} to {}", + request.channel, + request.sent_to.join(", ") + ); + let sca_code = passterm::prompt_password_tty(Some("Enter the code>"))?; + if let Err(e) = client.perform_sca(&sca_code).await { + // Ignore error if SCA already performed + if !matches!(e, ApiError::Magnet(MagnetError { ref short_message, .. }) if short_message == "TOKEN_SCA_HITELESITETT") + { + return Err(e.into()); + } } info!("Setup public key"); // TODO find a proper way to check if a public key have been setup - let perform_public_key = false; - if perform_public_key { - // TODO use the beter from/to_array API in the next version of the crypto lib - let signing_key = match keys.signing_key { - Some(bytes) => SigningKey::from_slice(bytes.as_ref())?, - None => { - let rand = SigningKey::random(&mut rand_core::OsRng); - let array: [u8; 32] = rand.to_bytes().as_slice().try_into().unwrap(); - keys.signing_key = Some(Base32::from(array)); - json_file::persist(&cfg.keys_path, &keys)?; - rand - } - }; - client.setup_public_key(&signing_key).await?; + // TODO use the better from/to_array API in the next version of the crypto lib + let signing_key = match keys.signing_key { + Some(bytes) => SigningKey::from_slice(bytes.as_ref())?, + None => { + let rand = SigningKey::random(&mut rand_core::OsRng); + let array: [u8; 32] = rand.to_bytes().as_slice().try_into().unwrap(); + keys.signing_key = Some(Base32::from(array)); + json_file::persist(&cfg.keys_path, &keys)?; + rand + } + }; + if let Err(e) = client.upload_public_key(&signing_key).await { + // Ignore error if public key already uploaded + if !matches!(e, ApiError::Magnet(MagnetError { ref short_message, .. }) if short_message == "KULCS_MAR_HASZNALATBAN") + { + return Err(e.into()); + } } Ok(()) diff --git a/wire-gateway/magnet-bank/src/magnet.rs b/wire-gateway/magnet-bank/src/magnet.rs @@ -174,11 +174,11 @@ impl ApiClient { })) .oauth(&self.consumer, Some(&self.access), None) .await - .magnet_json() + .magnet_empty() .await } - pub async fn setup_public_key(&self, key: &SigningKey) -> ApiResult<Value> { + pub async fn upload_public_key(&self, key: &SigningKey) -> ApiResult<Value> { let public_key = PublicKey::from_secret_scalar(key.as_nonzero_scalar()); let der = public_key.to_public_key_der().unwrap().to_vec(); self.client diff --git a/wire-gateway/magnet-bank/src/magnet/error.rs b/wire-gateway/magnet-bank/src/magnet/error.rs @@ -31,11 +31,11 @@ pub struct MagnetHeader { #[error("{error_code} {short_message} '{long_message}'")] pub struct MagnetError { #[serde(alias = "errorCode")] - error_code: u16, + pub error_code: u16, #[serde(alias = "shortMessage")] - short_message: String, + pub short_message: String, #[serde(alias = "longMessage")] - long_message: String, + pub long_message: String, } #[derive(Error, Debug)] diff --git a/wire-gateway/magnet-bank/src/main.rs b/wire-gateway/magnet-bank/src/main.rs @@ -52,7 +52,7 @@ enum Command { fn setup(level: Option<tracing::Level>, app: impl Future<Output = Result<(), anyhow::Error>>) { // Setup logger let level = level.unwrap_or(Level::INFO); - FmtSubscriber::builder() + let guard = FmtSubscriber::builder() .with_max_level(level) .finish() .set_default(); @@ -69,6 +69,7 @@ fn setup(level: Option<tracing::Level>, app: impl Future<Output = Result<(), any error!("{}", err); std::process::exit(1); } + drop(guard); } async fn app(args: Args) -> Result<(), anyhow::Error> {