fake_auth.rs 25 KB

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