cat_device_login.rs 5.4 KB

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