Skip to content

Commit 023220f

Browse files
committed
fix: route auth requests through configured auth transport
1 parent 4cd0070 commit 023220f

6 files changed

Lines changed: 300 additions & 39 deletions

File tree

crates/cdk/src/wallet/auth/auth_wallet.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,32 @@ impl AuthWallet {
6363
oidc_client: Option<OidcClient>,
6464
) -> Self {
6565
let http_client = Arc::new(AuthHttpClient::new(mint_url.clone(), cat));
66+
Self::new_with_client(
67+
mint_url,
68+
localstore,
69+
metadata_cache,
70+
protected_endpoints,
71+
oidc_client,
72+
http_client,
73+
)
74+
}
75+
76+
/// Create a new [`AuthWallet`] instance with a provided auth connector
77+
pub fn new_with_client(
78+
mint_url: MintUrl,
79+
localstore: Arc<dyn WalletDatabase<database::Error> + Send + Sync>,
80+
metadata_cache: Arc<MintMetadataCache>,
81+
protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
82+
oidc_client: Option<OidcClient>,
83+
auth_client: Arc<dyn AuthMintConnector + Send + Sync>,
84+
) -> Self {
6685
Self {
6786
mint_url,
6887
localstore,
6988
metadata_cache,
7089
protected_endpoints: Arc::new(RwLock::new(protected_endpoints)),
7190
refresh_token: Arc::new(RwLock::new(None)),
72-
auth_client: http_client,
91+
auth_client,
7392
oidc_client: Arc::new(RwLock::new(oidc_client)),
7493
}
7594
}

crates/cdk/src/wallet/builder.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::cdk_database::WalletDatabase;
1010
use crate::error::Error;
1111
use crate::mint_url::MintUrl;
1212
use crate::nuts::CurrencyUnit;
13-
use crate::wallet::auth::AuthWallet;
13+
use crate::wallet::auth::{AuthMintConnector, AuthWallet};
1414
use crate::wallet::mint_metadata_cache::MintMetadataCache;
1515
use crate::wallet::{HttpClient, MintConnector, SubscriptionManager, Wallet};
1616

@@ -21,6 +21,7 @@ pub struct WalletBuilder {
2121
localstore: Option<Arc<dyn WalletDatabase<database::Error> + Send + Sync>>,
2222
target_proof_count: Option<usize>,
2323
auth_wallet: Option<AuthWallet>,
24+
auth_connector: Option<Arc<dyn AuthMintConnector + Send + Sync>>,
2425
seed: Option<[u8; 64]>,
2526
use_http_subscription: bool,
2627
client: Option<Arc<dyn MintConnector + Send + Sync>>,
@@ -47,6 +48,7 @@ impl Default for WalletBuilder {
4748
localstore: None,
4849
target_proof_count: Some(3),
4950
auth_wallet: None,
51+
auth_connector: None,
5052
seed: None,
5153
client: None,
5254
metadata_cache_ttl: Some(Duration::from_secs(3600)),
@@ -128,6 +130,15 @@ impl WalletBuilder {
128130
self
129131
}
130132

133+
/// Set the auth connector used when an auth wallet is created from mint info
134+
pub fn auth_connector(
135+
mut self,
136+
auth_connector: Arc<dyn AuthMintConnector + Send + Sync>,
137+
) -> Self {
138+
self.auth_connector = Some(auth_connector);
139+
self
140+
}
141+
131142
/// Set the seed bytes
132143
pub fn seed(mut self, seed: [u8; 64]) -> Self {
133144
self.seed.zeroize();
@@ -249,6 +260,7 @@ impl WalletBuilder {
249260
metadata_cache,
250261
target_proof_count: self.target_proof_count.unwrap_or(3),
251262
auth_wallet: Arc::new(TokioRwLock::new(auth_wallet)),
263+
auth_connector: self.auth_connector.take(),
252264
#[cfg(feature = "npubcash")]
253265
npubcash_client: Arc::new(TokioRwLock::new(None)),
254266
seed,

crates/cdk/src/wallet/mint_connector/http_client.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,35 @@ where
870870
)),
871871
}
872872
}
873+
874+
/// Create new [`AuthHttpClient`] with a provided transport implementation.
875+
pub fn with_transport(mint_url: MintUrl, transport: T, cat: Option<AuthToken>) -> Self {
876+
Self {
877+
transport: transport.into(),
878+
mint_url,
879+
cat: Arc::new(RwLock::new(
880+
cat.unwrap_or(AuthToken::ClearAuth("".to_string())),
881+
)),
882+
}
883+
}
884+
885+
/// Create new [`AuthHttpClient`] with a proxy for specific TLDs.
886+
/// Specifying `None` for `host_matcher` will use the proxy for all
887+
/// requests.
888+
pub fn with_proxy(
889+
mint_url: MintUrl,
890+
proxy: Url,
891+
host_matcher: Option<&str>,
892+
accept_invalid_certs: bool,
893+
cat: Option<AuthToken>,
894+
) -> Result<Self, Error> {
895+
let mut transport = T::default();
896+
transport
897+
.with_proxy(proxy, host_matcher, accept_invalid_certs)
898+
.map_err(HttpClient::<T>::map_http_error)?;
899+
900+
Ok(Self::with_transport(mint_url, transport, cat))
901+
}
873902
}
874903

875904
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]

crates/cdk/src/wallet/mint_connector/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub mod transport;
2525
pub type AuthHttpClient = http_client::AuthHttpClient<transport::Async>;
2626
/// Default Http Client with async transport (non-Tor)
2727
pub type HttpClient = http_client::HttpClient<transport::Async>;
28+
/// Tor Auth HTTP Client with async transport (only when `tor` feature is enabled and not on wasm32)
29+
#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
30+
pub type TorAuthHttpClient = http_client::AuthHttpClient<transport::TorAsync>;
2831
/// Tor Http Client with async transport (only when `tor` feature is enabled and not on wasm32)
2932
#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
3033
pub type TorHttpClient = http_client::HttpClient<transport::TorAsync>;

crates/cdk/src/wallet/mod.rs

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ mod blind_signature;
4242
#[cfg(feature = "nostr")]
4343
mod nostr_backup;
4444
#[cfg(all(feature = "tor", not(target_arch = "wasm32")))]
45-
pub use mint_connector::TorHttpClient;
45+
pub use mint_connector::{TorAuthHttpClient, TorHttpClient};
4646
mod balance;
4747
mod builder;
4848
mod issue;
@@ -130,6 +130,7 @@ pub struct Wallet {
130130
/// The targeted amount of proofs to have at each size
131131
pub target_proof_count: usize,
132132
auth_wallet: Arc<TokioRwLock<Option<AuthWallet>>>,
133+
auth_connector: Option<Arc<dyn AuthMintConnector + Send + Sync>>,
133134
#[cfg(feature = "npubcash")]
134135
npubcash_client: Arc<TokioRwLock<Option<Arc<cdk_npubcash::NpubCashClient>>>>,
135136
seed: [u8; 64],
@@ -434,14 +435,24 @@ impl Wallet {
434435
None => {
435436
tracing::info!("Mint has auth enabled; creating auth wallet");
436437

437-
let new_auth_wallet = AuthWallet::new(
438-
self.mint_url.clone(),
439-
None,
440-
self.localstore.clone(),
441-
self.metadata_cache.clone(),
442-
protected_endpoints,
443-
oidc_client,
444-
);
438+
let new_auth_wallet = match self.auth_connector.as_ref() {
439+
Some(auth_connector) => AuthWallet::new_with_client(
440+
self.mint_url.clone(),
441+
self.localstore.clone(),
442+
self.metadata_cache.clone(),
443+
protected_endpoints,
444+
oidc_client,
445+
auth_connector.clone(),
446+
),
447+
None => AuthWallet::new(
448+
self.mint_url.clone(),
449+
None,
450+
self.localstore.clone(),
451+
self.metadata_cache.clone(),
452+
protected_endpoints,
453+
oidc_client,
454+
),
455+
};
445456
*auth_wallet = Some(new_auth_wallet.clone());
446457

447458
self.client
@@ -993,10 +1004,106 @@ impl Drop for Wallet {
9931004

9941005
#[cfg(test)]
9951006
mod tests {
1007+
use std::sync::Mutex;
1008+
1009+
use async_trait::async_trait;
1010+
use bitcoin::bip32::DerivationPath;
1011+
use bitcoin::secp256k1::Secp256k1;
1012+
use cdk_common::nut02::KeySetVersion;
1013+
use cdk_common::nuts::{KeySet, KeySetInfo, KeysetResponse, MintKeySet};
1014+
9961015
use super::*;
997-
use crate::nuts::{BlindSignature, BlindedMessage, PreMint, PreMintSecrets};
1016+
use crate::nuts::{AuthToken, BlindSignature, BlindedMessage, PreMint, PreMintSecrets};
9981017
use crate::secret::Secret;
9991018

1019+
fn build_test_auth_keyset(seed_byte: u8) -> KeySet {
1020+
let secp = Secp256k1::new();
1021+
let seed = [seed_byte; 32];
1022+
let path = DerivationPath::from_str("m/0'").expect("valid derivation path");
1023+
let mint_keyset = MintKeySet::generate_from_seed(
1024+
&secp,
1025+
&seed,
1026+
&[1, 2],
1027+
CurrencyUnit::Auth,
1028+
path,
1029+
0,
1030+
None,
1031+
KeySetVersion::Version00,
1032+
);
1033+
1034+
KeySet {
1035+
id: mint_keyset.id,
1036+
unit: mint_keyset.unit.clone(),
1037+
active: None,
1038+
keys: mint_keyset.keys.into(),
1039+
input_fee_ppk: mint_keyset.input_fee_ppk,
1040+
final_expiry: mint_keyset.final_expiry,
1041+
}
1042+
}
1043+
1044+
#[derive(Debug)]
1045+
struct CountingAuthConnector {
1046+
keyset: KeySet,
1047+
keysets_calls: Arc<Mutex<usize>>,
1048+
keyset_calls: Arc<Mutex<usize>>,
1049+
}
1050+
1051+
impl CountingAuthConnector {
1052+
fn new(keyset: KeySet) -> Self {
1053+
Self {
1054+
keyset,
1055+
keysets_calls: Arc::new(Mutex::new(0)),
1056+
keyset_calls: Arc::new(Mutex::new(0)),
1057+
}
1058+
}
1059+
}
1060+
1061+
#[async_trait]
1062+
impl AuthMintConnector for CountingAuthConnector {
1063+
async fn get_auth_token(&self) -> Result<AuthToken, Error> {
1064+
Ok(AuthToken::ClearAuth("dummy".to_string()))
1065+
}
1066+
1067+
async fn set_auth_token(&self, _: AuthToken) -> Result<(), Error> {
1068+
Ok(())
1069+
}
1070+
1071+
async fn get_mint_info(&self) -> Result<MintInfo, Error> {
1072+
Ok(MintInfo::default())
1073+
}
1074+
1075+
async fn get_mint_blind_auth_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
1076+
*self.keyset_calls.lock().expect("lock") += 1;
1077+
if keyset_id == self.keyset.id {
1078+
Ok(self.keyset.clone())
1079+
} else {
1080+
Err(Error::UnknownKeySet)
1081+
}
1082+
}
1083+
1084+
async fn get_mint_blind_auth_keysets(&self) -> Result<KeysetResponse, Error> {
1085+
*self.keysets_calls.lock().expect("lock") += 1;
1086+
Ok(KeysetResponse {
1087+
keysets: vec![KeySetInfo {
1088+
id: self.keyset.id,
1089+
unit: CurrencyUnit::Auth,
1090+
active: true,
1091+
input_fee_ppk: self.keyset.input_fee_ppk,
1092+
final_expiry: self.keyset.final_expiry,
1093+
}],
1094+
})
1095+
}
1096+
1097+
async fn post_mint_blind_auth(
1098+
&self,
1099+
_: crate::nuts::nut22::MintAuthRequest,
1100+
) -> Result<crate::nuts::MintResponse, Error> {
1101+
Err(Error::Custom(
1102+
"post_mint_blind_auth is not used in this test".to_string(),
1103+
))
1104+
}
1105+
}
1106+
10001107
/// Test that restore signature matching works correctly when response is in order
10011108
#[test]
10021109
fn test_restore_signature_matching_in_order() {
@@ -1332,4 +1439,41 @@ mod tests {
13321439
result
13331440
);
13341441
}
1442+
1443+
#[tokio::test]
1444+
async fn fetch_mint_info_uses_configured_auth_connector_for_auth_settings() {
1445+
use crate::wallet::test_utils::{
1446+
create_test_db, test_mint_info, test_mint_url, MockMintConnector,
1447+
};
1448+
1449+
let db = create_test_db().await;
1450+
let mock_client = Arc::new(MockMintConnector::new());
1451+
let mut mint_info = test_mint_info();
1452+
mint_info.time = None;
1453+
mint_info.nuts.nut22 = Some(crate::nuts::BlindAuthSettings::new(10, Vec::new()));
1454+
mock_client.set_mint_info_response(Ok(mint_info));
1455+
1456+
let auth_connector = Arc::new(CountingAuthConnector::new(build_test_auth_keyset(1)));
1457+
let keysets_calls = auth_connector.keysets_calls.clone();
1458+
let keyset_calls = auth_connector.keyset_calls.clone();
1459+
1460+
let wallet = WalletBuilder::new()
1461+
.mint_url(test_mint_url())
1462+
.unit(CurrencyUnit::Sat)
1463+
.localstore(db)
1464+
.seed([0u8; 64])
1465+
.shared_client(mock_client)
1466+
.auth_connector(auth_connector)
1467+
.build()
1468+
.expect("wallet should build");
1469+
1470+
wallet
1471+
.fetch_mint_info()
1472+
.await
1473+
.expect("mint info should load and auth keysets should refresh from mock connector");
1474+
1475+
assert!(wallet.auth_wallet.read().await.is_some());
1476+
assert_eq!(*keysets_calls.lock().expect("lock"), 1);
1477+
assert_eq!(*keyset_calls.lock().expect("lock"), 1);
1478+
}
13351479
}

0 commit comments

Comments
 (0)