cat_device_login.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. use std::path::Path;
  2. use std::time::Duration;
  3. use anyhow::{anyhow, Result};
  4. use cdk::mint_url::MintUrl;
  5. use cdk::nuts::MintInfo;
  6. use cdk::wallet::MultiMintWallet;
  7. use cdk::OidcClient;
  8. use clap::Args;
  9. use serde::{Deserialize, Serialize};
  10. use tokio::time::sleep;
  11. use crate::token_storage;
  12. #[derive(Args, Serialize, Deserialize)]
  13. pub struct CatDeviceLoginSubCommand {
  14. /// Mint url
  15. mint_url: MintUrl,
  16. /// Client ID for OIDC authentication
  17. #[arg(default_value = "cashu-client")]
  18. #[arg(long)]
  19. client_id: String,
  20. }
  21. pub async fn cat_device_login(
  22. multi_mint_wallet: &MultiMintWallet,
  23. sub_command_args: &CatDeviceLoginSubCommand,
  24. work_dir: &Path,
  25. ) -> Result<()> {
  26. let mint_url = sub_command_args.mint_url.clone();
  27. // Ensure the mint exists
  28. if !multi_mint_wallet.has_mint(&mint_url).await {
  29. multi_mint_wallet.add_mint(mint_url.clone()).await?;
  30. }
  31. let mint_info = multi_mint_wallet
  32. .fetch_mint_info(&mint_url)
  33. .await?
  34. .ok_or(anyhow!("Mint info not found"))?;
  35. let (access_token, refresh_token) =
  36. get_device_code_token(&mint_info, &sub_command_args.client_id).await;
  37. // Save tokens to file in work directory
  38. if let Err(e) =
  39. token_storage::save_tokens(work_dir, &mint_url, &access_token, &refresh_token).await
  40. {
  41. println!("Warning: Failed to save tokens to file: {e}");
  42. } else {
  43. println!("Tokens saved to work directory");
  44. }
  45. // Print a cute ASCII cat
  46. println!("\nAuthentication successful! 🎉\n");
  47. println!("\nYour tokens:");
  48. println!("access_token: {access_token}");
  49. println!("refresh_token: {refresh_token}");
  50. Ok(())
  51. }
  52. async fn get_device_code_token(mint_info: &MintInfo, client_id: &str) -> (String, String) {
  53. let openid_discovery = mint_info
  54. .nuts
  55. .nut21
  56. .clone()
  57. .expect("Nut21 defined")
  58. .openid_discovery;
  59. let oidc_client = OidcClient::new(openid_discovery, None);
  60. // Get the OIDC configuration
  61. let oidc_config = oidc_client
  62. .get_oidc_config()
  63. .await
  64. .expect("Failed to get OIDC config");
  65. // Get the device authorization endpoint
  66. let device_auth_url = oidc_config.device_authorization_endpoint;
  67. // Make the device code request
  68. let client = reqwest::Client::new();
  69. let device_code_response = client
  70. .post(device_auth_url)
  71. .form(&[("client_id", client_id)])
  72. .send()
  73. .await
  74. .expect("Failed to send device code request");
  75. let device_code_data: serde_json::Value = device_code_response
  76. .json()
  77. .await
  78. .expect("Failed to parse device code response");
  79. let device_code = device_code_data["device_code"]
  80. .as_str()
  81. .expect("No device code in response");
  82. let user_code = device_code_data["user_code"]
  83. .as_str()
  84. .expect("No user code in response");
  85. let verification_uri = device_code_data["verification_uri"]
  86. .as_str()
  87. .expect("No verification URI in response");
  88. let verification_uri_complete = device_code_data["verification_uri_complete"]
  89. .as_str()
  90. .unwrap_or(verification_uri);
  91. let interval = device_code_data["interval"].as_u64().unwrap_or(5);
  92. println!("\nTo login, visit: {verification_uri}");
  93. println!("And enter code: {user_code}\n");
  94. if verification_uri_complete != verification_uri {
  95. println!("Or visit this URL directly: {verification_uri_complete}\n");
  96. }
  97. // Poll for the token
  98. let token_url = oidc_config.token_endpoint;
  99. loop {
  100. sleep(Duration::from_secs(interval)).await;
  101. let token_response = client
  102. .post(&token_url)
  103. .form(&[
  104. ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
  105. ("device_code", device_code),
  106. ("client_id", client_id),
  107. ])
  108. .send()
  109. .await
  110. .expect("Failed to send token request");
  111. if token_response.status().is_success() {
  112. let token_data: serde_json::Value = token_response
  113. .json()
  114. .await
  115. .expect("Failed to parse token response");
  116. let access_token = token_data["access_token"]
  117. .as_str()
  118. .expect("No access token in response")
  119. .to_string();
  120. let refresh_token = token_data["refresh_token"]
  121. .as_str()
  122. .expect("No refresh token in response")
  123. .to_string();
  124. return (access_token, refresh_token);
  125. } else {
  126. let error_data: serde_json::Value = token_response
  127. .json()
  128. .await
  129. .expect("Failed to parse error response");
  130. let error = error_data["error"].as_str().unwrap_or("unknown_error");
  131. // If the user hasn't completed the flow yet, continue polling
  132. if error == "authorization_pending" || error == "slow_down" {
  133. if error == "slow_down" {
  134. // If we're polling too fast, slow down
  135. sleep(Duration::from_secs(interval + 5)).await;
  136. }
  137. println!("Waiting for user to complete authentication...");
  138. continue;
  139. } else {
  140. // For other errors, exit with an error message
  141. panic!("Authentication failed: {error}");
  142. }
  143. }
  144. }
  145. }