fake_auth.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. use std::env;
  2. use std::str::FromStr;
  3. use std::sync::Arc;
  4. use bip39::Mnemonic;
  5. use cashu::{MintAuthRequest, MintInfo};
  6. use cdk::amount::{Amount, SplitTarget};
  7. use cdk::mint_url::MintUrl;
  8. use cdk::nuts::nut00::{KnownMethod, ProofsMethods};
  9. use cdk::nuts::{
  10. AuthProof, AuthToken, BlindAuthToken, CheckStateRequest, CurrencyUnit, MeltQuoteBolt11Request,
  11. MeltQuoteState, MeltRequest, MintQuoteBolt11Request, MintRequest, PaymentMethod,
  12. RestoreRequest, State, SwapRequest,
  13. };
  14. use cdk::wallet::{AuthHttpClient, AuthMintConnector, HttpClient, MintConnector, WalletBuilder};
  15. use cdk::{Error, OidcClient};
  16. use cdk_fake_wallet::create_fake_invoice;
  17. use cdk_http_client::HttpClient as CommonHttpClient;
  18. use cdk_integration_tests::fund_wallet;
  19. use cdk_sqlite::wallet::memory;
  20. const MINT_URL: &str = "http://127.0.0.1:8087";
  21. const ENV_OIDC_USER: &str = "CDK_TEST_OIDC_USER";
  22. const ENV_OIDC_PASSWORD: &str = "CDK_TEST_OIDC_PASSWORD";
  23. fn get_oidc_credentials() -> (String, String) {
  24. let user = env::var(ENV_OIDC_USER).unwrap_or_else(|_| "test".to_string());
  25. let password = env::var(ENV_OIDC_PASSWORD).unwrap_or_else(|_| "test".to_string());
  26. (user, password)
  27. }
  28. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  29. async fn test_invalid_credentials() {
  30. let db = Arc::new(memory::empty().await.unwrap());
  31. let wallet = WalletBuilder::new()
  32. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  33. .unit(CurrencyUnit::Sat)
  34. .localstore(db.clone())
  35. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  36. .build()
  37. .expect("Wallet");
  38. let mint_info = wallet
  39. .fetch_mint_info()
  40. .await
  41. .expect("mint info")
  42. .expect("could not get mint info");
  43. // Try to get a token with invalid credentials
  44. let token_result =
  45. get_custom_access_token(&mint_info, "invalid_user", "invalid_password").await;
  46. // Should fail with an error
  47. assert!(
  48. token_result.is_err(),
  49. "Expected authentication to fail with invalid credentials"
  50. );
  51. }
  52. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  53. async fn test_quote_status_without_auth() {
  54. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  55. // Test mint quote status
  56. {
  57. let quote_res = client
  58. .get_mint_quote_status("123e4567-e89b-12d3-a456-426614174000")
  59. .await;
  60. assert!(
  61. matches!(quote_res, Err(Error::BlindAuthRequired)),
  62. "Expected AuthRequired error, got {:?}",
  63. quote_res
  64. );
  65. }
  66. // Test melt quote status
  67. {
  68. let quote_res = client
  69. .get_melt_quote_status("123e4567-e89b-12d3-a456-426614174000")
  70. .await;
  71. assert!(
  72. matches!(quote_res, Err(Error::BlindAuthRequired)),
  73. "Expected AuthRequired error, got {:?}",
  74. quote_res
  75. );
  76. }
  77. }
  78. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  79. async fn test_mint_without_auth() {
  80. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  81. {
  82. let request = MintQuoteBolt11Request {
  83. unit: CurrencyUnit::Sat,
  84. amount: 10.into(),
  85. description: None,
  86. pubkey: None,
  87. };
  88. let quote_res = client.post_mint_quote(request).await;
  89. assert!(
  90. matches!(quote_res, Err(Error::BlindAuthRequired)),
  91. "Expected AuthRequired error, got {:?}",
  92. quote_res
  93. );
  94. }
  95. {
  96. let request = MintRequest {
  97. quote: "123e4567-e89b-12d3-a456-426614174000".to_string(),
  98. outputs: vec![],
  99. signature: None,
  100. };
  101. let mint_res = client
  102. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
  103. .await;
  104. assert!(
  105. matches!(mint_res, Err(Error::BlindAuthRequired)),
  106. "Expected AuthRequired error, got {:?}",
  107. mint_res
  108. );
  109. }
  110. {
  111. let mint_res = client
  112. .get_mint_quote_status("123e4567-e89b-12d3-a456-426614174000")
  113. .await;
  114. assert!(
  115. matches!(mint_res, Err(Error::BlindAuthRequired)),
  116. "Expected AuthRequired error, got {:?}",
  117. mint_res
  118. );
  119. }
  120. }
  121. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  122. async fn test_mint_bat_without_cat() {
  123. let client = AuthHttpClient::new(MintUrl::from_str(MINT_URL).expect("valid mint url"), None);
  124. let res = client
  125. .post_mint_blind_auth(MintAuthRequest { outputs: vec![] })
  126. .await;
  127. assert!(
  128. matches!(res, Err(Error::ClearAuthRequired)),
  129. "Expected AuthRequired error, got {:?}",
  130. res
  131. );
  132. }
  133. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  134. async fn test_swap_without_auth() {
  135. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  136. let request = SwapRequest::new(vec![], vec![]);
  137. let quote_res = client.post_swap(request).await;
  138. assert!(
  139. matches!(quote_res, Err(Error::BlindAuthRequired)),
  140. "Expected AuthRequired error, got {:?}",
  141. quote_res
  142. );
  143. }
  144. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  145. async fn test_melt_without_auth() {
  146. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  147. // Test melt quote request
  148. {
  149. let request = MeltQuoteBolt11Request {
  150. request: create_fake_invoice(100, "".to_string()),
  151. unit: CurrencyUnit::Sat,
  152. options: None,
  153. };
  154. let quote_res = client.post_melt_quote(request).await;
  155. assert!(
  156. matches!(quote_res, Err(Error::BlindAuthRequired)),
  157. "Expected AuthRequired error, got {:?}",
  158. quote_res
  159. );
  160. }
  161. // Test melt quote
  162. {
  163. let request = MeltQuoteBolt11Request {
  164. request: create_fake_invoice(100, "".to_string()),
  165. unit: CurrencyUnit::Sat,
  166. options: None,
  167. };
  168. let quote_res = client.post_melt_quote(request).await;
  169. assert!(
  170. matches!(quote_res, Err(Error::BlindAuthRequired)),
  171. "Expected AuthRequired error, got {:?}",
  172. quote_res
  173. );
  174. }
  175. // Test melt
  176. {
  177. let request = MeltRequest::new(
  178. "123e4567-e89b-12d3-a456-426614174000".to_string(),
  179. vec![],
  180. None,
  181. );
  182. let melt_res = client
  183. .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), request)
  184. .await;
  185. assert!(
  186. matches!(melt_res, Err(Error::BlindAuthRequired)),
  187. "Expected AuthRequired error, got {:?}",
  188. melt_res
  189. );
  190. }
  191. // Check melt quote state
  192. {
  193. let melt_res = client
  194. .get_melt_quote_status("123e4567-e89b-12d3-a456-426614174000")
  195. .await;
  196. assert!(
  197. matches!(melt_res, Err(Error::BlindAuthRequired)),
  198. "Expected AuthRequired error, got {:?}",
  199. melt_res
  200. );
  201. }
  202. }
  203. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  204. async fn test_check_without_auth() {
  205. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  206. let request = CheckStateRequest { ys: vec![] };
  207. let quote_res = client.post_check_state(request).await;
  208. assert!(
  209. matches!(quote_res, Err(Error::BlindAuthRequired)),
  210. "Expected AuthRequired error, got {:?}",
  211. quote_res
  212. );
  213. }
  214. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  215. async fn test_restore_without_auth() {
  216. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  217. let request = RestoreRequest { outputs: vec![] };
  218. let restore_res = client.post_restore(request).await;
  219. assert!(
  220. matches!(restore_res, Err(Error::BlindAuthRequired)),
  221. "Expected AuthRequired error, got {:?}",
  222. restore_res
  223. );
  224. }
  225. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  226. async fn test_mint_blind_auth() {
  227. let db = Arc::new(memory::empty().await.unwrap());
  228. let wallet = WalletBuilder::new()
  229. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  230. .unit(CurrencyUnit::Sat)
  231. .localstore(db.clone())
  232. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  233. .build()
  234. .expect("Wallet");
  235. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  236. let (access_token, _) = get_access_token(&mint_info).await;
  237. wallet.set_cat(access_token).await.unwrap();
  238. wallet
  239. .mint_blind_auth(10.into())
  240. .await
  241. .expect("Could not mint blind auth");
  242. let proofs = wallet
  243. .get_unspent_auth_proofs()
  244. .await
  245. .expect("Could not get auth proofs");
  246. assert!(proofs.len() == 10)
  247. }
  248. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  249. async fn test_mint_with_auth() {
  250. let db = Arc::new(memory::empty().await.unwrap());
  251. let wallet = WalletBuilder::new()
  252. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  253. .unit(CurrencyUnit::Sat)
  254. .localstore(db.clone())
  255. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  256. .build()
  257. .expect("Wallet");
  258. let mint_info = wallet
  259. .fetch_mint_info()
  260. .await
  261. .expect("mint info")
  262. .expect("could not get mint info");
  263. let (access_token, _) = get_access_token(&mint_info).await;
  264. println!("st{}", access_token);
  265. wallet.set_cat(access_token).await.unwrap();
  266. wallet
  267. .mint_blind_auth(10.into())
  268. .await
  269. .expect("Could not mint blind auth");
  270. let wallet = Arc::new(wallet);
  271. let mint_amount: Amount = 100.into();
  272. let quote = wallet
  273. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  274. .await
  275. .unwrap();
  276. let proofs = wallet
  277. .wait_and_mint_quote(
  278. quote.clone(),
  279. SplitTarget::default(),
  280. None,
  281. tokio::time::Duration::from_secs(60),
  282. )
  283. .await
  284. .expect("payment");
  285. assert!(proofs.total_amount().expect("Could not get proofs amount") == mint_amount);
  286. }
  287. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  288. async fn test_swap_with_auth() {
  289. let db = Arc::new(memory::empty().await.unwrap());
  290. let wallet = WalletBuilder::new()
  291. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  292. .unit(CurrencyUnit::Sat)
  293. .localstore(db.clone())
  294. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  295. .build()
  296. .expect("Wallet");
  297. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  298. let (access_token, _) = get_access_token(&mint_info).await;
  299. wallet.set_cat(access_token).await.unwrap();
  300. let wallet = Arc::new(wallet);
  301. wallet.mint_blind_auth(10.into()).await.unwrap();
  302. fund_wallet(wallet.clone(), 100.into()).await;
  303. let proofs = wallet
  304. .get_unspent_proofs()
  305. .await
  306. .expect("Could not get proofs");
  307. let swapped_proofs = wallet
  308. .swap(
  309. Some(proofs.total_amount().unwrap()),
  310. SplitTarget::default(),
  311. proofs.clone(),
  312. None,
  313. false,
  314. )
  315. .await
  316. .expect("Could not swap")
  317. .expect("Could not swap");
  318. let check_spent = wallet
  319. .check_proofs_spent(proofs.clone())
  320. .await
  321. .expect("Could not check proofs");
  322. for state in check_spent {
  323. if state.state != State::Spent {
  324. panic!("Input proofs should be spent");
  325. }
  326. }
  327. assert!(swapped_proofs.total_amount().unwrap() == proofs.total_amount().unwrap())
  328. }
  329. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  330. async fn test_melt_with_auth() {
  331. let db = Arc::new(memory::empty().await.unwrap());
  332. let wallet = WalletBuilder::new()
  333. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  334. .unit(CurrencyUnit::Sat)
  335. .localstore(db.clone())
  336. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  337. .build()
  338. .expect("Wallet");
  339. let mint_info = wallet
  340. .fetch_mint_info()
  341. .await
  342. .expect("Mint info not found")
  343. .expect("Mint info not found");
  344. let (access_token, _) = get_access_token(&mint_info).await;
  345. wallet.set_cat(access_token).await.unwrap();
  346. let wallet = Arc::new(wallet);
  347. wallet.mint_blind_auth(10.into()).await.unwrap();
  348. fund_wallet(wallet.clone(), 100.into()).await;
  349. let bolt11 = create_fake_invoice(2_000, "".to_string());
  350. let melt_quote = wallet
  351. .melt_quote(PaymentMethod::BOLT11, bolt11.to_string(), None, None)
  352. .await
  353. .expect("Could not get melt quote");
  354. let prepared = wallet
  355. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  356. .await
  357. .expect("Could not prepare melt");
  358. let after_melt = prepared.confirm().await.expect("Could not melt");
  359. assert!(after_melt.state() == MeltQuoteState::Paid);
  360. }
  361. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  362. async fn test_mint_auth_over_max() {
  363. let db = Arc::new(memory::empty().await.unwrap());
  364. let wallet = WalletBuilder::new()
  365. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  366. .unit(CurrencyUnit::Sat)
  367. .localstore(db.clone())
  368. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  369. .build()
  370. .expect("Wallet");
  371. let wallet = Arc::new(wallet);
  372. let mint_info = wallet
  373. .fetch_mint_info()
  374. .await
  375. .expect("Mint info not found")
  376. .expect("Mint info not found");
  377. let (access_token, _) = get_access_token(&mint_info).await;
  378. wallet.set_cat(access_token).await.unwrap();
  379. let auth_proofs = wallet
  380. .mint_blind_auth((mint_info.nuts.nut22.expect("Auth enabled").bat_max_mint + 1).into())
  381. .await;
  382. assert!(
  383. matches!(
  384. auth_proofs,
  385. Err(Error::AmountOutofLimitRange(
  386. Amount::ZERO,
  387. Amount::ZERO,
  388. Amount::ZERO,
  389. ))
  390. ),
  391. "Expected amount out of limit error, got {:?}",
  392. auth_proofs
  393. );
  394. }
  395. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  396. async fn test_reuse_auth_proof() {
  397. let db = Arc::new(memory::empty().await.unwrap());
  398. let wallet = WalletBuilder::new()
  399. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  400. .unit(CurrencyUnit::Sat)
  401. .localstore(db.clone())
  402. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  403. .build()
  404. .expect("Wallet");
  405. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  406. let (access_token, _) = get_access_token(&mint_info).await;
  407. wallet.set_cat(access_token).await.unwrap();
  408. wallet.mint_blind_auth(1.into()).await.unwrap();
  409. let proofs = wallet
  410. .localstore
  411. .get_proofs(None, Some(CurrencyUnit::Auth), None, None)
  412. .await
  413. .unwrap();
  414. assert!(proofs.len() == 1);
  415. {
  416. let quote = wallet
  417. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  418. .await
  419. .expect("Quote should be allowed");
  420. assert!(quote.amount == Some(10.into()));
  421. }
  422. wallet
  423. .localstore
  424. .update_proofs(proofs, vec![])
  425. .await
  426. .unwrap();
  427. {
  428. let quote_res = wallet
  429. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  430. .await;
  431. assert!(
  432. matches!(quote_res, Err(Error::TokenAlreadySpent)),
  433. "Expected AuthRequired error, got {:?}",
  434. quote_res
  435. );
  436. }
  437. }
  438. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  439. async fn test_melt_with_invalid_auth() {
  440. let db = Arc::new(memory::empty().await.unwrap());
  441. let wallet = WalletBuilder::new()
  442. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  443. .unit(CurrencyUnit::Sat)
  444. .localstore(db.clone())
  445. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  446. .build()
  447. .expect("Wallet");
  448. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  449. let (access_token, _) = get_access_token(&mint_info).await;
  450. wallet.set_cat(access_token).await.unwrap();
  451. wallet.mint_blind_auth(10.into()).await.unwrap();
  452. fund_wallet(Arc::new(wallet.clone()), 1.into()).await;
  453. let proofs = wallet
  454. .get_unspent_proofs()
  455. .await
  456. .expect("wallet has proofs");
  457. println!("{:#?}", proofs);
  458. let proof = proofs.first().expect("wallet has one proof");
  459. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  460. {
  461. let invalid_auth_proof = AuthProof {
  462. keyset_id: proof.keyset_id,
  463. secret: proof.secret.clone(),
  464. c: proof.c,
  465. dleq: proof.dleq.clone(),
  466. };
  467. let _auth_token = AuthToken::BlindAuth(BlindAuthToken::new(invalid_auth_proof));
  468. let request = MintQuoteBolt11Request {
  469. unit: CurrencyUnit::Sat,
  470. amount: 10.into(),
  471. description: None,
  472. pubkey: None,
  473. };
  474. let quote_res = client.post_mint_quote(request).await;
  475. assert!(
  476. matches!(quote_res, Err(Error::BlindAuthRequired)),
  477. "Expected AuthRequired error, got {:?}",
  478. quote_res
  479. );
  480. }
  481. {
  482. let (access_token, _) = get_access_token(&mint_info).await;
  483. wallet.set_cat(access_token).await.unwrap();
  484. }
  485. }
  486. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  487. async fn test_refresh_access_token() {
  488. let db = Arc::new(memory::empty().await.unwrap());
  489. let wallet = WalletBuilder::new()
  490. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  491. .unit(CurrencyUnit::Sat)
  492. .localstore(db.clone())
  493. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  494. .build()
  495. .expect("Wallet");
  496. let mint_info = wallet
  497. .fetch_mint_info()
  498. .await
  499. .expect("mint info")
  500. .expect("could not get mint info");
  501. let (access_token, refresh_token) = get_access_token(&mint_info).await;
  502. // Set the initial access token and refresh token
  503. wallet.set_cat(access_token.clone()).await.unwrap();
  504. wallet
  505. .set_refresh_token(refresh_token.clone())
  506. .await
  507. .unwrap();
  508. // Mint some blind auth tokens with the initial access token
  509. wallet.mint_blind_auth(5.into()).await.unwrap();
  510. // Refresh the access token
  511. wallet.refresh_access_token().await.unwrap();
  512. // Verify we can still perform operations with the refreshed token
  513. let mint_amount: Amount = 10.into();
  514. // Try to mint more blind auth tokens with the refreshed token
  515. let auth_proofs = wallet.mint_blind_auth(5.into()).await.unwrap();
  516. assert_eq!(auth_proofs.len(), 5);
  517. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  518. assert_eq!(total_auth_proofs.len(), 10); // 5 from before refresh + 5 after refresh
  519. // Try to get a mint quote with the refreshed token
  520. let mint_quote = wallet
  521. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  522. .await
  523. .expect("failed to get mint quote with refreshed token");
  524. assert_eq!(mint_quote.amount, Some(mint_amount));
  525. // Verify the total number of auth tokens
  526. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  527. assert_eq!(total_auth_proofs.len(), 9); // 5 from before refresh + 5 after refresh - 1 for the quote
  528. }
  529. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  530. async fn test_invalid_refresh_token() {
  531. let db = Arc::new(memory::empty().await.unwrap());
  532. let wallet = WalletBuilder::new()
  533. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  534. .unit(CurrencyUnit::Sat)
  535. .localstore(db.clone())
  536. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  537. .build()
  538. .expect("Wallet");
  539. let mint_info = wallet
  540. .fetch_mint_info()
  541. .await
  542. .expect("mint info")
  543. .expect("could not get mint info");
  544. let (access_token, _) = get_access_token(&mint_info).await;
  545. // Set the initial access token
  546. wallet.set_cat(access_token.clone()).await.unwrap();
  547. // Set an invalid refresh token
  548. wallet
  549. .set_refresh_token("invalid_refresh_token".to_string())
  550. .await
  551. .unwrap();
  552. // Attempt to refresh the access token with an invalid refresh token
  553. let refresh_result = wallet.refresh_access_token().await;
  554. // Should fail with an error
  555. assert!(refresh_result.is_err(), "Expected refresh token error");
  556. }
  557. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  558. async fn test_auth_token_spending_order() {
  559. let db = Arc::new(memory::empty().await.unwrap());
  560. let wallet = WalletBuilder::new()
  561. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  562. .unit(CurrencyUnit::Sat)
  563. .localstore(db.clone())
  564. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  565. .build()
  566. .expect("Wallet");
  567. let mint_info = wallet
  568. .fetch_mint_info()
  569. .await
  570. .expect("mint info")
  571. .expect("could not get mint info");
  572. let (access_token, _) = get_access_token(&mint_info).await;
  573. wallet.set_cat(access_token).await.unwrap();
  574. // Mint auth tokens in two batches to test ordering
  575. wallet.mint_blind_auth(2.into()).await.unwrap();
  576. // Get the first batch of auth proofs
  577. let first_batch = wallet.get_unspent_auth_proofs().await.unwrap();
  578. assert_eq!(first_batch.len(), 2);
  579. // Mint a second batch
  580. wallet.mint_blind_auth(3.into()).await.unwrap();
  581. // Get all auth proofs
  582. let all_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  583. assert_eq!(all_proofs.len(), 5);
  584. // Use tokens and verify they're used in the expected order (FIFO)
  585. for i in 0..3 {
  586. let mint_quote = wallet
  587. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  588. .await
  589. .expect("failed to get mint quote");
  590. assert_eq!(mint_quote.amount, Some(10.into()));
  591. // Check remaining tokens after each operation
  592. let remaining = wallet.get_unspent_auth_proofs().await.unwrap();
  593. assert_eq!(
  594. remaining.len(),
  595. 5 - (i + 1),
  596. "Expected {} remaining auth tokens after {} operations",
  597. 5 - (i + 1),
  598. i + 1
  599. );
  600. }
  601. }
  602. async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
  603. let openid_discovery = mint_info
  604. .nuts
  605. .nut21
  606. .clone()
  607. .expect("Nut21 defined")
  608. .openid_discovery;
  609. let oidc_client = OidcClient::new(openid_discovery, None);
  610. // Get the token endpoint from the OIDC configuration
  611. let token_url = oidc_client
  612. .get_oidc_config()
  613. .await
  614. .expect("Failed to get OIDC config")
  615. .token_endpoint;
  616. // Create the request parameters
  617. let (user, password) = get_oidc_credentials();
  618. let params = [
  619. ("grant_type", "password"),
  620. ("client_id", "cashu-client"),
  621. ("username", &user),
  622. ("password", &password),
  623. ];
  624. // Make the token request directly
  625. let client = CommonHttpClient::new();
  626. let token_response: serde_json::Value = client
  627. .post(&token_url)
  628. .form(&params)
  629. .send()
  630. .await
  631. .expect("Failed to send token request")
  632. .json()
  633. .await
  634. .expect("Failed to parse token response");
  635. let access_token = token_response["access_token"]
  636. .as_str()
  637. .expect("No access token in response")
  638. .to_string();
  639. let refresh_token = token_response["refresh_token"]
  640. .as_str()
  641. .expect("No access token in response")
  642. .to_string();
  643. (access_token, refresh_token)
  644. }
  645. /// Get a new access token with custom credentials
  646. async fn get_custom_access_token(
  647. mint_info: &MintInfo,
  648. username: &str,
  649. password: &str,
  650. ) -> Result<(String, String), Error> {
  651. let openid_discovery = mint_info
  652. .nuts
  653. .nut21
  654. .clone()
  655. .expect("Nut21 defined")
  656. .openid_discovery;
  657. let oidc_client = OidcClient::new(openid_discovery, None);
  658. // Get the token endpoint from the OIDC configuration
  659. let token_url = oidc_client
  660. .get_oidc_config()
  661. .await
  662. .map_err(|_| Error::Custom("Failed to get OIDC config".to_string()))?
  663. .token_endpoint;
  664. // Create the request parameters
  665. let params = [
  666. ("grant_type", "password"),
  667. ("client_id", "cashu-client"),
  668. ("username", username),
  669. ("password", password),
  670. ];
  671. // Make the token request directly
  672. let client = CommonHttpClient::new();
  673. let response = client
  674. .post(&token_url)
  675. .form(&params)
  676. .send()
  677. .await
  678. .map_err(|_| Error::Custom("Failed to send token request".to_string()))?;
  679. if !response.is_success() {
  680. return Err(Error::Custom(format!(
  681. "Token request failed with status: {}",
  682. response.status()
  683. )));
  684. }
  685. let token_response: serde_json::Value = response
  686. .json()
  687. .await
  688. .map_err(|_| Error::Custom("Failed to parse token response".to_string()))?;
  689. let access_token = token_response["access_token"]
  690. .as_str()
  691. .ok_or_else(|| Error::Custom("No access token in response".to_string()))?
  692. .to_string();
  693. let refresh_token = token_response["refresh_token"]
  694. .as_str()
  695. .ok_or_else(|| Error::Custom("No refresh token in response".to_string()))?
  696. .to_string();
  697. Ok((access_token, refresh_token))
  698. }