http_client.rs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. //! HTTP Mint client with pluggable transport
  2. use std::collections::HashSet;
  3. use std::sync::{Arc, RwLock as StdRwLock};
  4. use async_trait::async_trait;
  5. use cdk_common::{
  6. nut19, MeltQuoteBolt12Request, MeltQuoteBolt12Response, MeltQuoteCustomResponse, Method,
  7. MintQuoteBolt12Request, MintQuoteBolt12Response, ProtectedEndpoint, RoutePath,
  8. };
  9. use serde::de::DeserializeOwned;
  10. use serde::Serialize;
  11. use tokio::sync::RwLock;
  12. use tracing::instrument;
  13. use url::Url;
  14. use web_time::{Duration, Instant};
  15. use super::transport::Transport;
  16. use super::{Error, MeltOptions, MintConnector};
  17. use crate::mint_url::MintUrl;
  18. use crate::nuts::nut00::{KnownMethod, PaymentMethod};
  19. use crate::nuts::nut22::MintAuthRequest;
  20. use crate::nuts::{
  21. AuthToken, CheckStateRequest, CheckStateResponse, Id, KeySet, KeysResponse, KeysetResponse,
  22. MeltQuoteBolt11Request, MeltQuoteBolt11Response, MeltQuoteCustomRequest, MeltRequest, MintInfo,
  23. MintQuoteBolt11Request, MintQuoteBolt11Response, MintQuoteCustomRequest,
  24. MintQuoteCustomResponse, MintRequest, MintResponse, RestoreRequest, RestoreResponse,
  25. SwapRequest, SwapResponse,
  26. };
  27. use crate::wallet::auth::{AuthMintConnector, AuthWallet};
  28. type Cache = (u64, HashSet<(nut19::Method, nut19::Path)>);
  29. /// Http Client
  30. #[derive(Debug, Clone)]
  31. pub struct HttpClient<T>
  32. where
  33. T: Transport + Send + Sync + 'static,
  34. {
  35. transport: Arc<T>,
  36. mint_url: MintUrl,
  37. cache_support: Arc<StdRwLock<Cache>>,
  38. auth_wallet: Arc<RwLock<Option<AuthWallet>>>,
  39. }
  40. impl<T> HttpClient<T>
  41. where
  42. T: Transport + Send + Sync + 'static,
  43. {
  44. /// Create new [`HttpClient`] with a provided transport implementation.
  45. pub fn with_transport(
  46. mint_url: MintUrl,
  47. transport: T,
  48. auth_wallet: Option<AuthWallet>,
  49. ) -> Self {
  50. Self {
  51. transport: transport.into(),
  52. mint_url,
  53. auth_wallet: Arc::new(RwLock::new(auth_wallet)),
  54. cache_support: Default::default(),
  55. }
  56. }
  57. /// Create new [`HttpClient`]
  58. pub fn new(mint_url: MintUrl, auth_wallet: Option<AuthWallet>) -> Self {
  59. Self {
  60. transport: T::default().into(),
  61. mint_url,
  62. auth_wallet: Arc::new(RwLock::new(auth_wallet)),
  63. cache_support: Default::default(),
  64. }
  65. }
  66. /// Get auth token for a protected endpoint
  67. #[instrument(skip(self))]
  68. pub async fn get_auth_token(
  69. &self,
  70. method: Method,
  71. path: RoutePath,
  72. ) -> Result<Option<AuthToken>, Error> {
  73. let auth_wallet = self.auth_wallet.read().await;
  74. match auth_wallet.as_ref() {
  75. Some(auth_wallet) => {
  76. let endpoint = ProtectedEndpoint::new(method, path);
  77. auth_wallet.get_auth_for_request(&endpoint).await
  78. }
  79. None => Ok(None),
  80. }
  81. }
  82. /// Create new [`HttpClient`] with a proxy for specific TLDs.
  83. /// Specifying `None` for `host_matcher` will use the proxy for all
  84. /// requests.
  85. pub fn with_proxy(
  86. mint_url: MintUrl,
  87. proxy: Url,
  88. host_matcher: Option<&str>,
  89. accept_invalid_certs: bool,
  90. ) -> Result<Self, Error> {
  91. let mut transport = T::default();
  92. transport.with_proxy(proxy, host_matcher, accept_invalid_certs)?;
  93. Ok(Self {
  94. transport: transport.into(),
  95. mint_url,
  96. auth_wallet: Arc::new(RwLock::new(None)),
  97. cache_support: Default::default(),
  98. })
  99. }
  100. /// Generic implementation of a retriable http request
  101. ///
  102. /// The retry only happens if the mint supports replay through the Caching of NUT-19.
  103. #[inline(always)]
  104. async fn retriable_http_request<P, R>(
  105. &self,
  106. method: nut19::Method,
  107. path: nut19::Path,
  108. auth_token: Option<AuthToken>,
  109. custom_headers: &[(&str, &str)],
  110. payload: &P,
  111. ) -> Result<R, Error>
  112. where
  113. P: Serialize + ?Sized + Send + Sync,
  114. R: DeserializeOwned,
  115. {
  116. let started = Instant::now();
  117. let retriable_window = self
  118. .cache_support
  119. .read()
  120. .map(|cache_support| {
  121. cache_support
  122. .1
  123. .get(&(method, path.clone()))
  124. .map(|_| cache_support.0)
  125. })
  126. .unwrap_or_default()
  127. .map(Duration::from_secs)
  128. .unwrap_or_default();
  129. let transport = self.transport.clone();
  130. loop {
  131. let url = match &path {
  132. nut19::Path::Swap => self.mint_url.join_paths(&["v1", "swap"])?,
  133. nut19::Path::Custom(custom_path) => {
  134. // Custom paths should be in the format "/v1/mint/{method}" or "/v1/melt/{method}"
  135. // Remove leading slash if present
  136. let path_str = custom_path.trim_start_matches('/');
  137. let parts: Vec<&str> = path_str.split('/').collect();
  138. self.mint_url.join_paths(&parts)?
  139. }
  140. };
  141. let result = match method {
  142. nut19::Method::Get => {
  143. transport
  144. .http_get_with_headers(url, auth_token.clone(), custom_headers)
  145. .await
  146. }
  147. nut19::Method::Post => {
  148. transport
  149. .http_post_with_headers(url, auth_token.clone(), custom_headers, payload)
  150. .await
  151. }
  152. };
  153. if result.is_ok() {
  154. return result;
  155. }
  156. match result.as_ref() {
  157. Err(Error::HttpError(status_code, _)) => {
  158. let status_code = status_code.to_owned().unwrap_or_default();
  159. if (400..=499).contains(&status_code) {
  160. // 4xx errors won't be 'solved' by retrying
  161. return result;
  162. }
  163. // retry request, if possible
  164. tracing::error!("Failed http_request {:?}", result.as_ref().err());
  165. if retriable_window < started.elapsed() {
  166. return result;
  167. }
  168. }
  169. Err(_) => return result,
  170. _ => unreachable!(),
  171. };
  172. }
  173. }
  174. }
  175. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
  176. #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
  177. impl<T> MintConnector for HttpClient<T>
  178. where
  179. T: Transport + Send + Sync + 'static,
  180. {
  181. #[cfg(all(feature = "bip353", not(target_arch = "wasm32")))]
  182. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  183. async fn resolve_dns_txt(&self, domain: &str) -> Result<Vec<String>, Error> {
  184. self.transport.resolve_dns_txt(domain).await
  185. }
  186. /// Fetch Lightning address pay request data
  187. #[instrument(skip(self))]
  188. async fn fetch_lnurl_pay_request(
  189. &self,
  190. url: &str,
  191. ) -> Result<crate::lightning_address::LnurlPayResponse, Error> {
  192. let parsed_url =
  193. url::Url::parse(url).map_err(|e| Error::Custom(format!("Invalid URL: {}", e)))?;
  194. self.transport.http_get(parsed_url, None).await
  195. }
  196. /// Fetch invoice from Lightning address callback
  197. #[instrument(skip(self))]
  198. async fn fetch_lnurl_invoice(
  199. &self,
  200. url: &str,
  201. ) -> Result<crate::lightning_address::LnurlPayInvoiceResponse, Error> {
  202. let parsed_url =
  203. url::Url::parse(url).map_err(|e| Error::Custom(format!("Invalid URL: {}", e)))?;
  204. self.transport.http_get(parsed_url, None).await
  205. }
  206. /// Get Active Mint Keys [NUT-01]
  207. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  208. async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
  209. let url = self.mint_url.join_paths(&["v1", "keys"])?;
  210. let transport = self.transport.clone();
  211. Ok(transport.http_get::<KeysResponse>(url, None).await?.keysets)
  212. }
  213. /// Get Keyset Keys [NUT-01]
  214. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  215. async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
  216. let url = self
  217. .mint_url
  218. .join_paths(&["v1", "keys", &keyset_id.to_string()])?;
  219. let transport = self.transport.clone();
  220. let keys_response = transport.http_get::<KeysResponse>(url, None).await?;
  221. Ok(keys_response
  222. .keysets
  223. .first()
  224. .ok_or(Error::UnknownKeySet)?
  225. .clone())
  226. }
  227. /// Get Keysets [NUT-02]
  228. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  229. async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
  230. let url = self.mint_url.join_paths(&["v1", "keysets"])?;
  231. let transport = self.transport.clone();
  232. transport.http_get(url, None).await
  233. }
  234. /// Mint Quote [NUT-04]
  235. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  236. async fn post_mint_quote(
  237. &self,
  238. request: MintQuoteBolt11Request,
  239. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  240. let url = self
  241. .mint_url
  242. .join_paths(&["v1", "mint", "quote", "bolt11"])?;
  243. let auth_token = self
  244. .get_auth_token(
  245. Method::Post,
  246. RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
  247. )
  248. .await?;
  249. self.transport.http_post(url, auth_token, &request).await
  250. }
  251. /// Mint Quote status
  252. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  253. async fn get_mint_quote_status(
  254. &self,
  255. quote_id: &str,
  256. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  257. let url = self
  258. .mint_url
  259. .join_paths(&["v1", "mint", "quote", "bolt11", quote_id])?;
  260. let auth_token = self
  261. .get_auth_token(
  262. Method::Get,
  263. RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
  264. )
  265. .await?;
  266. self.transport.http_get(url, auth_token).await
  267. }
  268. /// Mint Tokens [NUT-04]
  269. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  270. async fn post_mint(
  271. &self,
  272. method: &PaymentMethod,
  273. request: MintRequest<String>,
  274. ) -> Result<MintResponse, Error> {
  275. let auth_token = self
  276. .get_auth_token(Method::Post, RoutePath::Mint(method.to_string()))
  277. .await?;
  278. let path = match method {
  279. PaymentMethod::Known(KnownMethod::Bolt11) => {
  280. nut19::Path::Custom("/v1/mint/bolt11".to_string())
  281. }
  282. PaymentMethod::Known(KnownMethod::Bolt12) => {
  283. nut19::Path::Custom("/v1/mint/bolt12".to_string())
  284. }
  285. PaymentMethod::Custom(m) => nut19::Path::custom_mint(m),
  286. };
  287. self.retriable_http_request(nut19::Method::Post, path, auth_token, &[], &request)
  288. .await
  289. }
  290. /// Melt Quote [NUT-05]
  291. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  292. async fn post_melt_quote(
  293. &self,
  294. request: MeltQuoteBolt11Request,
  295. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  296. let url = self
  297. .mint_url
  298. .join_paths(&["v1", "melt", "quote", "bolt11"])?;
  299. let auth_token = self
  300. .get_auth_token(
  301. Method::Post,
  302. RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
  303. )
  304. .await?;
  305. self.transport.http_post(url, auth_token, &request).await
  306. }
  307. /// Melt Quote Status
  308. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  309. async fn get_melt_quote_status(
  310. &self,
  311. quote_id: &str,
  312. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  313. let url = self
  314. .mint_url
  315. .join_paths(&["v1", "melt", "quote", "bolt11", quote_id])?;
  316. let auth_token = self
  317. .get_auth_token(
  318. Method::Get,
  319. RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt11).to_string()),
  320. )
  321. .await?;
  322. self.transport.http_get(url, auth_token).await
  323. }
  324. /// Melt [NUT-05]
  325. /// [Nut-08] Lightning fee return if outputs defined
  326. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  327. async fn post_melt_with_options(
  328. &self,
  329. method: &PaymentMethod,
  330. request: MeltRequest<String>,
  331. options: MeltOptions,
  332. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  333. let auth_token = self
  334. .get_auth_token(Method::Post, RoutePath::Melt(method.to_string()))
  335. .await?;
  336. let path = match method {
  337. PaymentMethod::Known(KnownMethod::Bolt11) => {
  338. nut19::Path::Custom("/v1/melt/bolt11".to_string())
  339. }
  340. PaymentMethod::Known(KnownMethod::Bolt12) => {
  341. nut19::Path::Custom("/v1/melt/bolt12".to_string())
  342. }
  343. PaymentMethod::Custom(m) => nut19::Path::custom_melt(m),
  344. };
  345. let custom_headers = if options.async_melt {
  346. vec![("Prefer", "respond-async")]
  347. } else {
  348. vec![]
  349. };
  350. self.retriable_http_request(
  351. nut19::Method::Post,
  352. path,
  353. auth_token,
  354. &custom_headers,
  355. &request,
  356. )
  357. .await
  358. }
  359. /// Swap Token [NUT-03]
  360. #[instrument(skip(self, swap_request), fields(mint_url = %self.mint_url))]
  361. async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
  362. let auth_token = self.get_auth_token(Method::Post, RoutePath::Swap).await?;
  363. self.retriable_http_request(
  364. nut19::Method::Post,
  365. nut19::Path::Swap,
  366. auth_token,
  367. &[],
  368. &swap_request,
  369. )
  370. .await
  371. }
  372. /// Helper to get mint info
  373. async fn get_mint_info(&self) -> Result<MintInfo, Error> {
  374. let url = self.mint_url.join_paths(&["v1", "info"])?;
  375. let transport = self.transport.clone();
  376. let info: MintInfo = transport.http_get(url, None).await?;
  377. if let Ok(mut cache_support) = self.cache_support.write() {
  378. *cache_support = (
  379. info.nuts.nut19.ttl.unwrap_or(300),
  380. info.nuts
  381. .nut19
  382. .cached_endpoints
  383. .clone()
  384. .into_iter()
  385. .map(|cached_endpoint| (cached_endpoint.method, cached_endpoint.path))
  386. .collect(),
  387. );
  388. }
  389. Ok(info)
  390. }
  391. async fn get_auth_wallet(&self) -> Option<AuthWallet> {
  392. self.auth_wallet.read().await.clone()
  393. }
  394. async fn set_auth_wallet(&self, wallet: Option<AuthWallet>) {
  395. *self.auth_wallet.write().await = wallet;
  396. }
  397. /// Spendable check [NUT-07]
  398. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  399. async fn post_check_state(
  400. &self,
  401. request: CheckStateRequest,
  402. ) -> Result<CheckStateResponse, Error> {
  403. let url = self.mint_url.join_paths(&["v1", "checkstate"])?;
  404. let auth_token = self
  405. .get_auth_token(Method::Post, RoutePath::Checkstate)
  406. .await?;
  407. self.transport.http_post(url, auth_token, &request).await
  408. }
  409. /// Restore request [NUT-13]
  410. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  411. async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
  412. let url = self.mint_url.join_paths(&["v1", "restore"])?;
  413. let auth_token = self
  414. .get_auth_token(Method::Post, RoutePath::Restore)
  415. .await?;
  416. self.transport.http_post(url, auth_token, &request).await
  417. }
  418. /// Mint Quote Bolt12 [NUT-23]
  419. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  420. async fn post_mint_bolt12_quote(
  421. &self,
  422. request: MintQuoteBolt12Request,
  423. ) -> Result<MintQuoteBolt12Response<String>, Error> {
  424. let url = self
  425. .mint_url
  426. .join_paths(&["v1", "mint", "quote", "bolt12"])?;
  427. let auth_token = self
  428. .get_auth_token(
  429. Method::Post,
  430. RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
  431. )
  432. .await?;
  433. self.transport.http_post(url, auth_token, &request).await
  434. }
  435. /// Mint Quote Bolt12 status
  436. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  437. async fn get_mint_quote_bolt12_status(
  438. &self,
  439. quote_id: &str,
  440. ) -> Result<MintQuoteBolt12Response<String>, Error> {
  441. let url = self
  442. .mint_url
  443. .join_paths(&["v1", "mint", "quote", "bolt12", quote_id])?;
  444. let auth_token = self
  445. .get_auth_token(
  446. Method::Get,
  447. RoutePath::MintQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
  448. )
  449. .await?;
  450. self.transport.http_get(url, auth_token).await
  451. }
  452. /// Melt Quote Bolt12 [NUT-23]
  453. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  454. async fn post_melt_bolt12_quote(
  455. &self,
  456. request: MeltQuoteBolt12Request,
  457. ) -> Result<MeltQuoteBolt12Response<String>, Error> {
  458. let url = self
  459. .mint_url
  460. .join_paths(&["v1", "melt", "quote", "bolt12"])?;
  461. let auth_token = self
  462. .get_auth_token(
  463. Method::Post,
  464. RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
  465. )
  466. .await?;
  467. self.transport.http_post(url, auth_token, &request).await
  468. }
  469. /// Melt Quote Bolt12 Status [NUT-23]
  470. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  471. async fn get_melt_bolt12_quote_status(
  472. &self,
  473. quote_id: &str,
  474. ) -> Result<MeltQuoteBolt12Response<String>, Error> {
  475. let url = self
  476. .mint_url
  477. .join_paths(&["v1", "melt", "quote", "bolt12", quote_id])?;
  478. let auth_token = self
  479. .get_auth_token(
  480. Method::Get,
  481. RoutePath::MeltQuote(PaymentMethod::Known(KnownMethod::Bolt12).to_string()),
  482. )
  483. .await?;
  484. self.transport.http_get(url, auth_token).await
  485. }
  486. /// Mint Quote for Custom Payment Method
  487. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  488. async fn post_mint_custom_quote(
  489. &self,
  490. method: &PaymentMethod,
  491. request: MintQuoteCustomRequest,
  492. ) -> Result<MintQuoteCustomResponse<String>, Error> {
  493. let url = self
  494. .mint_url
  495. .join_paths(&["v1", "mint", "quote", &method.to_string()])?;
  496. let auth_token = self
  497. .get_auth_token(Method::Post, RoutePath::MintQuote(method.to_string()))
  498. .await?;
  499. self.transport.http_post(url, auth_token, &request).await
  500. }
  501. /// Mint Quote Status for Custom Payment Method
  502. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  503. async fn get_mint_quote_custom_status(
  504. &self,
  505. method: &str,
  506. quote_id: &str,
  507. ) -> Result<MintQuoteCustomResponse<String>, Error> {
  508. let url = self
  509. .mint_url
  510. .join_paths(&["v1", "mint", "quote", method, quote_id])?;
  511. let auth_token = self
  512. .get_auth_token(Method::Get, RoutePath::MintQuote(method.to_string()))
  513. .await?;
  514. self.transport.http_get(url, auth_token).await
  515. }
  516. /// Melt Quote for Custom Payment Method
  517. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  518. async fn post_melt_custom_quote(
  519. &self,
  520. request: MeltQuoteCustomRequest,
  521. ) -> Result<MeltQuoteCustomResponse<String>, Error> {
  522. let url = self
  523. .mint_url
  524. .join_paths(&["v1", "melt", "quote", &request.method])?;
  525. let auth_token = self
  526. .get_auth_token(Method::Post, RoutePath::MeltQuote(request.method.clone()))
  527. .await?;
  528. self.transport.http_post(url, auth_token, &request).await
  529. }
  530. /// Melt Quote Status for Custom Payment Method
  531. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  532. async fn get_melt_quote_custom_status(
  533. &self,
  534. method: &str,
  535. quote_id: &str,
  536. ) -> Result<MeltQuoteCustomResponse<String>, Error> {
  537. let url = self
  538. .mint_url
  539. .join_paths(&["v1", "melt", "quote", method, quote_id])?;
  540. let auth_token = self
  541. .get_auth_token(Method::Get, RoutePath::MeltQuote(method.to_string()))
  542. .await?;
  543. self.transport.http_get(url, auth_token).await
  544. }
  545. }
  546. /// Http Client
  547. #[derive(Debug, Clone)]
  548. pub struct AuthHttpClient<T>
  549. where
  550. T: Transport + Send + Sync + 'static,
  551. {
  552. transport: Arc<T>,
  553. mint_url: MintUrl,
  554. cat: Arc<RwLock<AuthToken>>,
  555. }
  556. impl<T> AuthHttpClient<T>
  557. where
  558. T: Transport + Send + Sync + 'static,
  559. {
  560. /// Create new [`AuthHttpClient`]
  561. pub fn new(mint_url: MintUrl, cat: Option<AuthToken>) -> Self {
  562. Self {
  563. transport: T::default().into(),
  564. mint_url,
  565. cat: Arc::new(RwLock::new(
  566. cat.unwrap_or(AuthToken::ClearAuth("".to_string())),
  567. )),
  568. }
  569. }
  570. }
  571. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
  572. #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
  573. impl<T> AuthMintConnector for AuthHttpClient<T>
  574. where
  575. T: Transport + Send + Sync + 'static,
  576. {
  577. async fn get_auth_token(&self) -> Result<AuthToken, Error> {
  578. Ok(self.cat.read().await.clone())
  579. }
  580. async fn set_auth_token(&self, token: AuthToken) -> Result<(), Error> {
  581. *self.cat.write().await = token;
  582. Ok(())
  583. }
  584. /// Get Mint Info [NUT-06]
  585. async fn get_mint_info(&self) -> Result<MintInfo, Error> {
  586. let url = self.mint_url.join_paths(&["v1", "info"])?;
  587. let mint_info: MintInfo = self.transport.http_get::<MintInfo>(url, None).await?;
  588. Ok(mint_info)
  589. }
  590. /// Get Auth Keyset Keys [NUT-22]
  591. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  592. async fn get_mint_blind_auth_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
  593. let url =
  594. self.mint_url
  595. .join_paths(&["v1", "auth", "blind", "keys", &keyset_id.to_string()])?;
  596. let mut keys_response = self.transport.http_get::<KeysResponse>(url, None).await?;
  597. let keyset = keys_response
  598. .keysets
  599. .drain(0..1)
  600. .next()
  601. .ok_or_else(|| Error::UnknownKeySet)?;
  602. Ok(keyset)
  603. }
  604. /// Get Auth Keysets [NUT-22]
  605. #[instrument(skip(self), fields(mint_url = %self.mint_url))]
  606. async fn get_mint_blind_auth_keysets(&self) -> Result<KeysetResponse, Error> {
  607. let url = self
  608. .mint_url
  609. .join_paths(&["v1", "auth", "blind", "keysets"])?;
  610. self.transport.http_get(url, None).await
  611. }
  612. /// Mint Tokens [NUT-22]
  613. #[instrument(skip(self, request), fields(mint_url = %self.mint_url))]
  614. async fn post_mint_blind_auth(&self, request: MintAuthRequest) -> Result<MintResponse, Error> {
  615. let url = self.mint_url.join_paths(&["v1", "auth", "blind", "mint"])?;
  616. self.transport
  617. .http_post(url, Some(self.cat.read().await.clone()), &request)
  618. .await
  619. }
  620. }