fake_auth.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  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
  275. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  276. .await
  277. .unwrap();
  278. let proofs = wallet
  279. .wait_and_mint_quote(
  280. quote.clone(),
  281. SplitTarget::default(),
  282. None,
  283. tokio::time::Duration::from_secs(60),
  284. )
  285. .await
  286. .expect("payment");
  287. assert!(proofs.total_amount().expect("Could not get proofs amount") == mint_amount);
  288. }
  289. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  290. async fn test_swap_with_auth() {
  291. let db = Arc::new(memory::empty().await.unwrap());
  292. let wallet = WalletBuilder::new()
  293. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  294. .unit(CurrencyUnit::Sat)
  295. .localstore(db.clone())
  296. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  297. .build()
  298. .expect("Wallet");
  299. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  300. let (access_token, _) = get_access_token(&mint_info).await;
  301. wallet.set_cat(access_token).await.unwrap();
  302. let wallet = Arc::new(wallet);
  303. wallet.mint_blind_auth(10.into()).await.unwrap();
  304. fund_wallet(wallet.clone(), 100.into()).await;
  305. let proofs = wallet
  306. .get_unspent_proofs()
  307. .await
  308. .expect("Could not get proofs");
  309. let swapped_proofs = wallet
  310. .swap(
  311. Some(proofs.total_amount().unwrap()),
  312. SplitTarget::default(),
  313. proofs.clone(),
  314. None,
  315. false,
  316. )
  317. .await
  318. .expect("Could not swap")
  319. .expect("Could not swap");
  320. let check_spent = wallet
  321. .check_proofs_spent(proofs.clone())
  322. .await
  323. .expect("Could not check proofs");
  324. for state in check_spent {
  325. if state.state != State::Spent {
  326. panic!("Input proofs should be spent");
  327. }
  328. }
  329. assert!(swapped_proofs.total_amount().unwrap() == proofs.total_amount().unwrap())
  330. }
  331. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  332. async fn test_melt_with_auth() {
  333. let db = Arc::new(memory::empty().await.unwrap());
  334. let wallet = WalletBuilder::new()
  335. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  336. .unit(CurrencyUnit::Sat)
  337. .localstore(db.clone())
  338. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  339. .build()
  340. .expect("Wallet");
  341. let mint_info = wallet
  342. .fetch_mint_info()
  343. .await
  344. .expect("Mint info not found")
  345. .expect("Mint info not found");
  346. let (access_token, _) = get_access_token(&mint_info).await;
  347. wallet.set_cat(access_token).await.unwrap();
  348. let wallet = Arc::new(wallet);
  349. wallet.mint_blind_auth(10.into()).await.unwrap();
  350. fund_wallet(wallet.clone(), 100.into()).await;
  351. let bolt11 = create_fake_invoice(2_000, "".to_string());
  352. let melt_quote = wallet
  353. .melt_quote(PaymentMethod::BOLT11, bolt11.to_string(), None, None)
  354. .await
  355. .expect("Could not get melt quote");
  356. let prepared = wallet
  357. .prepare_melt(&melt_quote.id, std::collections::HashMap::new())
  358. .await
  359. .expect("Could not prepare melt");
  360. let after_melt = prepared.confirm().await.expect("Could not melt");
  361. assert!(after_melt.state() == MeltQuoteState::Paid);
  362. }
  363. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  364. async fn test_mint_auth_over_max() {
  365. let db = Arc::new(memory::empty().await.unwrap());
  366. let wallet = WalletBuilder::new()
  367. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  368. .unit(CurrencyUnit::Sat)
  369. .localstore(db.clone())
  370. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  371. .build()
  372. .expect("Wallet");
  373. let wallet = Arc::new(wallet);
  374. let mint_info = wallet
  375. .fetch_mint_info()
  376. .await
  377. .expect("Mint info not found")
  378. .expect("Mint info not found");
  379. let (access_token, _) = get_access_token(&mint_info).await;
  380. wallet.set_cat(access_token).await.unwrap();
  381. let auth_proofs = wallet
  382. .mint_blind_auth((mint_info.nuts.nut22.expect("Auth enabled").bat_max_mint + 1).into())
  383. .await;
  384. assert!(
  385. matches!(
  386. auth_proofs,
  387. Err(Error::AmountOutofLimitRange(
  388. Amount::ZERO,
  389. Amount::ZERO,
  390. Amount::ZERO,
  391. ))
  392. ),
  393. "Expected amount out of limit error, got {:?}",
  394. auth_proofs
  395. );
  396. }
  397. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  398. async fn test_reuse_auth_proof() {
  399. let db = Arc::new(memory::empty().await.unwrap());
  400. let wallet = WalletBuilder::new()
  401. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  402. .unit(CurrencyUnit::Sat)
  403. .localstore(db.clone())
  404. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  405. .build()
  406. .expect("Wallet");
  407. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  408. let (access_token, _) = get_access_token(&mint_info).await;
  409. wallet.set_cat(access_token).await.unwrap();
  410. wallet.mint_blind_auth(1.into()).await.unwrap();
  411. let proofs = wallet
  412. .localstore
  413. .get_proofs(None, Some(CurrencyUnit::Auth), None, None)
  414. .await
  415. .unwrap();
  416. assert!(proofs.len() == 1);
  417. {
  418. let quote = wallet
  419. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  420. .await
  421. .expect("Quote should be allowed");
  422. assert!(quote.amount == Some(10.into()));
  423. }
  424. wallet
  425. .localstore
  426. .update_proofs(proofs, vec![])
  427. .await
  428. .unwrap();
  429. {
  430. let quote_res = wallet
  431. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  432. .await;
  433. assert!(
  434. matches!(quote_res, Err(Error::TokenAlreadySpent)),
  435. "Expected AuthRequired error, got {:?}",
  436. quote_res
  437. );
  438. }
  439. }
  440. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  441. async fn test_melt_with_invalid_auth() {
  442. let db = Arc::new(memory::empty().await.unwrap());
  443. let wallet = WalletBuilder::new()
  444. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  445. .unit(CurrencyUnit::Sat)
  446. .localstore(db.clone())
  447. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  448. .build()
  449. .expect("Wallet");
  450. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  451. let (access_token, _) = get_access_token(&mint_info).await;
  452. wallet.set_cat(access_token).await.unwrap();
  453. wallet.mint_blind_auth(10.into()).await.unwrap();
  454. fund_wallet(Arc::new(wallet.clone()), 1.into()).await;
  455. let proofs = wallet
  456. .get_unspent_proofs()
  457. .await
  458. .expect("wallet has proofs");
  459. println!("{:#?}", proofs);
  460. let proof = proofs.first().expect("wallet has one proof");
  461. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  462. {
  463. let invalid_auth_proof = AuthProof {
  464. keyset_id: proof.keyset_id,
  465. secret: proof.secret.clone(),
  466. c: proof.c,
  467. dleq: proof.dleq.clone(),
  468. };
  469. let _auth_token = AuthToken::BlindAuth(BlindAuthToken::new(invalid_auth_proof));
  470. let request = MintQuoteBolt11Request {
  471. unit: CurrencyUnit::Sat,
  472. amount: 10.into(),
  473. description: None,
  474. pubkey: None,
  475. };
  476. let quote_res = client.post_mint_quote(request).await;
  477. assert!(
  478. matches!(quote_res, Err(Error::BlindAuthRequired)),
  479. "Expected AuthRequired error, got {:?}",
  480. quote_res
  481. );
  482. }
  483. {
  484. let (access_token, _) = get_access_token(&mint_info).await;
  485. wallet.set_cat(access_token).await.unwrap();
  486. }
  487. }
  488. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  489. async fn test_refresh_access_token() {
  490. let db = Arc::new(memory::empty().await.unwrap());
  491. let wallet = WalletBuilder::new()
  492. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  493. .unit(CurrencyUnit::Sat)
  494. .localstore(db.clone())
  495. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  496. .build()
  497. .expect("Wallet");
  498. let mint_info = wallet
  499. .fetch_mint_info()
  500. .await
  501. .expect("mint info")
  502. .expect("could not get mint info");
  503. let (access_token, refresh_token) = get_access_token(&mint_info).await;
  504. // Set the initial access token and refresh token
  505. wallet.set_cat(access_token.clone()).await.unwrap();
  506. wallet
  507. .set_refresh_token(refresh_token.clone())
  508. .await
  509. .unwrap();
  510. // Mint some blind auth tokens with the initial access token
  511. wallet.mint_blind_auth(5.into()).await.unwrap();
  512. // Refresh the access token
  513. wallet.refresh_access_token().await.unwrap();
  514. // Verify we can still perform operations with the refreshed token
  515. let mint_amount: Amount = 10.into();
  516. // Try to mint more blind auth tokens with the refreshed token
  517. let auth_proofs = wallet.mint_blind_auth(5.into()).await.unwrap();
  518. assert_eq!(auth_proofs.len(), 5);
  519. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  520. assert_eq!(total_auth_proofs.len(), 10); // 5 from before refresh + 5 after refresh
  521. // Try to get a mint quote with the refreshed token
  522. let mint_quote = wallet
  523. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  524. .await
  525. .expect("failed to get mint quote with refreshed token");
  526. assert_eq!(mint_quote.amount, Some(mint_amount));
  527. // Verify the total number of auth tokens
  528. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  529. assert_eq!(total_auth_proofs.len(), 9); // 5 from before refresh + 5 after refresh - 1 for the quote
  530. }
  531. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  532. async fn test_invalid_refresh_token() {
  533. let db = Arc::new(memory::empty().await.unwrap());
  534. let wallet = WalletBuilder::new()
  535. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  536. .unit(CurrencyUnit::Sat)
  537. .localstore(db.clone())
  538. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  539. .build()
  540. .expect("Wallet");
  541. let mint_info = wallet
  542. .fetch_mint_info()
  543. .await
  544. .expect("mint info")
  545. .expect("could not get mint info");
  546. let (access_token, _) = get_access_token(&mint_info).await;
  547. // Set the initial access token
  548. wallet.set_cat(access_token.clone()).await.unwrap();
  549. // Set an invalid refresh token
  550. wallet
  551. .set_refresh_token("invalid_refresh_token".to_string())
  552. .await
  553. .unwrap();
  554. // Attempt to refresh the access token with an invalid refresh token
  555. let refresh_result = wallet.refresh_access_token().await;
  556. // Should fail with an error
  557. assert!(refresh_result.is_err(), "Expected refresh token error");
  558. }
  559. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  560. async fn test_auth_token_spending_order() {
  561. let db = Arc::new(memory::empty().await.unwrap());
  562. let wallet = WalletBuilder::new()
  563. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  564. .unit(CurrencyUnit::Sat)
  565. .localstore(db.clone())
  566. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  567. .build()
  568. .expect("Wallet");
  569. let mint_info = wallet
  570. .fetch_mint_info()
  571. .await
  572. .expect("mint info")
  573. .expect("could not get mint info");
  574. let (access_token, _) = get_access_token(&mint_info).await;
  575. wallet.set_cat(access_token).await.unwrap();
  576. // Mint auth tokens in two batches to test ordering
  577. wallet.mint_blind_auth(2.into()).await.unwrap();
  578. // Get the first batch of auth proofs
  579. let first_batch = wallet.get_unspent_auth_proofs().await.unwrap();
  580. assert_eq!(first_batch.len(), 2);
  581. // Mint a second batch
  582. wallet.mint_blind_auth(3.into()).await.unwrap();
  583. // Get all auth proofs
  584. let all_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  585. assert_eq!(all_proofs.len(), 5);
  586. // Use tokens and verify they're used in the expected order (FIFO)
  587. for i in 0..3 {
  588. let mint_quote = wallet
  589. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  590. .await
  591. .expect("failed to get mint quote");
  592. assert_eq!(mint_quote.amount, Some(10.into()));
  593. // Check remaining tokens after each operation
  594. let remaining = wallet.get_unspent_auth_proofs().await.unwrap();
  595. assert_eq!(
  596. remaining.len(),
  597. 5 - (i + 1),
  598. "Expected {} remaining auth tokens after {} operations",
  599. 5 - (i + 1),
  600. i + 1
  601. );
  602. }
  603. }
  604. async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
  605. let openid_discovery = mint_info
  606. .nuts
  607. .nut21
  608. .clone()
  609. .expect("Nut21 defined")
  610. .openid_discovery;
  611. let oidc_client = OidcClient::new(openid_discovery, None);
  612. // Get the token endpoint from the OIDC configuration
  613. let token_url = oidc_client
  614. .get_oidc_config()
  615. .await
  616. .expect("Failed to get OIDC config")
  617. .token_endpoint;
  618. // Create the request parameters
  619. let (user, password) = get_oidc_credentials();
  620. let params = [
  621. ("grant_type", "password"),
  622. ("client_id", "cashu-client"),
  623. ("username", &user),
  624. ("password", &password),
  625. ];
  626. // Make the token request directly
  627. let client = CommonHttpClient::new();
  628. let token_response: serde_json::Value = client
  629. .post(&token_url)
  630. .form(&params)
  631. .send()
  632. .await
  633. .expect("Failed to send token request")
  634. .json()
  635. .await
  636. .expect("Failed to parse token response");
  637. let access_token = token_response["access_token"]
  638. .as_str()
  639. .expect("No access token in response")
  640. .to_string();
  641. let refresh_token = token_response["refresh_token"]
  642. .as_str()
  643. .expect("No access token in response")
  644. .to_string();
  645. (access_token, refresh_token)
  646. }
  647. /// Get a new access token with custom credentials
  648. async fn get_custom_access_token(
  649. mint_info: &MintInfo,
  650. username: &str,
  651. password: &str,
  652. ) -> Result<(String, String), Error> {
  653. let openid_discovery = mint_info
  654. .nuts
  655. .nut21
  656. .clone()
  657. .expect("Nut21 defined")
  658. .openid_discovery;
  659. let oidc_client = OidcClient::new(openid_discovery, None);
  660. // Get the token endpoint from the OIDC configuration
  661. let token_url = oidc_client
  662. .get_oidc_config()
  663. .await
  664. .map_err(|_| Error::Custom("Failed to get OIDC config".to_string()))?
  665. .token_endpoint;
  666. // Create the request parameters
  667. let params = [
  668. ("grant_type", "password"),
  669. ("client_id", "cashu-client"),
  670. ("username", username),
  671. ("password", password),
  672. ];
  673. // Make the token request directly
  674. let client = CommonHttpClient::new();
  675. let response = client
  676. .post(&token_url)
  677. .form(&params)
  678. .send()
  679. .await
  680. .map_err(|_| Error::Custom("Failed to send token request".to_string()))?;
  681. if !response.is_success() {
  682. return Err(Error::Custom(format!(
  683. "Token request failed with status: {}",
  684. response.status()
  685. )));
  686. }
  687. let token_response: serde_json::Value = response
  688. .json()
  689. .await
  690. .map_err(|_| Error::Custom("Failed to parse token response".to_string()))?;
  691. let access_token = token_response["access_token"]
  692. .as_str()
  693. .ok_or_else(|| Error::Custom("No access token in response".to_string()))?
  694. .to_string();
  695. let refresh_token = token_response["refresh_token"]
  696. .as_str()
  697. .ok_or_else(|| Error::Custom("No refresh token in response".to_string()))?
  698. .to_string();
  699. Ok((access_token, refresh_token))
  700. }