Refactor MintBuilder (#887)

* Refactor MintBuilder
* Validate azp instead of aud for client id
This commit is contained in:
David Caseria
2025-07-19 12:13:11 -04:00
committed by GitHub
parent 7f0e261a25
commit f018465aa6
20 changed files with 386 additions and 395 deletions

View File

@@ -20,7 +20,7 @@ pub(crate) async fn handle(
let mut subscription = context let mut subscription = context
.state .state
.mint .mint
.pubsub_manager .pubsub_manager()
.try_subscribe(params) .try_subscribe(params)
.await .await
.map_err(|_| WsError::ParseError)?; .map_err(|_| WsError::ParseError)?;

View File

@@ -82,7 +82,7 @@ async fn get_device_code_token(mint_info: &MintInfo, client_id: &str) -> (String
.expect("Nut21 defined") .expect("Nut21 defined")
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the OIDC configuration // Get the OIDC configuration
let oidc_config = oidc_client let oidc_config = oidc_client

View File

@@ -93,7 +93,7 @@ async fn get_access_token(
.expect("Nut21 defined") .expect("Nut21 defined")
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration // Get the token endpoint from the OIDC configuration
let token_url = oidc_client let token_url = oidc_client

View File

@@ -164,7 +164,7 @@ async fn refresh_access_token(
.ok_or_else(|| anyhow::anyhow!("OIDC discovery information not available"))? .ok_or_else(|| anyhow::anyhow!("OIDC discovery information not available"))?
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration // Get the token endpoint from the OIDC configuration
let token_url = oidc_client.get_oidc_config().await?.token_endpoint; let token_url = oidc_client.get_oidc_config().await?.token_endpoint;

View File

@@ -162,8 +162,8 @@ impl PaymentProcessorKey {
} }
} }
/// Secs wuotes are valid /// Seconds quotes are valid
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct QuoteTTL { pub struct QuoteTTL {
/// Seconds mint quote is valid /// Seconds mint quote is valid
pub mint_ttl: u64, pub mint_ttl: u64,
@@ -178,6 +178,15 @@ impl QuoteTTL {
} }
} }
impl Default for QuoteTTL {
fn default() -> Self {
Self {
mint_ttl: 60 * 60, // 1 hour
melt_ttl: 60, // 1 minute
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::str::FromStr; use std::str::FromStr;

View File

@@ -31,14 +31,10 @@ where
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
let mut mint_builder = MintBuilder::new(); let mut mint_builder = MintBuilder::new(Arc::new(database));
mint_builder = mint_builder mint_builder
.with_localstore(Arc::new(database)) .add_payment_processor(
.with_keystore(Arc::new(key_store));
mint_builder = mint_builder
.add_ln_backend(
CurrencyUnit::Sat, CurrencyUnit::Sat,
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
MintMeltLimits::new(1, 300), MintMeltLimits::new(1, 300),
@@ -46,10 +42,14 @@ where
) )
.await?; .await?;
mint_builder = let auth_database = Arc::new(auth_database);
mint_builder.set_clear_auth_settings(openid_discovery, "cashu-client".to_string());
mint_builder = mint_builder.set_blind_auth_settings(50); mint_builder = mint_builder.with_auth(
auth_database.clone(),
openid_discovery,
"cashu-client".to_string(),
vec![],
);
let blind_auth_endpoints = vec![ let blind_auth_endpoints = vec![
ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11), ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11),
@@ -71,6 +71,8 @@ where
acc acc
}); });
mint_builder = mint_builder.with_blind_auth(50, blind_auth_endpoints.keys().cloned().collect());
let mut tx = auth_database.begin_transaction().await?; let mut tx = auth_database.begin_transaction().await?;
tx.add_protected_endpoints(blind_auth_endpoints).await?; tx.add_protected_endpoints(blind_auth_endpoints).await?;
@@ -85,15 +87,13 @@ where
tx.commit().await?; tx.commit().await?;
mint_builder = mint_builder.with_auth_localstore(Arc::new(auth_database));
let mnemonic = Mnemonic::generate(12)?; let mnemonic = Mnemonic::generate(12)?;
mint_builder = mint_builder mint_builder = mint_builder.with_description("fake test mint".to_string());
.with_description("fake test mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
let _mint = mint_builder.build().await?; let _mint = mint_builder
.build_with_seed(Arc::new(key_store), &mnemonic.to_seed_normalized(""))
.await?;
todo!("Need to start this a cdk mintd keeping as ref for now"); todo!("Need to start this a cdk mintd keeping as ref for now");
} }

View File

@@ -10,7 +10,7 @@ use async_trait::async_trait;
use bip39::Mnemonic; use bip39::Mnemonic;
use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response}; use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
use cdk::amount::SplitTarget; use cdk::amount::SplitTarget;
use cdk::cdk_database::{self, WalletDatabase}; use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
use cdk::mint::{MintBuilder, MintMeltLimits}; use cdk::mint::{MintBuilder, MintMeltLimits};
use cdk::nuts::nut00::ProofsMethods; use cdk::nuts::nut00::ProofsMethods;
use cdk::nuts::{ use cdk::nuts::{
@@ -227,25 +227,22 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
// Read environment variable to determine database type // Read environment variable to determine database type
let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set"); let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
let mut mint_builder = match db_type.to_lowercase().as_str() { let localstore = match db_type.to_lowercase().as_str() {
"memory" => MintBuilder::new() "memory" => Arc::new(cdk_sqlite::mint::memory::empty().await?),
.with_localstore(Arc::new(cdk_sqlite::mint::memory::empty().await?))
.with_keystore(Arc::new(cdk_sqlite::mint::memory::empty().await?)),
_ => { _ => {
// Create a temporary directory for SQLite database // Create a temporary directory for SQLite database
let temp_dir = create_temp_dir("cdk-test-sqlite-mint")?; let temp_dir = create_temp_dir("cdk-test-sqlite-mint")?;
let path = temp_dir.join("mint.db").to_str().unwrap().to_string(); let path = temp_dir.join("mint.db").to_str().unwrap().to_string();
let database = Arc::new( Arc::new(
cdk_sqlite::MintSqliteDatabase::new(&path) cdk_sqlite::MintSqliteDatabase::new(&path)
.await .await
.expect("Could not create sqlite db"), .expect("Could not create sqlite db"),
); )
MintBuilder::new()
.with_localstore(database.clone())
.with_keystore(database)
} }
}; };
let mut mint_builder = MintBuilder::new(localstore.clone());
let fee_reserve = FeeReserve { let fee_reserve = FeeReserve {
min_fee_reserve: 1.into(), min_fee_reserve: 1.into(),
percent_fee_reserve: 1.0, percent_fee_reserve: 1.0,
@@ -258,8 +255,8 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
0, 0,
); );
mint_builder = mint_builder mint_builder
.add_ln_backend( .add_payment_processor(
CurrencyUnit::Sat, CurrencyUnit::Sat,
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
MintMeltLimits::new(1, 10_000), MintMeltLimits::new(1, 10_000),
@@ -272,23 +269,18 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
mint_builder = mint_builder mint_builder = mint_builder
.with_name("pure test mint".to_string()) .with_name("pure test mint".to_string())
.with_description("pure test mint".to_string()) .with_description("pure test mint".to_string())
.with_urls(vec!["https://aaa".to_string()]) .with_urls(vec!["https://aaa".to_string()]);
.with_seed(mnemonic.to_seed_normalized("").to_vec());
let localstore = mint_builder let tx_localstore = localstore.clone();
.localstore let mut tx = tx_localstore.begin_transaction().await?;
.as_ref()
.map(|x| x.clone())
.expect("localstore");
let mut tx = localstore.begin_transaction().await?;
tx.set_mint_info(mint_builder.mint_info.clone()).await?;
let quote_ttl = QuoteTTL::new(10000, 10000); let quote_ttl = QuoteTTL::new(10000, 10000);
tx.set_quote_ttl(quote_ttl).await?; tx.set_quote_ttl(quote_ttl).await?;
tx.commit().await?; tx.commit().await?;
let mint = mint_builder.build().await?; let mint = mint_builder
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
.await?;
let mint_clone = mint.clone(); let mint_clone = mint.clone();
let shutdown = Arc::new(Notify::new()); let shutdown = Arc::new(Notify::new());

View File

@@ -753,7 +753,7 @@ async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
.expect("Nutxx defined") .expect("Nutxx defined")
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration // Get the token endpoint from the OIDC configuration
let token_url = oidc_client let token_url = oidc_client
@@ -811,7 +811,7 @@ async fn get_custom_access_token(
.expect("Nutxx defined") .expect("Nutxx defined")
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration // Get the token endpoint from the OIDC configuration
let token_url = oidc_client let token_url = oidc_client

View File

@@ -443,7 +443,7 @@ pub async fn test_p2pk_swap() {
.collect(); .collect();
let mut listener = mint_bob let mut listener = mint_bob
.pubsub_manager .pubsub_manager()
.try_subscribe::<IndexableParams>( .try_subscribe::<IndexableParams>(
Params { Params {
kind: cdk::nuts::nut17::Kind::ProofState, kind: cdk::nuts::nut17::Kind::ProofState,
@@ -773,7 +773,7 @@ async fn test_concurrent_double_spend_swap() {
// Verify that all proofs are marked as spent in the mint // Verify that all proofs are marked as spent in the mint
let states = mint_bob let states = mint_bob
.localstore .localstore()
.get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>()) .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
.await .await
.expect("Failed to get proof state"); .expect("Failed to get proof state");
@@ -870,7 +870,7 @@ async fn test_concurrent_double_spend_melt() {
// Verify that all proofs are marked as spent in the mint // Verify that all proofs are marked as spent in the mint
let states = mint_bob let states = mint_bob
.localstore .localstore()
.get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>()) .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
.await .await
.expect("Failed to get proof state"); .expect("Failed to get proof state");

View File

@@ -29,14 +29,15 @@ async fn test_correct_keyset() {
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
let mut mint_builder = MintBuilder::new();
let localstore = Arc::new(database); let localstore = Arc::new(database);
mint_builder = mint_builder let mut mint_builder = MintBuilder::new(localstore.clone());
.with_localstore(localstore.clone())
.with_keystore(localstore.clone());
mint_builder = mint_builder mint_builder = mint_builder
.add_ln_backend( .with_name("regtest mint".to_string())
.with_description("regtest mint".to_string());
mint_builder
.add_payment_processor(
CurrencyUnit::Sat, CurrencyUnit::Sat,
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
MintMeltLimits::new(1, 5_000), MintMeltLimits::new(1, 5_000),
@@ -44,18 +45,14 @@ async fn test_correct_keyset() {
) )
.await .await
.unwrap(); .unwrap();
// .with_seed(mnemonic.to_seed_normalized("").to_vec());
mint_builder = mint_builder let mint = mint_builder
.with_name("regtest mint".to_string()) .build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
.with_description("regtest mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
let mint = mint_builder.build().await.unwrap();
let mut tx = localstore.begin_transaction().await.unwrap();
tx.set_mint_info(mint_builder.mint_info.clone())
.await .await
.unwrap(); .unwrap();
let mut tx = localstore.begin_transaction().await.unwrap();
let quote_ttl = QuoteTTL::new(10000, 10000); let quote_ttl = QuoteTTL::new(10000, 10000);
tx.set_quote_ttl(quote_ttl).await.unwrap(); tx.set_quote_ttl(quote_ttl).await.unwrap();
@@ -81,7 +78,6 @@ async fn test_correct_keyset() {
assert_ne!(keyset_info.id, old_keyset_info.id); assert_ne!(keyset_info.id, old_keyset_info.id);
mint.rotate_keyset(CurrencyUnit::Sat, 32, 0).await.unwrap(); mint.rotate_keyset(CurrencyUnit::Sat, 32, 0).await.unwrap();
let mint = mint_builder.build().await.unwrap();
let active = mint.get_active_keysets(); let active = mint.get_active_keysets();

View File

@@ -643,7 +643,7 @@ impl CdkMint for MintRPCServer {
let mint_quote = self let mint_quote = self
.mint .mint
.localstore .localstore()
.get_mint_quote(&quote_id) .get_mint_quote(&quote_id)
.await .await
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))? .map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?
@@ -659,9 +659,8 @@ impl CdkMint for MintRPCServer {
payment_identifier: mint_quote.request_lookup_id.clone(), payment_identifier: mint_quote.request_lookup_id.clone(),
}; };
let mut tx = self let localstore = self.mint.localstore();
.mint let mut tx = localstore
.localstore
.begin_transaction() .begin_transaction()
.await .await
.map_err(|_| Status::internal("Could not start db transaction".to_string()))?; .map_err(|_| Status::internal("Could not start db transaction".to_string()))?;
@@ -693,9 +692,8 @@ impl CdkMint for MintRPCServer {
vec![], // payment_ids vec![], // payment_ids
); );
let mut tx = self let mint_store = self.mint.localstore();
.mint let mut tx = mint_store
.localstore
.begin_transaction() .begin_transaction()
.await .await
.map_err(|_| Status::internal("Could not update quote".to_string()))?; .map_err(|_| Status::internal("Could not update quote".to_string()))?;
@@ -710,7 +708,7 @@ impl CdkMint for MintRPCServer {
let mint_quote = self let mint_quote = self
.mint .mint
.localstore .localstore()
.get_mint_quote(&quote_id) .get_mint_quote(&quote_id)
.await .await
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))? .map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?

View File

@@ -19,7 +19,7 @@ use bip39::Mnemonic;
use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase}; use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
use cdk::cdk_payment; use cdk::cdk_payment;
use cdk::cdk_payment::MintPayment; use cdk::cdk_payment::MintPayment;
use cdk::mint::{MintBuilder, MintMeltLimits}; use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
#[cfg(any( #[cfg(any(
feature = "cln", feature = "cln",
feature = "lnbits", feature = "lnbits",
@@ -84,16 +84,13 @@ compile_error!(
async fn main() -> Result<()> { async fn main() -> Result<()> {
let (work_dir, settings, localstore, keystore) = initial_setup().await?; let (work_dir, settings, localstore, keystore) = initial_setup().await?;
let mint_builder = MintBuilder::new() let mint_builder = MintBuilder::new(localstore);
.with_localstore(localstore)
.with_keystore(keystore);
let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?; let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?;
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?; let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?;
let mint_builder_info = mint_builder.mint_info.clone();
let mint = mint_builder.build().await?; let mint = build_mint(&settings, keystore, mint_builder).await?;
tracing::debug!("Mint built from builder."); tracing::debug!("Mint built from builder.");
@@ -109,7 +106,7 @@ async fn main() -> Result<()> {
&settings, &settings,
ln_routers, ln_routers,
&work_dir, &work_dir,
mint_builder_info, mint.mint_info().await?,
) )
.await?; .await?;
@@ -236,9 +233,6 @@ async fn configure_mint_builder(
// Configure lightning backend // Configure lightning backend
let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?; let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
// Configure signatory or seed
let mint_builder = configure_signing_method(settings, mint_builder).await?;
// Configure caching // Configure caching
let mint_builder = configure_cache(settings, mint_builder); let mint_builder = configure_cache(settings, mint_builder);
@@ -274,7 +268,7 @@ fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder)
} }
for contact in contacts { for contact in contacts {
builder = builder.add_contact_info(contact); builder = builder.with_contact_info(contact);
} }
if let Some(pubkey) = settings.mint_info.pubkey { if let Some(pubkey) = settings.mint_info.pubkey {
@@ -435,8 +429,8 @@ async fn configure_backend_for_unit(
if let Some(bolt12) = payment_settings.get("bolt12") { if let Some(bolt12) = payment_settings.get("bolt12") {
if bolt12.as_bool().unwrap_or_default() { if bolt12.as_bool().unwrap_or_default() {
mint_builder = mint_builder mint_builder
.add_ln_backend( .add_payment_processor(
unit.clone(), unit.clone(),
PaymentMethod::Bolt12, PaymentMethod::Bolt12,
mint_melt_limits, mint_melt_limits,
@@ -446,8 +440,8 @@ async fn configure_backend_for_unit(
} }
} }
mint_builder = mint_builder mint_builder
.add_ln_backend( .add_payment_processor(
unit.clone(), unit.clone(),
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
mint_melt_limits, mint_melt_limits,
@@ -456,47 +450,15 @@ async fn configure_backend_for_unit(
.await?; .await?;
if let Some(input_fee) = settings.info.input_fee_ppk { if let Some(input_fee) = settings.info.input_fee_ppk {
mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?; mint_builder.set_unit_fee(&unit, input_fee)?;
} }
let nut17_supported = SupportedMethods::default_bolt11(unit); let nut17_supported = SupportedMethods::default_bolt11(unit);
mint_builder = mint_builder.add_supported_websockets(nut17_supported); mint_builder = mint_builder.with_supported_websockets(nut17_supported);
Ok(mint_builder) Ok(mint_builder)
} }
/// Configures the signing method (remote signatory or local seed)
async fn configure_signing_method(
settings: &config::Settings,
mint_builder: MintBuilder,
) -> Result<MintBuilder> {
if let Some(signatory_url) = settings.info.signatory_url.clone() {
tracing::info!(
"Connecting to remote signatory to {} with certs {:?}",
signatory_url,
settings.info.signatory_certs.clone()
);
Ok(mint_builder.with_signatory(Arc::new(
cdk_signatory::SignatoryRpcClient::new(
signatory_url,
settings.info.signatory_certs.clone(),
)
.await?,
)))
} else if let Some(mnemonic) = settings
.info
.mnemonic
.clone()
.map(|s| Mnemonic::from_str(&s))
.transpose()?
{
Ok(mint_builder.with_seed(mnemonic.to_seed_normalized("").to_vec()))
} else {
bail!("No seed nor remote signatory set");
}
}
/// Configures cache settings /// Configures cache settings
fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder { fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
let cached_endpoints = vec![ let cached_endpoints = vec![
@@ -506,7 +468,7 @@ fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> Mi
]; ];
let cache: HttpCache = settings.info.http_cache.clone().into(); let cache: HttpCache = settings.info.http_cache.clone().into();
mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints) mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
} }
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
@@ -532,16 +494,9 @@ async fn setup_authentication(
} }
}; };
mint_builder = mint_builder.with_auth_localstore(auth_localstore.clone());
let mint_blind_auth_endpoint = let mint_blind_auth_endpoint =
ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth); ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
mint_builder = mint_builder.set_clear_auth_settings(
auth_settings.openid_discovery,
auth_settings.openid_client_id,
);
let mut protected_endpoints = HashMap::new(); let mut protected_endpoints = HashMap::new();
protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear); protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
@@ -644,7 +599,14 @@ async fn setup_authentication(
} }
} }
mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat); mint_builder = mint_builder.with_auth(
auth_localstore.clone(),
auth_settings.openid_discovery,
auth_settings.openid_client_id,
vec![mint_blind_auth_endpoint],
);
mint_builder =
mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
let mut tx = auth_localstore.begin_transaction().await?; let mut tx = auth_localstore.begin_transaction().await?;
@@ -655,6 +617,43 @@ async fn setup_authentication(
Ok(mint_builder) Ok(mint_builder)
} }
/// Build mints with the configured the signing method (remote signatory or local seed)
async fn build_mint(
settings: &config::Settings,
keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
mint_builder: MintBuilder,
) -> Result<Mint> {
if let Some(signatory_url) = settings.info.signatory_url.clone() {
tracing::info!(
"Connecting to remote signatory to {} with certs {:?}",
signatory_url,
settings.info.signatory_certs.clone()
);
Ok(mint_builder
.build_with_signatory(Arc::new(
cdk_signatory::SignatoryRpcClient::new(
signatory_url,
settings.info.signatory_certs.clone(),
)
.await?,
))
.await?)
} else if let Some(mnemonic) = settings
.info
.mnemonic
.clone()
.map(|s| Mnemonic::from_str(&s))
.transpose()?
{
Ok(mint_builder
.build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
.await?)
} else {
bail!("No seed nor remote signatory set");
}
}
async fn start_services( async fn start_services(
mint: Arc<cdk::mint::Mint>, mint: Arc<cdk::mint::Mint>,
settings: &config::Settings, settings: &config::Settings,
@@ -704,7 +703,7 @@ async fn start_services(
mint.set_mint_info(mint_builder_info).await?; mint.set_mint_info(mint_builder_info).await?;
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?; mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
} else { } else {
if mint.localstore.get_quote_ttl().await.is_err() { if mint.localstore().get_quote_ttl().await.is_err() {
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?; mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
} }
// Add version information // Add version information

View File

@@ -112,7 +112,7 @@ async fn get_access_token(mint_info: &MintInfo) -> String {
.expect("Nut21 defined") .expect("Nut21 defined")
.openid_discovery; .openid_discovery;
let oidc_client = OidcClient::new(openid_discovery); let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration // Get the token endpoint from the OIDC configuration
let token_url = oidc_client let token_url = oidc_client

View File

@@ -3,13 +3,13 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::anyhow;
use bitcoin::bip32::DerivationPath; use bitcoin::bip32::DerivationPath;
use cdk_common::database::{self, MintDatabase, MintKeysDatabase}; use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
use cdk_common::error::Error; use cdk_common::error::Error;
use cdk_common::nut04::MintMethodOptions; use cdk_common::nut04::MintMethodOptions;
use cdk_common::nut05::MeltMethodOptions; use cdk_common::nut05::MeltMethodOptions;
use cdk_common::payment::Bolt11Settings; use cdk_common::payment::Bolt11Settings;
#[cfg(feature = "auth")]
use cdk_common::{nut21, nut22}; use cdk_common::{nut21, nut22};
use cdk_signatory::signatory::Signatory; use cdk_signatory::signatory::Signatory;
@@ -19,103 +19,91 @@ use super::nut19::{self, CachedEndpoint};
use super::MintAuthDatabase; use super::MintAuthDatabase;
use super::Nuts; use super::Nuts;
use crate::amount::Amount; use crate::amount::Amount;
#[cfg(feature = "auth")]
use crate::cdk_database; use crate::cdk_database;
use crate::cdk_payment::{self, MintPayment}; use crate::cdk_payment::{self, MintPayment};
use crate::mint::Mint; use crate::mint::Mint;
#[cfg(feature = "auth")]
use crate::nuts::ProtectedEndpoint;
use crate::nuts::{ use crate::nuts::{
ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, MintVersion, ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, MintVersion,
MppMethodSettings, PaymentMethod, MppMethodSettings, PaymentMethod,
}; };
use crate::types::PaymentProcessorKey; use crate::types::PaymentProcessorKey;
/// Cashu Mint /// Cashu Mint Builder
#[derive(Default)]
pub struct MintBuilder { pub struct MintBuilder {
/// Mint Info mint_info: MintInfo,
pub mint_info: MintInfo, localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
/// Mint Storage backend
pub localstore: Option<Arc<dyn MintDatabase<database::Error> + Send + Sync>>,
/// Database for the Signatory
keystore: Option<Arc<dyn MintKeysDatabase<Err = database::Error> + Send + Sync>>,
/// Mint Storage backend
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>>, auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>>,
/// Ln backends for mint payment_processors:
ln: Option<
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>, HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
>,
seed: Option<Vec<u8>>,
supported_units: HashMap<CurrencyUnit, (u64, u8)>, supported_units: HashMap<CurrencyUnit, (u64, u8)>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>, custom_paths: HashMap<CurrencyUnit, DerivationPath>,
// protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
openid_discovery: Option<String>,
signatory: Option<Arc<dyn Signatory + Sync + Send + 'static>>,
} }
impl MintBuilder { impl MintBuilder {
/// New mint builder /// New [`MintBuilder`]
pub fn new() -> MintBuilder { pub fn new(localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>) -> MintBuilder {
let mut builder = MintBuilder::default(); let mint_info = MintInfo {
nuts: Nuts::new()
.nut07(true)
.nut08(true)
.nut09(true)
.nut10(true)
.nut11(true)
.nut12(true),
..Default::default()
};
let nuts = Nuts::new() MintBuilder {
.nut07(true) mint_info,
.nut08(true) localstore,
.nut09(true) #[cfg(feature = "auth")]
.nut10(true) auth_localstore: None,
.nut11(true) payment_processors: HashMap::new(),
.nut12(true) supported_units: HashMap::new(),
.nut14(true) custom_paths: HashMap::new(),
.nut20(true); }
builder.mint_info.nuts = nuts;
builder
} }
/// Set signatory service /// Set clear auth settings
pub fn with_signatory(mut self, signatory: Arc<dyn Signatory + Sync + Send + 'static>) -> Self {
self.signatory = Some(signatory);
self
}
/// Set seed
pub fn with_seed(mut self, seed: Vec<u8>) -> Self {
self.seed = Some(seed);
self
}
/// Set localstore
pub fn with_localstore(
mut self,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
) -> MintBuilder {
self.localstore = Some(localstore);
self
}
/// Set keystore database
pub fn with_keystore(
mut self,
keystore: Arc<dyn MintKeysDatabase<Err = database::Error> + Send + Sync>,
) -> MintBuilder {
self.keystore = Some(keystore);
self
}
/// Set auth localstore
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
pub fn with_auth_localstore( pub fn with_auth(
mut self, mut self,
localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>, auth_localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>,
) -> MintBuilder { openid_discovery: String,
self.auth_localstore = Some(localstore); client_id: String,
protected_endpoints: Vec<ProtectedEndpoint>,
) -> Self {
self.auth_localstore = Some(auth_localstore);
self.mint_info.nuts.nut21 = Some(nut21::Settings::new(
openid_discovery,
client_id,
protected_endpoints,
));
self self
} }
/// Set Openid discovery url /// Set blind auth settings
pub fn with_openid_discovery(mut self, openid_discovery: String) -> Self { #[cfg(feature = "auth")]
self.openid_discovery = Some(openid_discovery); pub fn with_blind_auth(
mut self,
bat_max_mint: u64,
protected_endpoints: Vec<ProtectedEndpoint>,
) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut22 = Some(nut22::Settings::new(bat_max_mint, protected_endpoints));
self.mint_info.nuts = nuts;
self
}
/// Set mint info
pub fn with_mint_info(mut self, mint_info: MintInfo) -> Self {
self.mint_info = mint_info;
self self
} }
@@ -168,32 +156,68 @@ impl MintBuilder {
} }
/// Set contact info /// Set contact info
pub fn add_contact_info(mut self, contact_info: ContactInfo) -> Self { pub fn with_contact_info(mut self, contact_info: ContactInfo) -> Self {
let mut contacts = self.mint_info.contact.clone().unwrap_or_default(); let mut contacts = self.mint_info.contact.clone().unwrap_or_default();
contacts.push(contact_info); contacts.push(contact_info);
self.mint_info.contact = Some(contacts); self.mint_info.contact = Some(contacts);
self self
} }
/// Add ln backend /// Set pubkey
pub async fn add_ln_backend( pub fn with_pubkey(mut self, pubkey: crate::nuts::PublicKey) -> Self {
self.mint_info.pubkey = Some(pubkey);
self
}
/// Support websockets
pub fn with_supported_websockets(mut self, supported_method: SupportedMethods) -> Self {
let mut supported_settings = self.mint_info.nuts.nut17.supported.clone();
if !supported_settings.contains(&supported_method) {
supported_settings.push(supported_method);
self.mint_info.nuts = self.mint_info.nuts.nut17(supported_settings);
}
self
}
/// Add support for NUT19
pub fn with_cache(mut self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
let nut19_settings = nut19::Settings {
ttl,
cached_endpoints,
};
self.mint_info.nuts.nut19 = nut19_settings;
self
}
/// Set custom derivation paths for mint units
pub fn with_custom_derivation_paths(
mut self, mut self,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Self {
self.custom_paths = custom_paths;
self
}
/// Add payment processor
pub async fn add_payment_processor(
&mut self,
unit: CurrencyUnit, unit: CurrencyUnit,
method: PaymentMethod, method: PaymentMethod,
limits: MintMeltLimits, limits: MintMeltLimits,
ln_backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, payment_processor: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
) -> Result<Self, Error> { ) -> Result<(), Error> {
let ln_key = PaymentProcessorKey { let key = PaymentProcessorKey {
unit: unit.clone(), unit: unit.clone(),
method: method.clone(), method: method.clone(),
}; };
tracing::debug!("Adding ln backed for {}, {}", unit, method); let settings = payment_processor.get_settings().await?;
tracing::debug!("with limits {:?}", limits);
let mut ln = self.ln.unwrap_or_default();
let settings = ln_backend.get_settings().await?;
let settings: Bolt11Settings = settings.try_into()?; let settings: Bolt11Settings = settings.try_into()?;
@@ -235,91 +259,19 @@ impl MintBuilder {
self.mint_info.nuts.nut05.methods.push(melt_method_settings); self.mint_info.nuts.nut05.methods.push(melt_method_settings);
self.mint_info.nuts.nut05.disabled = false; self.mint_info.nuts.nut05.disabled = false;
ln.insert(ln_key.clone(), ln_backend);
let mut supported_units = self.supported_units.clone(); let mut supported_units = self.supported_units.clone();
supported_units.insert(ln_key.unit, (0, 32)); supported_units.insert(key.unit.clone(), (0, 32));
self.supported_units = supported_units; self.supported_units = supported_units;
self.ln = Some(ln); self.payment_processors.insert(key, payment_processor);
Ok(())
Ok(self)
}
/// Set pubkey
pub fn with_pubkey(mut self, pubkey: crate::nuts::PublicKey) -> Self {
self.mint_info.pubkey = Some(pubkey);
self
}
/// Support websockets
pub fn add_supported_websockets(mut self, supported_method: SupportedMethods) -> Self {
let mut supported_settings = self.mint_info.nuts.nut17.supported.clone();
if !supported_settings.contains(&supported_method) {
supported_settings.push(supported_method);
self.mint_info.nuts = self.mint_info.nuts.nut17(supported_settings);
}
self
}
/// Add support for NUT19
pub fn add_cache(mut self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
let nut19_settings = nut19::Settings {
ttl,
cached_endpoints,
};
self.mint_info.nuts.nut19 = nut19_settings;
self
}
/// Set custom derivation paths for mint units
pub fn add_custom_derivation_paths(
mut self,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Self {
self.custom_paths = custom_paths;
self
}
/// Set clear auth settings
pub fn set_clear_auth_settings(mut self, openid_discovery: String, client_id: String) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut21 = Some(nut21::Settings::new(
openid_discovery.clone(),
client_id,
vec![],
));
self.openid_discovery = Some(openid_discovery);
self.mint_info.nuts = nuts;
self
}
/// Set blind auth settings
pub fn set_blind_auth_settings(mut self, bat_max_mint: u64) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut22 = Some(nut22::Settings::new(bat_max_mint, vec![]));
self.mint_info.nuts = nuts;
self
} }
/// Sets the input fee ppk for a given unit /// Sets the input fee ppk for a given unit
/// ///
/// The unit **MUST** already have been added with a ln backend /// The unit **MUST** already have been added with a ln backend
pub fn set_unit_fee(mut self, unit: &CurrencyUnit, input_fee_ppk: u64) -> Result<Self, Error> { pub fn set_unit_fee(&mut self, unit: &CurrencyUnit, input_fee_ppk: u64) -> Result<(), Error> {
let (input_fee, _max_order) = self let (input_fee, _max_order) = self
.supported_units .supported_units
.get_mut(unit) .get_mut(unit)
@@ -327,59 +279,53 @@ impl MintBuilder {
*input_fee = input_fee_ppk; *input_fee = input_fee_ppk;
Ok(self) Ok(())
} }
/// Build mint /// Build the mint with the provided signatory
pub async fn build(&self) -> anyhow::Result<Mint> { pub async fn build_with_signatory(
let localstore = self self,
.localstore signatory: Arc<dyn Signatory + Send + Sync>,
.clone() ) -> Result<Mint, Error> {
.ok_or(anyhow!("Localstore not set"))?;
let ln = self.ln.clone().ok_or(anyhow!("Ln backends not set"))?;
let signatory = if let Some(signatory) = self.signatory.as_ref() {
signatory.clone()
} else {
let seed = self.seed.as_ref().ok_or(anyhow!("Mint seed not set"))?;
let in_memory_signatory = cdk_signatory::db_signatory::DbSignatory::new(
self.keystore.clone().ok_or(anyhow!("keystore not set"))?,
seed,
self.supported_units.clone(),
HashMap::new(),
)
.await?;
Arc::new(cdk_signatory::embedded::Service::new(Arc::new(
in_memory_signatory,
)))
};
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
if let Some(openid_discovery) = &self.openid_discovery { if let Some(auth_localstore) = self.auth_localstore {
let auth_localstore = self return Mint::new_with_auth(
.auth_localstore self.mint_info,
.clone()
.ok_or(anyhow!("Auth localstore not set"))?;
return Ok(Mint::new_with_auth(
signatory, signatory,
localstore, self.localstore,
auth_localstore, auth_localstore,
ln, self.payment_processors,
openid_discovery.clone(),
) )
.await?); .await;
} }
Mint::new(
self.mint_info,
signatory,
self.localstore,
self.payment_processors,
)
.await
}
#[cfg(not(feature = "auth"))] /// Build the mint with the provided keystore and seed
if self.openid_discovery.is_some() { pub async fn build_with_seed(
return Err(anyhow!( self,
"OpenID discovery URL provided but auth feature is not enabled" keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
)); seed: &[u8],
} ) -> Result<Mint, Error> {
let in_memory_signatory = cdk_signatory::db_signatory::DbSignatory::new(
keystore,
seed,
self.supported_units.clone(),
HashMap::new(),
)
.await?;
Ok(Mint::new(signatory, localstore, ln).await?) let signatory = Arc::new(cdk_signatory::embedded::Service::new(Arc::new(
in_memory_signatory,
)));
self.build_with_signatory(signatory).await
} }
} }

View File

@@ -23,7 +23,7 @@ impl Mint {
return Ok(()); return Ok(());
} }
let ln = match self.ln.get(&PaymentProcessorKey::new( let ln = match self.payment_processors.get(&PaymentProcessorKey::new(
quote.unit.clone(), quote.unit.clone(),
quote.payment_method.clone(), quote.payment_method.clone(),
)) { )) {

View File

@@ -152,7 +152,7 @@ impl Mint {
.await?; .await?;
let ln = self let ln = self
.ln .payment_processors
.get(&PaymentProcessorKey::new( .get(&PaymentProcessorKey::new(
unit.clone(), unit.clone(),
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
@@ -250,7 +250,7 @@ impl Mint {
.await?; .await?;
let ln = self let ln = self
.ln .payment_processors
.get(&PaymentProcessorKey::new( .get(&PaymentProcessorKey::new(
unit.clone(), unit.clone(),
PaymentMethod::Bolt12, PaymentMethod::Bolt12,
@@ -586,7 +586,7 @@ impl Mint {
_ => None, _ => None,
}; };
tracing::debug!("partial_amount: {:?}", partial_amount); tracing::debug!("partial_amount: {:?}", partial_amount);
let ln = match self.ln.get(&PaymentProcessorKey::new( let ln = match self.payment_processors.get(&PaymentProcessorKey::new(
quote.unit.clone(), quote.unit.clone(),
PaymentMethod::Bolt11, PaymentMethod::Bolt11,
)) { )) {

View File

@@ -53,17 +53,17 @@ pub struct Mint {
/// ///
/// It is implemented in the cdk-signatory crate, and it can be embedded in the mint or it can /// It is implemented in the cdk-signatory crate, and it can be embedded in the mint or it can
/// be a gRPC client to a remote signatory server. /// be a gRPC client to a remote signatory server.
pub signatory: Arc<dyn Signatory + Send + Sync>, signatory: Arc<dyn Signatory + Send + Sync>,
/// Mint Storage backend /// Mint Storage backend
pub localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>, localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
/// Auth Storage backend (only available with auth feature) /// Auth Storage backend (only available with auth feature)
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
pub auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>, auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>,
/// Ln backends for mint /// Payment processors for mint
pub ln: payment_processors:
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>, HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
/// Subscription manager /// Subscription manager
pub pubsub_manager: Arc<PubSubManager>, pubsub_manager: Arc<PubSubManager>,
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
oidc_client: Option<OidcClient>, oidc_client: Option<OidcClient>,
/// In-memory keyset /// In-memory keyset
@@ -71,40 +71,23 @@ pub struct Mint {
} }
impl Mint { impl Mint {
/// Get the payment processor for the given unit and payment method
pub fn get_payment_processor(
&self,
unit: CurrencyUnit,
payment_method: PaymentMethod,
) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
self.ln.get(&key).cloned().ok_or_else(|| {
tracing::info!(
"No payment processor set for pair {}, {}",
unit,
payment_method
);
Error::UnsupportedUnit
})
}
/// Create new [`Mint`] without authentication /// Create new [`Mint`] without authentication
pub async fn new( pub async fn new(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>, signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>, localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
ln: HashMap< payment_processors: HashMap<
PaymentProcessorKey, PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>, >,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Self::new_internal( Self::new_internal(
mint_info,
signatory, signatory,
localstore, localstore,
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
None, None,
ln, payment_processors,
#[cfg(feature = "auth")]
None,
) )
.await .await
} }
@@ -112,21 +95,21 @@ impl Mint {
/// Create new [`Mint`] with authentication support /// Create new [`Mint`] with authentication support
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
pub async fn new_with_auth( pub async fn new_with_auth(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>, signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>, localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
auth_localstore: Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>, auth_localstore: Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>,
ln: HashMap< payment_processors: HashMap<
PaymentProcessorKey, PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>, >,
open_id_discovery: String,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Self::new_internal( Self::new_internal(
mint_info,
signatory, signatory,
localstore, localstore,
Some(auth_localstore), Some(auth_localstore),
ln, payment_processors,
Some(open_id_discovery),
) )
.await .await
} }
@@ -134,21 +117,17 @@ impl Mint {
/// Internal function to create a new [`Mint`] with shared logic /// Internal function to create a new [`Mint`] with shared logic
#[inline] #[inline]
async fn new_internal( async fn new_internal(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>, signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>, localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
#[cfg(feature = "auth")] auth_localstore: Option< #[cfg(feature = "auth")] auth_localstore: Option<
Arc<dyn database::MintAuthDatabase<Err = database::Error> + Send + Sync>, Arc<dyn database::MintAuthDatabase<Err = database::Error> + Send + Sync>,
>, >,
ln: HashMap< payment_processors: HashMap<
PaymentProcessorKey, PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>, >,
#[cfg(feature = "auth")] open_id_discovery: Option<String>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
#[cfg(feature = "auth")]
let oidc_client =
open_id_discovery.map(|openid_discovery| OidcClient::new(openid_discovery.clone()));
let keysets = signatory.keysets().await?; let keysets = signatory.keysets().await?;
if !keysets if !keysets
.keysets .keysets
@@ -168,19 +147,57 @@ impl Mint {
.count() .count()
); );
let mint_store = localstore.clone();
let mut tx = mint_store.begin_transaction().await?;
tx.set_mint_info(mint_info.clone()).await?;
tx.set_quote_ttl(QuoteTTL::default()).await?;
tx.commit().await?;
Ok(Self { Ok(Self {
signatory, signatory,
pubsub_manager: Arc::new(localstore.clone().into()), pubsub_manager: Arc::new(localstore.clone().into()),
localstore, localstore,
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
oidc_client, oidc_client: mint_info.nuts.nut21.as_ref().map(|nut21| {
ln, OidcClient::new(
nut21.openid_discovery.clone(),
Some(nut21.client_id.clone()),
)
}),
payment_processors,
#[cfg(feature = "auth")] #[cfg(feature = "auth")]
auth_localstore, auth_localstore,
keysets: Arc::new(ArcSwap::new(keysets.keysets.into())), keysets: Arc::new(ArcSwap::new(keysets.keysets.into())),
}) })
} }
/// Get the payment processor for the given unit and payment method
pub fn get_payment_processor(
&self,
unit: CurrencyUnit,
payment_method: PaymentMethod,
) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
self.payment_processors.get(&key).cloned().ok_or_else(|| {
tracing::info!(
"No payment processor set for pair {}, {}",
unit,
payment_method
);
Error::UnsupportedUnit
})
}
/// Localstore
pub fn localstore(&self) -> Arc<dyn MintDatabase<database::Error> + Send + Sync> {
Arc::clone(&self.localstore)
}
/// Pub Sub manager
pub fn pubsub_manager(&self) -> Arc<PubSubManager> {
Arc::clone(&self.pubsub_manager)
}
/// Get mint info /// Get mint info
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn mint_info(&self) -> Result<MintInfo, Error> { pub async fn mint_info(&self) -> Result<MintInfo, Error> {
@@ -259,7 +276,7 @@ impl Mint {
Vec<PaymentProcessorKey>, Vec<PaymentProcessorKey>,
)> = Vec::new(); )> = Vec::new();
for (key, ln) in self.ln.iter() { for (key, ln) in self.payment_processors.iter() {
// Check if we already have this processor // Check if we already have this processor
let found = processor_groups.iter_mut().find(|(proc_ref, _)| { let found = processor_groups.iter_mut().find(|(proc_ref, _)| {
// Compare Arc pointer equality using ptr_eq // Compare Arc pointer equality using ptr_eq
@@ -609,7 +626,7 @@ mod tests {
.expect("Failed to create signatory"), .expect("Failed to create signatory"),
); );
Mint::new(signatory, localstore, HashMap::new()) Mint::new(MintInfo::default(), signatory, localstore, HashMap::new())
.await .await
.unwrap() .unwrap()
} }

View File

@@ -27,7 +27,7 @@ impl Mint {
method: PaymentMethod::Bolt11, method: PaymentMethod::Bolt11,
}; };
let ln_backend = match self.ln.get(&ln_key) { let ln_backend = match self.payment_processors.get(&ln_key) {
Some(ln_backend) => ln_backend, Some(ln_backend) => ln_backend,
None => { None => {
tracing::warn!("No backend for ln key: {:?}", ln_key); tracing::warn!("No backend for ln key: {:?}", ln_key);

View File

@@ -32,9 +32,9 @@ pub enum Error {
/// Unsupported Algo /// Unsupported Algo
#[error("Unsupported signing algo")] #[error("Unsupported signing algo")]
UnsupportedSigningAlgo, UnsupportedSigningAlgo,
/// Access token not returned /// Invalid Client ID
#[error("Error getting access token")] #[error("Invalid Client ID")]
AccessTokenMissing, InvalidClientId,
} }
impl From<Error> for cdk_common::error::Error { impl From<Error> for cdk_common::error::Error {
@@ -58,6 +58,7 @@ pub struct OidcConfig {
pub struct OidcClient { pub struct OidcClient {
client: Client, client: Client,
openid_discovery: String, openid_discovery: String,
client_id: Option<String>,
oidc_config: Arc<RwLock<Option<OidcConfig>>>, oidc_config: Arc<RwLock<Option<OidcConfig>>>,
jwks_set: Arc<RwLock<Option<JwkSet>>>, jwks_set: Arc<RwLock<Option<JwkSet>>>,
} }
@@ -88,10 +89,11 @@ pub struct TokenResponse {
impl OidcClient { impl OidcClient {
/// Create new [`OidcClient`] /// Create new [`OidcClient`]
pub fn new(openid_discovery: String) -> Self { pub fn new(openid_discovery: String, client_id: Option<String>) -> Self {
Self { Self {
client: Client::new(), client: Client::new(),
openid_discovery, openid_discovery,
client_id,
oidc_config: Arc::new(RwLock::new(None)), oidc_config: Arc::new(RwLock::new(None)),
jwks_set: Arc::new(RwLock::new(None)), jwks_set: Arc::new(RwLock::new(None)),
} }
@@ -192,11 +194,40 @@ impl OidcClient {
validation validation
}; };
if let Err(err) = match decode::<HashMap<String, serde_json::Value>>(cat_jwt, &decoding_key, &validation) {
decode::<HashMap<String, serde_json::Value>>(cat_jwt, &decoding_key, &validation) Ok(claims) => {
{ tracing::debug!("Successfully verified cat");
tracing::debug!("Could not verify cat: {}", err); tracing::debug!("Claims: {:?}", claims.claims);
return Err(err.into()); if let Some(client_id) = &self.client_id {
if let Some(token_client_id) = claims.claims.get("client_id") {
if let Some(token_client_id_value) = token_client_id.as_str() {
if token_client_id_value != client_id {
tracing::warn!(
"Client ID mismatch: expected {}, got {}",
client_id,
token_client_id_value
);
return Err(Error::InvalidClientId);
}
}
} else if let Some(azp) = claims.claims.get("azp") {
if let Some(azp_value) = azp.as_str() {
if azp_value != client_id {
tracing::warn!(
"Client ID (azp) mismatch: expected {}, got {}",
client_id,
azp_value
);
return Err(Error::InvalidClientId);
}
}
}
}
}
Err(err) => {
tracing::debug!("Could not verify cat: {}", err);
return Err(err.into());
}
} }
Ok(()) Ok(())

View File

@@ -268,8 +268,9 @@ impl Wallet {
auth_wallet.protected_endpoints.write().await; auth_wallet.protected_endpoints.write().await;
*protected_endpoints = mint_info.protected_endpoints(); *protected_endpoints = mint_info.protected_endpoints();
if let Some(oidc_client) = if let Some(oidc_client) = mint_info
mint_info.openid_discovery().map(OidcClient::new) .openid_discovery()
.map(|url| OidcClient::new(url, None))
{ {
auth_wallet.set_oidc_client(Some(oidc_client)).await; auth_wallet.set_oidc_client(Some(oidc_client)).await;
} }
@@ -277,7 +278,9 @@ impl Wallet {
None => { None => {
tracing::info!("Mint has auth enabled creating auth wallet"); tracing::info!("Mint has auth enabled creating auth wallet");
let oidc_client = mint_info.openid_discovery().map(OidcClient::new); let oidc_client = mint_info
.openid_discovery()
.map(|url| OidcClient::new(url, None));
let new_auth_wallet = AuthWallet::new( let new_auth_wallet = AuthWallet::new(
self.mint_url.clone(), self.mint_url.clone(),
None, None,