commit 7797b39802f6254f87bd15ae1909396daea74113
parent 72832a7d9de80cc40a8307f0f66152d45ccfa77d
Author: Antoine A <>
Date: Fri, 17 Jan 2025 15:03:19 +0100
magnet-bank: setup reset and dev cmd
Diffstat:
9 files changed, 213 insertions(+), 39 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -304,9 +304,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.2.9"
+version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
+checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [
"shlex",
]
@@ -1392,9 +1392,9 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "jiff"
-version = "0.1.23"
+version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7597657ea66d53f6e926a67d4cc3d125c4b57fa662f2d007a5476307de948453"
+checksum = "d2bb0c2e28117985a4d90e3bc70092bc8f226f434c7ec7e23dd9ff99c5c5721a"
dependencies = [
"log",
"portable-atomic",
@@ -1498,6 +1498,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
+ "serde_path_to_error",
"serde_urlencoded",
"sha1",
"spki",
@@ -2973,9 +2974,9 @@ checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4"
[[package]]
name = "valuable"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
diff --git a/Cargo.toml b/Cargo.toml
@@ -14,6 +14,7 @@ debug = true
thiserror = "2.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
+serde_path_to_error = "0.1"
tokio = { version = "1.42", features = ["macros"] }
axum = "0.8"
sqlx = { version = "0.8", default-features = false }
diff --git a/wire-gateway/magnet-bank/Cargo.toml b/wire-gateway/magnet-bank/Cargo.toml
@@ -30,6 +30,7 @@ taler-common.workspace = true
taler-api.workspace = true
clap.workspace = true
serde.workspace = true
+serde_path_to_error.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
@@ -37,4 +38,4 @@ tokio.workspace = true
[dev-dependencies]
-test-utils.workspace = true
-\ No newline at end of file
+test-utils.workspace = true
diff --git a/wire-gateway/magnet-bank/src/dev.rs b/wire-gateway/magnet-bank/src/dev.rs
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+use sqlx::PgPool;
+use taler_common::config::Config;
+
+use crate::{
+ config::{DbConfig, MagnetConfig},
+ keys,
+ magnet::AuthClient,
+};
+
+#[derive(clap::Subcommand, Debug)]
+pub enum DevCmd {
+ /// Print account info
+ Account,
+}
+
+pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> {
+ let db = DbConfig::parse(&cfg)?;
+ let pool = PgPool::connect_with(db.cfg).await?;
+ let cfg = MagnetConfig::parse(&cfg)?;
+ let keys = keys::load(&cfg)?;
+ let client = AuthClient::new(reqwest::Client::new(), cfg.api_url, cfg.consumer)
+ .upgrade(keys.access_token);
+ match cmd {
+ DevCmd::Account => {
+ let res = client.list_accounts().await?;
+ dbg!(res);
+ }
+ }
+ Ok(())
+}
diff --git a/wire-gateway/magnet-bank/src/keys.rs b/wire-gateway/magnet-bank/src/keys.rs
@@ -29,18 +29,54 @@ use crate::{
};
#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
-pub struct MagnetKeys {
+struct KeysFile {
access_token: Option<Token>,
signing_key: Option<Base32<32>>,
}
-pub async fn setup(cfg: MagnetConfig) -> Result<(), anyhow::Error> {
+#[derive(Debug)]
+pub struct Keys {
+ pub access_token: Token,
+ pub signing_key: SigningKey,
+}
+
+pub fn load(cfg: &MagnetConfig) -> anyhow::Result<Keys> {
+ // Load JSON file
+ let file: KeysFile = match json_file::load(&cfg.keys_path) {
+ Ok(file) => file,
+ Err(e) => return Err(anyhow::anyhow!("Could not magnet keys: {e}")),
+ };
+
+ fn incomplete_err() -> anyhow::Error {
+ anyhow::anyhow!("Missing magnet keys, run 'magnet-bank setup' first")
+ }
+
+ // Check full
+ let access_token = file.access_token.ok_or_else(incomplete_err)?;
+ let signing_key = file.signing_key.ok_or_else(incomplete_err)?;
+
+ // Load signing key
+
+ let signing_key = SigningKey::from_slice(&*signing_key)?;
+
+ Ok(Keys {
+ access_token,
+ signing_key,
+ })
+}
+
+pub async fn setup(cfg: MagnetConfig, reset: bool) -> anyhow::Result<()> {
+ if reset {
+ if let Err(e) = std::fs::remove_file(&cfg.keys_path) {
+ if e.kind() != ErrorKind::NotFound {
+ Err(e)?;
+ }
+ }
+ }
let mut keys = match json_file::load(&cfg.keys_path) {
Ok(existing) => existing,
- Err(e) => match e.kind() {
- ErrorKind::NotFound => MagnetKeys::default(),
- _ => Err(e)?,
- },
+ Err(e) if e.kind() == ErrorKind::NotFound => KeysFile::default(),
+ Err(e) => Err(e)?,
};
let client = AuthClient::new(
diff --git a/wire-gateway/magnet-bank/src/lib.rs b/wire-gateway/magnet-bank/src/lib.rs
@@ -20,3 +20,4 @@ pub mod db;
pub mod keys;
pub mod magnet;
pub mod wire_gateway;
+pub mod dev;
+\ No newline at end of file
diff --git a/wire-gateway/magnet-bank/src/magnet.rs b/wire-gateway/magnet-bank/src/magnet.rs
@@ -19,6 +19,7 @@ use error::ApiResult;
use p256::{ecdsa::SigningKey, PublicKey};
use serde_json::{json, Value};
use spki::EncodePublicKey;
+use taler_common::types::amount;
use crate::magnet::{error::MagnetBuilder, oauth::OAuthBuilder};
@@ -80,6 +81,72 @@ pub struct ScaResult {
pub sent_to: Vec<String>,
}
+#[derive(serde::Deserialize, Debug)]
+pub struct Partner {
+ #[serde(rename = "megnevezes")]
+ pub name: String,
+ #[serde(rename = "kod")]
+ pub code: u64,
+ #[serde(rename = "adoszam")]
+ pub tax_number: Option<String>,
+ #[serde(rename = "ebUfallapot")]
+ pub status: String, // TODO enum
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct AccountType {
+ #[serde(rename = "kod")]
+ code: u64,
+ #[serde(rename = "megnevezes")]
+ name: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct Currency {
+ #[serde(rename = "jel")]
+ symbol: amount::Currency,
+ #[serde(rename = "megnevezes")]
+ name: String,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct Account {
+ #[serde(rename = "alapertelmezett")]
+ default: bool,
+ #[serde(rename = "bankszamlaTipus")]
+ ty: AccountType,
+ #[serde(rename = "deviza")]
+ currency: Currency,
+ #[serde(rename = "ibanSzamlaszam")]
+ iban: String,
+ #[serde(rename = "kod")]
+ code: u64,
+ #[serde(rename = "szamlaszam")]
+ number: String,
+ #[serde(rename = "tulajdonosKod")]
+ owner_code: u64,
+ #[serde(rename = "lakossagi")]
+ resident: bool,
+ #[serde(rename = "megnevezes")]
+ name: Option<String>,
+ partner: Partner,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct PartnerAccounts {
+ partner: Partner,
+ #[serde(rename = "bankszamlaList")]
+ bank_accounts: Vec<Account>,
+ #[serde(rename = "kertJogosultsag")]
+ requested_permission: u64,
+}
+
+#[derive(serde::Deserialize, Debug)]
+pub struct PartnerList {
+ #[serde(rename = "partnerSzamlaList")]
+ parteners: Vec<PartnerAccounts>,
+}
+
pub struct AuthClient {
client: reqwest::Client,
api_url: reqwest::Url,
@@ -191,4 +258,13 @@ impl ApiClient {
.magnet_json()
.await
}
+
+ pub async fn list_accounts(&self) -> ApiResult<PartnerList> {
+ self.client
+ .get(self.join("/RESTApi/resources/v2/partnerszamla/0"))
+ .oauth(&self.consumer, Some(&self.access), None)
+ .await
+ .magnet_json()
+ .await
+ }
}
diff --git a/wire-gateway/magnet-bank/src/magnet/error.rs b/wire-gateway/magnet-bank/src/magnet/error.rs
@@ -16,15 +16,24 @@
use reqwest::{header, Response, StatusCode};
use serde::{de::DeserializeOwned, Deserialize};
-use serde_json::value::RawValue;
use thiserror::Error;
use tracing::error;
#[derive(Deserialize, Debug)]
-pub struct MagnetHeader {
+pub struct MagnetResponse<T> {
timestamp: jiff::civil::DateTime,
- #[serde(alias = "errorCode")]
- error_code: Option<u16>,
+ #[serde(flatten)]
+ body: MagnetBody<T>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct Empty {}
+
+#[derive(Deserialize, Debug)]
+#[serde(untagged)]
+pub enum MagnetBody<T> {
+ Error(MagnetError),
+ Ok(T),
}
#[derive(Deserialize, Error, Debug)]
@@ -45,7 +54,7 @@ pub enum ApiError {
#[error("magnet {0}")]
Magnet(#[from] MagnetError),
#[error("JSON body: {0}")]
- Json(#[from] serde_json::Error),
+ Json(#[from] serde_path_to_error::Error<serde_json::Error>),
#[error("form body: {0}")]
Form(#[from] serde_urlencoded::de::Error),
#[error("status {0}")]
@@ -117,22 +126,16 @@ async fn error_handling(res: reqwest::Result<Response>) -> ApiResult<String> {
}
/** Parse magnet JSON response */
-async fn magnet_raw_json(res: reqwest::Result<Response>) -> ApiResult<Box<RawValue>> {
+async fn magnet_json<T: DeserializeOwned>(res: reqwest::Result<Response>) -> ApiResult<T> {
let body = error_handling(res).await?;
- let raw = RawValue::from_string(body).map_err(ApiError::Json)?;
- let header: MagnetHeader = serde_json::from_str(raw.get()).map_err(ApiError::Json)?;
- if header.error_code.is_some_and(|it| it != 200) {
- let error: MagnetError = serde_json::from_str(raw.get()).map_err(ApiError::Json)?;
- Err(ApiError::Magnet(error))
- } else {
- Ok(raw)
- }
-}
+ let deserializer = &mut serde_json::Deserializer::from_str(&body);
-/** Parse magnet JSON response into our own type */
-async fn magnet_json<T: DeserializeOwned>(response: reqwest::Result<Response>) -> ApiResult<T> {
- let raw = magnet_raw_json(response).await?;
- serde_json::from_str(raw.get()).map_err(ApiError::Json)
+ let body: MagnetResponse<T> =
+ serde_path_to_error::deserialize(deserializer).map_err(ApiError::Json)?;
+ match body.body {
+ MagnetBody::Error(e) => Err(ApiError::Magnet(e)),
+ MagnetBody::Ok(t) => Ok(t),
+ }
}
/** Parse magnet URL encoded response into our own type */
@@ -158,7 +161,7 @@ impl MagnetBuilder for reqwest::Result<Response> {
}
async fn magnet_empty(self) -> ApiResult<()> {
- magnet_raw_json(self).await?;
+ magnet_json::<Empty>(self).await?;
Ok(())
}
diff --git a/wire-gateway/magnet-bank/src/main.rs b/wire-gateway/magnet-bank/src/main.rs
@@ -19,7 +19,9 @@ use std::{future::Future, path::PathBuf, sync::Arc};
use clap::Parser;
use magnet_bank::{
config::{DbConfig, MagnetConfig, WireGatewayConfig},
- db, keys,
+ db,
+ dev::{self, DevCmd},
+ keys,
wire_gateway::MagnetWireGateway,
};
use sqlx::PgPool;
@@ -50,7 +52,10 @@ struct Args {
#[derive(clap::Subcommand, Debug)]
enum Command {
/// Setup magnet-bank auth token and account settings for Wire Gateway use
- Setup,
+ Setup {
+ #[clap(long, short)]
+ reset: bool,
+ },
/// Initialize magnet-bank database
Dbinit {
#[clap(long, short)]
@@ -58,6 +63,9 @@ enum Command {
},
/// Run magnet-bank HTTP server
Serve,
+ /// Hidden dev commands
+ #[command(subcommand, hide(true))]
+ Dev(DevCmd),
}
fn setup(level: Option<tracing::Level>, app: impl Future<Output = Result<(), anyhow::Error>>) {
@@ -83,15 +91,15 @@ fn setup(level: Option<tracing::Level>, app: impl Future<Output = Result<(), any
}
}
-async fn app(args: Args) -> Result<(), anyhow::Error> {
+async fn app(args: Args) -> anyhow::Result<()> {
let source = ConfigSource::new("magnet-bank", "magnet-bank", "magnet-bank");
let cfg = Config::from_file(source, args.config)?;
match args.cmd {
- Command::Setup => {
+ Command::Setup { reset } => {
let cfg = MagnetConfig::parse(&cfg)?;
- keys::setup(cfg).await?
+ keys::setup(cfg, reset).await?
}
Command::Dbinit { reset } => {
let db = DbConfig::parse(&cfg)?;
@@ -111,6 +119,7 @@ async fn app(args: Args) -> Result<(), anyhow::Error> {
)
.await?;
}
+ Command::Dev(dev_cmd) => dev::dev(cfg, dev_cmd).await?,
}
Ok(())
}