mint_blind_auth.rs 6.8 KB

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