fake_auth.rs 25 KB

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