fake_auth.rs 26 KB

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