fake_auth.rs 25 KB

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