fake_auth.rs 25 KB

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