fake_auth.rs 25 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_integration_tests::fund_wallet;
  18. use cdk_sqlite::wallet::memory;
  19. const MINT_URL: &str = "http://127.0.0.1:8087";
  20. const ENV_OIDC_USER: &str = "CDK_TEST_OIDC_USER";
  21. const ENV_OIDC_PASSWORD: &str = "CDK_TEST_OIDC_PASSWORD";
  22. fn get_oidc_credentials() -> (String, String) {
  23. let user = env::var(ENV_OIDC_USER).unwrap_or_else(|_| "test".to_string());
  24. let password = env::var(ENV_OIDC_PASSWORD).unwrap_or_else(|_| "test".to_string());
  25. (user, password)
  26. }
  27. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  28. async fn test_invalid_credentials() {
  29. let db = Arc::new(memory::empty().await.unwrap());
  30. let wallet = WalletBuilder::new()
  31. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  32. .unit(CurrencyUnit::Sat)
  33. .localstore(db.clone())
  34. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  35. .build()
  36. .expect("Wallet");
  37. let mint_info = wallet
  38. .fetch_mint_info()
  39. .await
  40. .expect("mint info")
  41. .expect("could not get mint info");
  42. // Try to get a token with invalid credentials
  43. let token_result =
  44. get_custom_access_token(&mint_info, "invalid_user", "invalid_password").await;
  45. // Should fail with an error
  46. assert!(
  47. token_result.is_err(),
  48. "Expected authentication to fail with invalid credentials"
  49. );
  50. }
  51. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  52. async fn test_quote_status_without_auth() {
  53. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  54. // Test mint quote status
  55. {
  56. let quote_res = client
  57. .get_mint_quote_status("123e4567-e89b-12d3-a456-426614174000")
  58. .await;
  59. assert!(
  60. matches!(quote_res, Err(Error::BlindAuthRequired)),
  61. "Expected AuthRequired error, got {:?}",
  62. quote_res
  63. );
  64. }
  65. // Test melt quote status
  66. {
  67. let quote_res = client
  68. .get_melt_quote_status("123e4567-e89b-12d3-a456-426614174000")
  69. .await;
  70. assert!(
  71. matches!(quote_res, Err(Error::BlindAuthRequired)),
  72. "Expected AuthRequired error, got {:?}",
  73. quote_res
  74. );
  75. }
  76. }
  77. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  78. async fn test_mint_without_auth() {
  79. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  80. {
  81. let request = MintQuoteBolt11Request {
  82. unit: CurrencyUnit::Sat,
  83. amount: 10.into(),
  84. description: None,
  85. pubkey: None,
  86. };
  87. let quote_res = client.post_mint_quote(request).await;
  88. assert!(
  89. matches!(quote_res, Err(Error::BlindAuthRequired)),
  90. "Expected AuthRequired error, got {:?}",
  91. quote_res
  92. );
  93. }
  94. {
  95. let request = MintRequest {
  96. quote: "123e4567-e89b-12d3-a456-426614174000".to_string(),
  97. outputs: vec![],
  98. signature: None,
  99. };
  100. let mint_res = client
  101. .post_mint(&PaymentMethod::Known(KnownMethod::Bolt11), request)
  102. .await;
  103. assert!(
  104. matches!(mint_res, Err(Error::BlindAuthRequired)),
  105. "Expected AuthRequired error, got {:?}",
  106. mint_res
  107. );
  108. }
  109. {
  110. let mint_res = client
  111. .get_mint_quote_status("123e4567-e89b-12d3-a456-426614174000")
  112. .await;
  113. assert!(
  114. matches!(mint_res, Err(Error::BlindAuthRequired)),
  115. "Expected AuthRequired error, got {:?}",
  116. mint_res
  117. );
  118. }
  119. }
  120. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  121. async fn test_mint_bat_without_cat() {
  122. let client = AuthHttpClient::new(MintUrl::from_str(MINT_URL).expect("valid mint url"), None);
  123. let res = client
  124. .post_mint_blind_auth(MintAuthRequest { outputs: vec![] })
  125. .await;
  126. assert!(
  127. matches!(res, Err(Error::ClearAuthRequired)),
  128. "Expected AuthRequired error, got {:?}",
  129. res
  130. );
  131. }
  132. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  133. async fn test_swap_without_auth() {
  134. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  135. let request = SwapRequest::new(vec![], vec![]);
  136. let quote_res = client.post_swap(request).await;
  137. assert!(
  138. matches!(quote_res, Err(Error::BlindAuthRequired)),
  139. "Expected AuthRequired error, got {:?}",
  140. quote_res
  141. );
  142. }
  143. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  144. async fn test_melt_without_auth() {
  145. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  146. // Test melt quote request
  147. {
  148. let request = MeltQuoteBolt11Request {
  149. request: create_fake_invoice(100, "".to_string()),
  150. unit: CurrencyUnit::Sat,
  151. options: None,
  152. };
  153. let quote_res = client.post_melt_quote(request).await;
  154. assert!(
  155. matches!(quote_res, Err(Error::BlindAuthRequired)),
  156. "Expected AuthRequired error, got {:?}",
  157. quote_res
  158. );
  159. }
  160. // Test melt quote
  161. {
  162. let request = MeltQuoteBolt11Request {
  163. request: create_fake_invoice(100, "".to_string()),
  164. unit: CurrencyUnit::Sat,
  165. options: None,
  166. };
  167. let quote_res = client.post_melt_quote(request).await;
  168. assert!(
  169. matches!(quote_res, Err(Error::BlindAuthRequired)),
  170. "Expected AuthRequired error, got {:?}",
  171. quote_res
  172. );
  173. }
  174. // Test melt
  175. {
  176. let request = MeltRequest::new(
  177. "123e4567-e89b-12d3-a456-426614174000".to_string(),
  178. vec![],
  179. None,
  180. );
  181. let melt_res = client
  182. .post_melt(&PaymentMethod::Known(KnownMethod::Bolt11), request)
  183. .await;
  184. assert!(
  185. matches!(melt_res, Err(Error::BlindAuthRequired)),
  186. "Expected AuthRequired error, got {:?}",
  187. melt_res
  188. );
  189. }
  190. // Check melt quote state
  191. {
  192. let melt_res = client
  193. .get_melt_quote_status("123e4567-e89b-12d3-a456-426614174000")
  194. .await;
  195. assert!(
  196. matches!(melt_res, Err(Error::BlindAuthRequired)),
  197. "Expected AuthRequired error, got {:?}",
  198. melt_res
  199. );
  200. }
  201. }
  202. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  203. async fn test_check_without_auth() {
  204. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  205. let request = CheckStateRequest { ys: vec![] };
  206. let quote_res = client.post_check_state(request).await;
  207. assert!(
  208. matches!(quote_res, Err(Error::BlindAuthRequired)),
  209. "Expected AuthRequired error, got {:?}",
  210. quote_res
  211. );
  212. }
  213. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  214. async fn test_restore_without_auth() {
  215. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  216. let request = RestoreRequest { outputs: vec![] };
  217. let restore_res = client.post_restore(request).await;
  218. assert!(
  219. matches!(restore_res, Err(Error::BlindAuthRequired)),
  220. "Expected AuthRequired error, got {:?}",
  221. restore_res
  222. );
  223. }
  224. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  225. async fn test_mint_blind_auth() {
  226. let db = Arc::new(memory::empty().await.unwrap());
  227. let wallet = WalletBuilder::new()
  228. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  229. .unit(CurrencyUnit::Sat)
  230. .localstore(db.clone())
  231. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  232. .build()
  233. .expect("Wallet");
  234. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  235. let (access_token, _) = get_access_token(&mint_info).await;
  236. wallet.set_cat(access_token).await.unwrap();
  237. wallet
  238. .mint_blind_auth(10.into())
  239. .await
  240. .expect("Could not mint blind auth");
  241. let proofs = wallet
  242. .get_unspent_auth_proofs()
  243. .await
  244. .expect("Could not get auth proofs");
  245. assert!(proofs.len() == 10)
  246. }
  247. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  248. async fn test_mint_with_auth() {
  249. let db = Arc::new(memory::empty().await.unwrap());
  250. let wallet = WalletBuilder::new()
  251. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  252. .unit(CurrencyUnit::Sat)
  253. .localstore(db.clone())
  254. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  255. .build()
  256. .expect("Wallet");
  257. let mint_info = wallet
  258. .fetch_mint_info()
  259. .await
  260. .expect("mint info")
  261. .expect("could not get mint info");
  262. let (access_token, _) = get_access_token(&mint_info).await;
  263. println!("st{}", access_token);
  264. wallet.set_cat(access_token).await.unwrap();
  265. wallet
  266. .mint_blind_auth(10.into())
  267. .await
  268. .expect("Could not mint blind auth");
  269. let wallet = Arc::new(wallet);
  270. let mint_amount: Amount = 100.into();
  271. let quote = wallet
  272. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  273. .await
  274. .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(PaymentMethod::BOLT11, Some(10.into()), None, 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
  428. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  429. .await;
  430. assert!(
  431. matches!(quote_res, Err(Error::TokenAlreadySpent)),
  432. "Expected AuthRequired error, got {:?}",
  433. quote_res
  434. );
  435. }
  436. }
  437. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  438. async fn test_melt_with_invalid_auth() {
  439. let db = Arc::new(memory::empty().await.unwrap());
  440. let wallet = WalletBuilder::new()
  441. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  442. .unit(CurrencyUnit::Sat)
  443. .localstore(db.clone())
  444. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  445. .build()
  446. .expect("Wallet");
  447. let mint_info = wallet.fetch_mint_info().await.unwrap().unwrap();
  448. let (access_token, _) = get_access_token(&mint_info).await;
  449. wallet.set_cat(access_token).await.unwrap();
  450. wallet.mint_blind_auth(10.into()).await.unwrap();
  451. fund_wallet(Arc::new(wallet.clone()), 1.into()).await;
  452. let proofs = wallet
  453. .get_unspent_proofs()
  454. .await
  455. .expect("wallet has proofs");
  456. println!("{:#?}", proofs);
  457. let proof = proofs.first().expect("wallet has one proof");
  458. let client = HttpClient::new(MintUrl::from_str(MINT_URL).expect("Valid mint url"), None);
  459. {
  460. let invalid_auth_proof = AuthProof {
  461. keyset_id: proof.keyset_id,
  462. secret: proof.secret.clone(),
  463. c: proof.c,
  464. dleq: proof.dleq.clone(),
  465. };
  466. let _auth_token = AuthToken::BlindAuth(BlindAuthToken::new(invalid_auth_proof));
  467. let request = MintQuoteBolt11Request {
  468. unit: CurrencyUnit::Sat,
  469. amount: 10.into(),
  470. description: None,
  471. pubkey: None,
  472. };
  473. let quote_res = client.post_mint_quote(request).await;
  474. assert!(
  475. matches!(quote_res, Err(Error::BlindAuthRequired)),
  476. "Expected AuthRequired error, got {:?}",
  477. quote_res
  478. );
  479. }
  480. {
  481. let (access_token, _) = get_access_token(&mint_info).await;
  482. wallet.set_cat(access_token).await.unwrap();
  483. }
  484. }
  485. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  486. async fn test_refresh_access_token() {
  487. let db = Arc::new(memory::empty().await.unwrap());
  488. let wallet = WalletBuilder::new()
  489. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  490. .unit(CurrencyUnit::Sat)
  491. .localstore(db.clone())
  492. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  493. .build()
  494. .expect("Wallet");
  495. let mint_info = wallet
  496. .fetch_mint_info()
  497. .await
  498. .expect("mint info")
  499. .expect("could not get mint info");
  500. let (access_token, refresh_token) = get_access_token(&mint_info).await;
  501. // Set the initial access token and refresh token
  502. wallet.set_cat(access_token.clone()).await.unwrap();
  503. wallet
  504. .set_refresh_token(refresh_token.clone())
  505. .await
  506. .unwrap();
  507. // Mint some blind auth tokens with the initial access token
  508. wallet.mint_blind_auth(5.into()).await.unwrap();
  509. // Refresh the access token
  510. wallet.refresh_access_token().await.unwrap();
  511. // Verify we can still perform operations with the refreshed token
  512. let mint_amount: Amount = 10.into();
  513. // Try to mint more blind auth tokens with the refreshed token
  514. let auth_proofs = wallet.mint_blind_auth(5.into()).await.unwrap();
  515. assert_eq!(auth_proofs.len(), 5);
  516. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  517. assert_eq!(total_auth_proofs.len(), 10); // 5 from before refresh + 5 after refresh
  518. // Try to get a mint quote with the refreshed token
  519. let mint_quote = wallet
  520. .mint_quote(PaymentMethod::BOLT11, Some(mint_amount), None, None)
  521. .await
  522. .expect("failed to get mint quote with refreshed token");
  523. assert_eq!(mint_quote.amount, Some(mint_amount));
  524. // Verify the total number of auth tokens
  525. let total_auth_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  526. assert_eq!(total_auth_proofs.len(), 9); // 5 from before refresh + 5 after refresh - 1 for the quote
  527. }
  528. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  529. async fn test_invalid_refresh_token() {
  530. let db = Arc::new(memory::empty().await.unwrap());
  531. let wallet = WalletBuilder::new()
  532. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  533. .unit(CurrencyUnit::Sat)
  534. .localstore(db.clone())
  535. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  536. .build()
  537. .expect("Wallet");
  538. let mint_info = wallet
  539. .fetch_mint_info()
  540. .await
  541. .expect("mint info")
  542. .expect("could not get mint info");
  543. let (access_token, _) = get_access_token(&mint_info).await;
  544. // Set the initial access token
  545. wallet.set_cat(access_token.clone()).await.unwrap();
  546. // Set an invalid refresh token
  547. wallet
  548. .set_refresh_token("invalid_refresh_token".to_string())
  549. .await
  550. .unwrap();
  551. // Attempt to refresh the access token with an invalid refresh token
  552. let refresh_result = wallet.refresh_access_token().await;
  553. // Should fail with an error
  554. assert!(refresh_result.is_err(), "Expected refresh token error");
  555. }
  556. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  557. async fn test_auth_token_spending_order() {
  558. let db = Arc::new(memory::empty().await.unwrap());
  559. let wallet = WalletBuilder::new()
  560. .mint_url(MintUrl::from_str(MINT_URL).expect("Valid mint url"))
  561. .unit(CurrencyUnit::Sat)
  562. .localstore(db.clone())
  563. .seed(Mnemonic::generate(12).unwrap().to_seed_normalized(""))
  564. .build()
  565. .expect("Wallet");
  566. let mint_info = wallet
  567. .fetch_mint_info()
  568. .await
  569. .expect("mint info")
  570. .expect("could not get mint info");
  571. let (access_token, _) = get_access_token(&mint_info).await;
  572. wallet.set_cat(access_token).await.unwrap();
  573. // Mint auth tokens in two batches to test ordering
  574. wallet.mint_blind_auth(2.into()).await.unwrap();
  575. // Get the first batch of auth proofs
  576. let first_batch = wallet.get_unspent_auth_proofs().await.unwrap();
  577. assert_eq!(first_batch.len(), 2);
  578. // Mint a second batch
  579. wallet.mint_blind_auth(3.into()).await.unwrap();
  580. // Get all auth proofs
  581. let all_proofs = wallet.get_unspent_auth_proofs().await.unwrap();
  582. assert_eq!(all_proofs.len(), 5);
  583. // Use tokens and verify they're used in the expected order (FIFO)
  584. for i in 0..3 {
  585. let mint_quote = wallet
  586. .mint_quote(PaymentMethod::BOLT11, Some(10.into()), None, None)
  587. .await
  588. .expect("failed to get mint quote");
  589. assert_eq!(mint_quote.amount, Some(10.into()));
  590. // Check remaining tokens after each operation
  591. let remaining = wallet.get_unspent_auth_proofs().await.unwrap();
  592. assert_eq!(
  593. remaining.len(),
  594. 5 - (i + 1),
  595. "Expected {} remaining auth tokens after {} operations",
  596. 5 - (i + 1),
  597. i + 1
  598. );
  599. }
  600. }
  601. async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
  602. let openid_discovery = mint_info
  603. .nuts
  604. .nut21
  605. .clone()
  606. .expect("Nut21 defined")
  607. .openid_discovery;
  608. let oidc_client = OidcClient::new(openid_discovery, None);
  609. // Get the token endpoint from the OIDC configuration
  610. let token_url = oidc_client
  611. .get_oidc_config()
  612. .await
  613. .expect("Failed to get OIDC config")
  614. .token_endpoint;
  615. // Create the request parameters
  616. let (user, password) = get_oidc_credentials();
  617. let params = [
  618. ("grant_type", "password"),
  619. ("client_id", "cashu-client"),
  620. ("username", &user),
  621. ("password", &password),
  622. ];
  623. // Make the token request directly
  624. let client = reqwest::Client::new();
  625. let response = client
  626. .post(token_url)
  627. .form(&params)
  628. .send()
  629. .await
  630. .expect("Failed to send token request");
  631. let token_response: serde_json::Value = response
  632. .json()
  633. .await
  634. .expect("Failed to parse token response");
  635. let access_token = token_response["access_token"]
  636. .as_str()
  637. .expect("No access token in response")
  638. .to_string();
  639. let refresh_token = token_response["refresh_token"]
  640. .as_str()
  641. .expect("No access token in response")
  642. .to_string();
  643. (access_token, refresh_token)
  644. }
  645. /// Get a new access token with custom credentials
  646. async fn get_custom_access_token(
  647. mint_info: &MintInfo,
  648. username: &str,
  649. password: &str,
  650. ) -> Result<(String, String), Error> {
  651. let openid_discovery = mint_info
  652. .nuts
  653. .nut21
  654. .clone()
  655. .expect("Nut21 defined")
  656. .openid_discovery;
  657. let oidc_client = OidcClient::new(openid_discovery, None);
  658. // Get the token endpoint from the OIDC configuration
  659. let token_url = oidc_client
  660. .get_oidc_config()
  661. .await
  662. .map_err(|_| Error::Custom("Failed to get OIDC config".to_string()))?
  663. .token_endpoint;
  664. // Create the request parameters
  665. let params = [
  666. ("grant_type", "password"),
  667. ("client_id", "cashu-client"),
  668. ("username", username),
  669. ("password", password),
  670. ];
  671. // Make the token request directly
  672. let client = reqwest::Client::new();
  673. let response = client
  674. .post(token_url)
  675. .form(&params)
  676. .send()
  677. .await
  678. .map_err(|_| Error::Custom("Failed to send token request".to_string()))?;
  679. if !response.status().is_success() {
  680. return Err(Error::Custom(format!(
  681. "Token request failed with status: {}",
  682. response.status()
  683. )));
  684. }
  685. let token_response: serde_json::Value = response
  686. .json()
  687. .await
  688. .map_err(|_| Error::Custom("Failed to parse token response".to_string()))?;
  689. let access_token = token_response["access_token"]
  690. .as_str()
  691. .ok_or_else(|| Error::Custom("No access token in response".to_string()))?
  692. .to_string();
  693. let refresh_token = token_response["refresh_token"]
  694. .as_str()
  695. .ok_or_else(|| Error::Custom("No refresh token in response".to_string()))?
  696. .to_string();
  697. Ok((access_token, refresh_token))
  698. }