mint_blind_auth.rs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. use std::path::Path;
  2. use std::str::FromStr;
  3. use anyhow::{anyhow, Result};
  4. use cdk::mint_url::MintUrl;
  5. use cdk::nuts::{CurrencyUnit, MintInfo};
  6. use cdk::wallet::types::WalletKey;
  7. use cdk::wallet::MultiMintWallet;
  8. use cdk::{Amount, OidcClient};
  9. use clap::Args;
  10. use serde::{Deserialize, Serialize};
  11. use crate::token_storage;
  12. #[derive(Args, Serialize, Deserialize)]
  13. pub struct MintBlindAuthSubCommand {
  14. /// Mint url
  15. mint_url: MintUrl,
  16. /// Amount
  17. amount: Option<u64>,
  18. /// Cat (access token)
  19. #[arg(long)]
  20. cat: Option<String>,
  21. /// Currency unit e.g. sat
  22. #[arg(default_value = "sat")]
  23. #[arg(short, long)]
  24. unit: String,
  25. }
  26. pub async fn mint_blind_auth(
  27. multi_mint_wallet: &MultiMintWallet,
  28. sub_command_args: &MintBlindAuthSubCommand,
  29. work_dir: &Path,
  30. ) -> Result<()> {
  31. let mint_url = sub_command_args.mint_url.clone();
  32. let unit = CurrencyUnit::from_str(&sub_command_args.unit)?;
  33. let wallet = match multi_mint_wallet
  34. .get_wallet(&WalletKey::new(mint_url.clone(), unit.clone()))
  35. .await
  36. {
  37. Some(wallet) => wallet.clone(),
  38. None => {
  39. multi_mint_wallet
  40. .create_and_add_wallet(&mint_url.to_string(), unit, None)
  41. .await?
  42. }
  43. };
  44. wallet.get_mint_info().await?;
  45. // Try to get the token from the provided argument or from the stored file
  46. let cat = match &sub_command_args.cat {
  47. Some(token) => token.clone(),
  48. None => {
  49. // Try to load from file
  50. match token_storage::get_token_for_mint(work_dir, &mint_url).await {
  51. Ok(Some(token_data)) => {
  52. println!("Using access token from cashu_tokens.json");
  53. token_data.access_token
  54. }
  55. Ok(None) => {
  56. return Err(anyhow::anyhow!(
  57. "No access token provided and no token found in cashu_tokens.json for this mint"
  58. ));
  59. }
  60. Err(e) => {
  61. return Err(anyhow::anyhow!(
  62. "Failed to read token from cashu_tokens.json: {}",
  63. e
  64. ));
  65. }
  66. }
  67. }
  68. };
  69. // Try to set the access token
  70. if let Err(err) = wallet.set_cat(cat.clone()).await {
  71. tracing::error!("Could not set cat: {}", err);
  72. // Try to refresh the token if we have a refresh token
  73. if let Ok(Some(token_data)) = token_storage::get_token_for_mint(work_dir, &mint_url).await {
  74. println!("Attempting to refresh the access token...");
  75. // Get the mint info to access OIDC configuration
  76. if let Some(mint_info) = wallet.get_mint_info().await? {
  77. match refresh_access_token(&mint_info, &token_data.refresh_token).await {
  78. Ok((new_access_token, new_refresh_token)) => {
  79. println!("Successfully refreshed access token");
  80. // Save the new tokens
  81. if let Err(e) = token_storage::save_tokens(
  82. work_dir,
  83. &mint_url,
  84. &new_access_token,
  85. &new_refresh_token,
  86. )
  87. .await
  88. {
  89. println!("Warning: Failed to save refreshed tokens: {}", e);
  90. }
  91. // Try setting the new access token
  92. if let Err(err) = wallet.set_cat(new_access_token).await {
  93. tracing::error!("Could not set refreshed cat: {}", err);
  94. return Err(anyhow::anyhow!(
  95. "Authentication failed even after token refresh"
  96. ));
  97. }
  98. // Set the refresh token
  99. wallet.set_refresh_token(new_refresh_token).await?;
  100. }
  101. Err(e) => {
  102. tracing::error!("Failed to refresh token: {}", e);
  103. return Err(anyhow::anyhow!("Failed to refresh access token: {}", e));
  104. }
  105. }
  106. }
  107. } else {
  108. return Err(anyhow::anyhow!(
  109. "Authentication failed and no refresh token available"
  110. ));
  111. }
  112. } else {
  113. // If we have a refresh token, set it
  114. if let Ok(Some(token_data)) = token_storage::get_token_for_mint(work_dir, &mint_url).await {
  115. tracing::info!("Attempting to use refresh access token to refresh auth token");
  116. wallet.set_refresh_token(token_data.refresh_token).await?;
  117. wallet.refresh_access_token().await?;
  118. }
  119. }
  120. println!("Attempting to mint blind auth");
  121. let amount = match sub_command_args.amount {
  122. Some(amount) => amount,
  123. None => {
  124. let mint_info = wallet
  125. .get_mint_info()
  126. .await?
  127. .ok_or(anyhow!("Unknown mint info"))?;
  128. mint_info
  129. .bat_max_mint()
  130. .ok_or(anyhow!("Unknown max bat mint"))?
  131. }
  132. };
  133. let proofs = wallet.mint_blind_auth(Amount::from(amount)).await?;
  134. println!("Received {} auth proofs for mint {mint_url}", proofs.len());
  135. Ok(())
  136. }
  137. async fn refresh_access_token(
  138. mint_info: &MintInfo,
  139. refresh_token: &str,
  140. ) -> Result<(String, String)> {
  141. let openid_discovery = mint_info
  142. .nuts
  143. .nut21
  144. .clone()
  145. .ok_or_else(|| anyhow::anyhow!("OIDC discovery information not available"))?
  146. .openid_discovery;
  147. let oidc_client = OidcClient::new(openid_discovery);
  148. // Get the token endpoint from the OIDC configuration
  149. let token_url = oidc_client.get_oidc_config().await?.token_endpoint;
  150. // Create the request parameters for token refresh
  151. let params = [
  152. ("grant_type", "refresh_token"),
  153. ("refresh_token", refresh_token),
  154. ("client_id", "cashu-client"), // Using default client ID
  155. ];
  156. // Make the token refresh request
  157. let client = reqwest::Client::new();
  158. let response = client.post(token_url).form(&params).send().await?;
  159. if !response.status().is_success() {
  160. return Err(anyhow::anyhow!(
  161. "Token refresh failed with status: {}",
  162. response.status()
  163. ));
  164. }
  165. let token_response: serde_json::Value = response.json().await?;
  166. let access_token = token_response["access_token"]
  167. .as_str()
  168. .ok_or_else(|| anyhow::anyhow!("No access token in refresh response"))?
  169. .to_string();
  170. // Get the new refresh token or use the old one if not provided
  171. let new_refresh_token = token_response["refresh_token"]
  172. .as_str()
  173. .unwrap_or(refresh_token)
  174. .to_string();
  175. Ok((access_token, new_refresh_token))
  176. }