Skip to main content

lexe_node_client/
client.rs

1//! This module contains the code for the [`NodeClient`] and [`GatewayClient`]
2//! that the app uses to connect to the user node / gateway respectively, as
3//! well as related TLS configurations and certificates for both the client side
4//! (app) and server side (node/gateway).
5//!
6//! [`NodeClient`]: crate::client::NodeClient
7//! [`GatewayClient`]: crate::client::GatewayClient
8
9use std::{
10    borrow::Cow,
11    sync::Arc,
12    time::{Duration, SystemTime},
13};
14
15use anyhow::{Context, ensure};
16use arc_swap::ArcSwapOption;
17use async_trait::async_trait;
18use lexe_api::{
19    auth::{self, BearerAuthenticator},
20    def::{
21        AppBackendApi, AppGatewayApi, AppNodeProvisionApi, AppNodeRunApi,
22        BearerAuthBackendApi,
23    },
24    error::{BackendApiError, GatewayApiError, NodeApiError, NodeErrorKind},
25    models::{
26        command::{
27            BackupInfo, CloseChannelRequest, CreateInvoiceRequest,
28            CreateInvoiceResponse, CreateOfferRequest, CreateOfferResponse,
29            DebugInfo, EnclavesToProvisionRequest, GetAddressResponse,
30            GetNewPayments, GetUpdatedPayments, HumanBitcoinAddress,
31            ListChannelsResponse, NodeInfo, NodeInfoV1, OpenChannelRequest,
32            OpenChannelResponse, PayInvoiceRequest, PayInvoiceResponse,
33            PayOfferRequest, PayOfferResponse, PayOnchainRequest,
34            PayOnchainResponse, PaymentCreatedIndexes, PaymentIdStruct,
35            PreflightCloseChannelRequest, PreflightCloseChannelResponse,
36            PreflightOpenChannelRequest, PreflightOpenChannelResponse,
37            PreflightPayInvoiceRequest, PreflightPayInvoiceResponse,
38            PreflightPayOfferRequest, PreflightPayOfferResponse,
39            PreflightPayOnchainRequest, PreflightPayOnchainResponse,
40            SetupGDrive, UpdatePaymentNote,
41        },
42        nwc::{
43            CreateNwcClientRequest, CreateNwcClientResponse,
44            ListNwcClientResponse, NostrPkStruct, UpdateNwcClientRequest,
45            UpdateNwcClientResponse,
46        },
47    },
48    rest::{POST, RequestBuilderExt, RestClient},
49    types::{
50        Empty,
51        payments::{MaybeBasicPaymentV2, VecBasicPaymentV1, VecBasicPaymentV2},
52        username::UsernameStruct,
53    },
54};
55use lexe_common::{
56    api::{
57        auth::{
58            BearerAuthRequestWire, BearerAuthResponse, BearerAuthToken, Scope,
59            TokenWithExpiration, UserSignupRequestWire,
60            UserSignupRequestWireV1,
61        },
62        fiat_rates::FiatRates,
63        models::{
64            SignMsgRequest, SignMsgResponse, VerifyMsgRequest,
65            VerifyMsgResponse,
66        },
67        provision::NodeProvisionRequest,
68        revocable_clients::{
69            CreateRevocableClientRequest, CreateRevocableClientResponse,
70            GetRevocableClients, RevocableClient, RevocableClients,
71            UpdateClientRequest, UpdateClientResponse,
72        },
73        user::UserPk,
74        version::{CurrentEnclaves, EnclavesToProvision, NodeEnclave},
75    },
76    byte_str::ByteStr,
77    constants::{self, node_provision_dns},
78    env::DeployEnv,
79};
80use lexe_crypto::{ed25519, rng::Crng};
81use lexe_enclave::enclave::Measurement;
82use lexe_tls::{attest_client, lexe_ca, rustls};
83use reqwest::Url;
84
85use crate::credentials::{ClientCredentials, CredentialsRef};
86
87/// The client to the gateway itself, i.e. requests terminate at the gateway.
88#[derive(Clone)]
89pub struct GatewayClient {
90    rest: RestClient,
91    gateway_url: Cow<'static, str>,
92}
93
94/// The client to the user node.
95///
96/// Requests are proxied via the gateway CONNECT proxies. These proxies avoid
97/// exposing user nodes to the public internet and enforce user authentication
98/// and other request rate limits.
99///
100/// - Requests made to running nodes use the Run-specific [`RestClient`].
101/// - Requests made to provisioning nodes use a [`RestClient`] which is created
102///   on-the-fly. This is because it is necessary to include a TLS config which
103///   checks the server's remote attestation against a [`Measurement`] which is
104///   only known at provisioning time. This is also desirable because provision
105///   requests generally happen only once, so there is no need to maintain a
106///   connection pool after provisioning has complete.
107#[derive(Clone)]
108pub struct NodeClient {
109    inner: Arc<NodeClientInner>,
110}
111
112struct NodeClientInner {
113    /// The user's public key, if available from credentials.
114    user_pk: Option<UserPk>,
115    gateway_client: GatewayClient,
116    /// The [`RestClient`] used to communicate with a Run node.
117    ///
118    /// This is an [`ArcSwapOption`] so that we can atomically swap in a new
119    /// client with a new proxy config when the auth token expires.
120    ///
121    /// Previously, we used this patch to dynamically set the proxy auth header
122    /// with the latest auth token:
123    /// [proxy: allow setting proxy-auth at intercept time](https://github.com/lexe-app/reqwest/commit/dea2dd7a1d3c52e50d1c47803fdc57d73e35c769)
124    /// This approach has the best connection reuse, since the connection pool
125    /// is shared across all tokens; we should only need to reconnect if the
126    /// underlying connection times out.
127    ///
128    /// This approach removes the need for a patch. One downside: it replaces
129    /// the connection pool whenever we need to re-auth. Until we get
130    /// per-request proxy configs in `reqwest`, this is likely the best we can
131    /// do. Though one reconnection per 10 min. is probably ok.
132    run_rest: ArcSwapOption<RunRestClient>,
133    run_url: &'static str,
134    use_sgx: bool,
135    deploy_env: DeployEnv,
136    authenticator: Arc<BearerAuthenticator>,
137    tls_config: rustls::ClientConfig,
138}
139
140/// A [`RestClient`] with required proxy configuration needed to communicate
141/// with a user node.
142struct RunRestClient {
143    client: RestClient,
144    /// When the auth token used in the proxy config expires, or `None` if it
145    /// never expires.
146    token_expiration: Option<SystemTime>,
147}
148
149// --- impl GatewayClient --- //
150
151impl GatewayClient {
152    pub fn new(
153        deploy_env: DeployEnv,
154        gateway_url: impl Into<Cow<'static, str>>,
155        user_agent: impl Into<Cow<'static, str>>,
156    ) -> anyhow::Result<Self> {
157        fn inner(
158            deploy_env: DeployEnv,
159            gateway_url: Cow<'static, str>,
160            user_agent: Cow<'static, str>,
161        ) -> anyhow::Result<GatewayClient> {
162            let tls_config = lexe_ca::app_gateway_client_config(deploy_env);
163            let rest = RestClient::new(user_agent, "gateway", tls_config);
164            Ok(GatewayClient { rest, gateway_url })
165        }
166        inner(deploy_env, gateway_url.into(), user_agent.into())
167    }
168}
169
170impl AppBackendApi for GatewayClient {
171    async fn signup_v2(
172        &self,
173        signed_req: &ed25519::Signed<&UserSignupRequestWire>,
174    ) -> Result<Empty, BackendApiError> {
175        let gateway_url = &self.gateway_url;
176        let req = self
177            .rest
178            .builder(POST, format!("{gateway_url}/app/v2/signup"))
179            .signed_bcs(signed_req)
180            .map_err(BackendApiError::bcs_serialize)?;
181        self.rest.send(req).await
182    }
183
184    async fn signup_v1(
185        &self,
186        _signed_req: &ed25519::Signed<&UserSignupRequestWireV1>,
187    ) -> Result<Empty, BackendApiError> {
188        debug_assert!(false, "Use `signup_v2`");
189        Err(BackendApiError::not_found("Use `/app/v2/signup`"))
190    }
191
192    async fn enclaves_to_provision(
193        &self,
194        req: &EnclavesToProvisionRequest,
195        auth: BearerAuthToken,
196    ) -> Result<EnclavesToProvision, BackendApiError> {
197        let gateway_url = &self.gateway_url;
198        let url = format!("{gateway_url}/app/v1/enclaves_to_provision");
199        let req = self.rest.post(url, req).bearer_auth(&auth);
200        self.rest.send(req).await
201    }
202}
203
204#[async_trait]
205impl BearerAuthBackendApi for GatewayClient {
206    async fn bearer_auth(
207        &self,
208        signed_req: &ed25519::Signed<&BearerAuthRequestWire>,
209    ) -> Result<BearerAuthResponse, BackendApiError> {
210        let gateway_url = &self.gateway_url;
211        let req = self
212            .rest
213            .builder(POST, format!("{gateway_url}/app/bearer_auth"))
214            .signed_bcs(signed_req)
215            .map_err(BackendApiError::bcs_serialize)?;
216        self.rest.send(req).await
217    }
218}
219
220impl AppGatewayApi for GatewayClient {
221    async fn get_fiat_rates(&self) -> Result<FiatRates, GatewayApiError> {
222        let gateway_url = &self.gateway_url;
223        let req = self
224            .rest
225            .get(format!("{gateway_url}/app/v1/fiat_rates"), &Empty {});
226        self.rest.send(req).await
227    }
228
229    async fn latest_release(&self) -> Result<NodeEnclave, GatewayApiError> {
230        let gateway_url = &self.gateway_url;
231        let req = self
232            .rest
233            .get(format!("{gateway_url}/app/v1/latest_release"), &Empty {});
234        self.rest.send(req).await
235    }
236
237    async fn current_releases(
238        &self,
239    ) -> Result<CurrentEnclaves, GatewayApiError> {
240        let gateway_url = &self.gateway_url;
241        let req = self
242            .rest
243            .get(format!("{gateway_url}/app/v1/current_releases"), &Empty {});
244        self.rest.send(req).await
245    }
246
247    async fn current_enclaves(
248        &self,
249    ) -> Result<CurrentEnclaves, GatewayApiError> {
250        let gateway_url = &self.gateway_url;
251        let req = self
252            .rest
253            .get(format!("{gateway_url}/app/v1/current_enclaves"), &Empty {});
254        self.rest.send(req).await
255    }
256}
257
258// --- impl NodeClient --- //
259
260impl NodeClient {
261    pub fn new(
262        rng: &mut impl Crng,
263        use_sgx: bool,
264        deploy_env: DeployEnv,
265        gateway_client: GatewayClient,
266        credentials: CredentialsRef<'_>,
267    ) -> anyhow::Result<Self> {
268        let run_url = constants::NODE_RUN_URL;
269
270        let gateway_url = &gateway_client.gateway_url;
271        ensure!(
272            gateway_url.starts_with("https://"),
273            "proxy connection must be https: gateway url: {gateway_url}",
274        );
275
276        let user_pk = credentials.user_pk();
277        let authenticator = credentials.bearer_authenticator();
278        let tls_config = credentials.tls_config(rng, deploy_env)?;
279        let run_rest = ArcSwapOption::from(None);
280
281        Ok(Self {
282            inner: Arc::new(NodeClientInner {
283                user_pk,
284                gateway_client,
285                run_rest,
286                run_url,
287                use_sgx,
288                deploy_env,
289                authenticator,
290                tls_config,
291            }),
292        })
293    }
294
295    /// Returns the user's public key, if available from the credentials.
296    ///
297    /// Returns `None` if credentials were created before node v0.8.11,
298    /// which didn't include user_pk.
299    pub fn user_pk(&self) -> Option<UserPk> {
300        self.inner.user_pk
301    }
302
303    /// Get an authenticated [`RunRestClient`] for making requests to the user
304    /// node's run endpoint via the gateway CONNECT proxy.
305    ///
306    /// The returned client always has a fresh auth token for the gateway proxy.
307    ///
308    /// In the common case where our token is still fresh, this is a fast atomic
309    /// load of the cached client. If the token is expired, we will request a
310    /// new token, build a new client, and swap it in atomically.
311    async fn authed_run_rest(
312        &self,
313    ) -> Result<Arc<RunRestClient>, NodeApiError> {
314        let now = SystemTime::now();
315
316        // Fast path: we already have a fresh token and client
317        if let Some(run_rest) = self.maybe_authed_run_rest(now) {
318            return Ok(run_rest);
319        }
320
321        // TODO(phlip9): `std::hint::cold_path()` here when that stabilizes
322
323        // Get an unexpired auth token. This is probably a new token, but we may
324        // race with other tasks here, so we could also get a cached token.
325        let auth_token = self.get_auth_token(now).await?;
326
327        // Check again if another task concurrently swapped in a fresh client.
328        // A little hacky, but significantly reduces the chance that we create
329        // multiple clients.
330        if let Some(run_rest) = self.maybe_authed_run_rest(now) {
331            // TODO(phlip9): `std::hint::cold_path()` here when that stabilizes
332            return Ok(run_rest);
333        }
334
335        // Build a new client with the new token
336        let run_rest = RunRestClient::new(
337            &self.inner.gateway_client,
338            self.inner.run_url,
339            auth_token,
340            self.inner.tls_config.clone(),
341        )
342        .map_err(NodeApiError::bad_auth)?;
343        let run_rest = Arc::new(run_rest);
344
345        // Swap it in
346        self.inner.run_rest.swap(Some(run_rest.clone()));
347
348        Ok(run_rest)
349    }
350
351    /// Returns `Some(_)` if we already have an authenticated run rest client
352    /// whose token is unexpired.
353    fn maybe_authed_run_rest(
354        &self,
355        now: SystemTime,
356    ) -> Option<Arc<RunRestClient>> {
357        let maybe_run_rest = self.inner.run_rest.load_full();
358        if let Some(run_rest) = maybe_run_rest
359            && !run_rest.token_needs_refresh(now)
360        {
361            Some(run_rest)
362        } else {
363            None
364        }
365    }
366
367    /// Get an unexpired auth token (maybe cached, maybe new) for the gateway
368    /// CONNECT proxy.
369    async fn get_auth_token(
370        &self,
371        now: SystemTime,
372    ) -> Result<TokenWithExpiration, NodeApiError> {
373        self.inner
374            .authenticator
375            .get_token_with_exp(&self.inner.gateway_client, now)
376            .await
377            // TODO(phlip9): how to best convert `BackendApiError` to
378            //               `NodeApiError`?
379            .map_err(|backend_error| {
380                // Contains backend kind msg and regular msg
381                let msg = format!("{backend_error:#}");
382
383                let BackendApiError {
384                    data, sensitive, ..
385                } = backend_error;
386
387                NodeApiError {
388                    kind: NodeErrorKind::BadAuth,
389                    msg,
390                    data,
391                    sensitive,
392                }
393            })
394    }
395
396    /// Builds a Provision-specific [`RestClient`] which can be used to make a
397    /// provision request to a provisioning node.
398    ///
399    /// This client doesn't automatically refresh its auth token, so avoid
400    /// holding onto this client for too long.
401    fn provision_rest_client(
402        &self,
403        provision_url: &str,
404        auth_token: BearerAuthToken,
405        measurement: Measurement,
406    ) -> anyhow::Result<RestClient> {
407        let proxy = static_proxy_config(
408            &self.inner.gateway_client.gateway_url,
409            provision_url,
410            auth_token,
411        )
412        .context("Invalid proxy config")?;
413
414        let tls_config = attest_client::app_node_provision_client_config(
415            self.inner.use_sgx,
416            self.inner.deploy_env,
417            measurement,
418        );
419
420        let user_agent = self.inner.gateway_client.rest.user_agent().clone();
421        let (from, to) = (user_agent, "node-provision");
422        let reqwest_client = RestClient::client_builder(&from)
423            .proxy(proxy)
424            .use_preconfigured_tls(tls_config)
425            // Provision can take longer than 5 sec. <3 gdrive : )
426            .timeout(Duration::from_secs(30))
427            .build()
428            .context("Failed to build client")?;
429
430        let provision_rest = RestClient::from_inner(reqwest_client, from, to);
431
432        Ok(provision_rest)
433    }
434
435    /// Ask the user node to create a new [`RevocableClient`] and return it
436    /// along with its [`ClientCredentials`].
437    pub async fn create_client_credentials(
438        &self,
439        req: CreateRevocableClientRequest,
440    ) -> anyhow::Result<(RevocableClient, ClientCredentials)> {
441        // Mint a new long-lived connect token
442        let lexe_auth_token = self.request_long_lived_connect_token().await?;
443
444        // Register a new revocable client
445        let resp = self.create_revocable_client(req.clone()).await?;
446
447        let client = RevocableClient {
448            pubkey: resp.pubkey,
449            created_at: resp.created_at,
450            label: req.label,
451            scope: req.scope,
452            expires_at: req.expires_at,
453            is_revoked: false,
454        };
455
456        let client_credentials =
457            ClientCredentials::from_response(lexe_auth_token, resp);
458
459        Ok((client, client_credentials))
460    }
461
462    /// Get a new long-lived auth token scoped only for the gateway connect
463    /// proxy. Used for the SDK to connect to the node.
464    async fn request_long_lived_connect_token(
465        &self,
466    ) -> anyhow::Result<BearerAuthToken> {
467        let user_key_pair = self
468            .inner
469            .authenticator
470            .user_key_pair()
471            .context("Somehow using a static bearer auth token")?;
472
473        let now = SystemTime::now();
474        let lifetime_secs = 10 * 365 * 24 * 60 * 60; // 10 years
475        let scope = Some(Scope::NodeConnect);
476        let long_lived_connect_token = lexe_api::auth::do_bearer_auth(
477            &self.inner.gateway_client,
478            now,
479            user_key_pair,
480            lifetime_secs,
481            scope,
482        )
483        .await
484        .context("Failed to get long-lived connect token")?;
485
486        Ok(long_lived_connect_token.token)
487    }
488
489    /// Get a short-lived auth token with [`Scope::All`] for provisioning.
490    pub async fn request_provision_token(
491        &self,
492    ) -> anyhow::Result<BearerAuthToken> {
493        let user_key_pair = self
494            .inner
495            .authenticator
496            .user_key_pair()
497            .context("Somehow using a static bearer auth token")?;
498
499        let now = SystemTime::now();
500        let lifetime_secs = 60; // 1 minute
501        let scope = Some(Scope::All);
502        let token = lexe_api::auth::do_bearer_auth(
503            &self.inner.gateway_client,
504            now,
505            user_key_pair,
506            lifetime_secs,
507            scope,
508        )
509        .await
510        .context("Failed to get app token")?;
511
512        Ok(token.token)
513    }
514}
515
516impl AppNodeProvisionApi for NodeClient {
517    async fn provision(
518        &self,
519        measurement: Measurement,
520        data: NodeProvisionRequest,
521    ) -> Result<Empty, NodeApiError> {
522        let now = SystemTime::now();
523        let mr_short = measurement.short();
524        let provision_dns = node_provision_dns(&mr_short);
525        let provision_url = format!("https://{provision_dns}");
526
527        // Create rest client on the fly
528        let auth_token = self.get_auth_token(now).await?.token;
529        let provision_rest = self
530            .provision_rest_client(&provision_url, auth_token, measurement)
531            .context("Failed to build provision rest client")
532            .map_err(NodeApiError::provision)?;
533
534        let req = provision_rest
535            .post(format!("{provision_url}/app/provision"), &data);
536        provision_rest.send(req).await
537    }
538}
539
540impl AppNodeRunApi for NodeClient {
541    async fn node_info(&self) -> Result<NodeInfo, NodeApiError> {
542        let run_rest = &self.authed_run_rest().await?.client;
543        let run_url = &self.inner.run_url;
544        let url = format!("{run_url}/app/v2/node_info");
545        let req = run_rest.get(url, &Empty {});
546        run_rest.send(req).await
547    }
548
549    async fn node_info_v1(&self) -> Result<NodeInfoV1, NodeApiError> {
550        let run_rest = &self.authed_run_rest().await?.client;
551        let run_url = &self.inner.run_url;
552        let url = format!("{run_url}/app/node_info");
553        let req = run_rest.get(url, &Empty {});
554        run_rest.send(req).await
555    }
556
557    async fn debug_info(&self) -> Result<DebugInfo, NodeApiError> {
558        let run_rest = &self.authed_run_rest().await?.client;
559        let run_url = &self.inner.run_url;
560        let url = format!("{run_url}/app/debug_info");
561        let req = run_rest.get(url, &Empty {});
562        run_rest.send(req).await
563    }
564
565    async fn list_channels(
566        &self,
567    ) -> Result<ListChannelsResponse, NodeApiError> {
568        let run_rest = &self.authed_run_rest().await?.client;
569        let run_url = &self.inner.run_url;
570        let url = format!("{run_url}/app/list_channels");
571        let req = run_rest.get(url, &Empty {});
572        run_rest.send(req).await
573    }
574
575    async fn sign_message(
576        &self,
577        data: SignMsgRequest,
578    ) -> Result<SignMsgResponse, NodeApiError> {
579        let run_rest = &self.authed_run_rest().await?.client;
580        let run_url = &self.inner.run_url;
581        let url = format!("{run_url}/app/sign_message");
582        let req = run_rest.post(url, &data);
583        run_rest.send(req).await
584    }
585
586    async fn verify_message(
587        &self,
588        data: VerifyMsgRequest,
589    ) -> Result<VerifyMsgResponse, NodeApiError> {
590        let run_rest = &self.authed_run_rest().await?.client;
591        let run_url = &self.inner.run_url;
592        let url = format!("{run_url}/app/verify_message");
593        let req = run_rest.post(url, &data);
594        run_rest.send(req).await
595    }
596
597    async fn open_channel(
598        &self,
599        data: OpenChannelRequest,
600    ) -> Result<OpenChannelResponse, NodeApiError> {
601        let run_rest = &self.authed_run_rest().await?.client;
602        let run_url = &self.inner.run_url;
603        let url = format!("{run_url}/app/open_channel");
604        let req = run_rest.post(url, &data);
605        run_rest.send(req).await
606    }
607
608    async fn preflight_open_channel(
609        &self,
610        data: PreflightOpenChannelRequest,
611    ) -> Result<PreflightOpenChannelResponse, NodeApiError> {
612        let run_rest = &self.authed_run_rest().await?.client;
613        let run_url = &self.inner.run_url;
614        let url = format!("{run_url}/app/preflight_open_channel");
615        let req = run_rest.post(url, &data);
616        run_rest.send(req).await
617    }
618
619    async fn close_channel(
620        &self,
621        data: CloseChannelRequest,
622    ) -> Result<Empty, NodeApiError> {
623        let run_rest = &self.authed_run_rest().await?.client;
624        let run_url = &self.inner.run_url;
625        let url = format!("{run_url}/app/close_channel");
626        let req = run_rest.post(url, &data);
627        run_rest.send(req).await
628    }
629
630    async fn preflight_close_channel(
631        &self,
632        data: PreflightCloseChannelRequest,
633    ) -> Result<PreflightCloseChannelResponse, NodeApiError> {
634        let run_rest = &self.authed_run_rest().await?.client;
635        let run_url = &self.inner.run_url;
636        let url = format!("{run_url}/app/preflight_close_channel");
637        let req = run_rest.post(url, &data);
638        run_rest.send(req).await
639    }
640
641    async fn create_invoice(
642        &self,
643        data: CreateInvoiceRequest,
644    ) -> Result<CreateInvoiceResponse, NodeApiError> {
645        let run_rest = &self.authed_run_rest().await?.client;
646        let run_url = &self.inner.run_url;
647        let url = format!("{run_url}/app/create_invoice");
648        let req = run_rest.post(url, &data);
649        run_rest.send(req).await
650    }
651
652    async fn pay_invoice(
653        &self,
654        req: PayInvoiceRequest,
655    ) -> Result<PayInvoiceResponse, NodeApiError> {
656        let run_rest = &self.authed_run_rest().await?.client;
657        let run_url = &self.inner.run_url;
658        let url = format!("{run_url}/app/pay_invoice");
659        // `pay_invoice` may call `max_flow` which takes a long time.
660        let req = run_rest
661            .post(url, &req)
662            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
663        run_rest.send(req).await
664    }
665
666    async fn preflight_pay_invoice(
667        &self,
668        req: PreflightPayInvoiceRequest,
669    ) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
670        let run_rest = &self.authed_run_rest().await?.client;
671        let run_url = &self.inner.run_url;
672        let url = format!("{run_url}/app/preflight_pay_invoice");
673        // `preflight_pay_invoice` may call `max_flow` which takes a long time.
674        let req = run_rest
675            .post(url, &req)
676            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
677        run_rest.send(req).await
678    }
679
680    async fn pay_onchain(
681        &self,
682        req: PayOnchainRequest,
683    ) -> Result<PayOnchainResponse, NodeApiError> {
684        let run_rest = &self.authed_run_rest().await?.client;
685        let run_url = &self.inner.run_url;
686        let url = format!("{run_url}/app/pay_onchain");
687        let req = run_rest.post(url, &req);
688        run_rest.send(req).await
689    }
690
691    async fn preflight_pay_onchain(
692        &self,
693        req: PreflightPayOnchainRequest,
694    ) -> Result<PreflightPayOnchainResponse, NodeApiError> {
695        let run_rest = &self.authed_run_rest().await?.client;
696        let run_url = &self.inner.run_url;
697        let url = format!("{run_url}/app/preflight_pay_onchain");
698        let req = run_rest.post(url, &req);
699        run_rest.send(req).await
700    }
701
702    async fn create_offer(
703        &self,
704        req: CreateOfferRequest,
705    ) -> Result<CreateOfferResponse, NodeApiError> {
706        let run_rest = &self.authed_run_rest().await?.client;
707        let run_url = &self.inner.run_url;
708        let url = format!("{run_url}/app/create_offer");
709        let req = run_rest.post(url, &req);
710        run_rest.send(req).await
711    }
712
713    async fn pay_offer(
714        &self,
715        req: PayOfferRequest,
716    ) -> Result<PayOfferResponse, NodeApiError> {
717        let run_rest = &self.authed_run_rest().await?.client;
718        let run_url = &self.inner.run_url;
719        let url = format!("{run_url}/app/pay_offer");
720        let req = run_rest.post(url, &req);
721        run_rest.send(req).await
722    }
723
724    async fn preflight_pay_offer(
725        &self,
726        req: PreflightPayOfferRequest,
727    ) -> Result<PreflightPayOfferResponse, NodeApiError> {
728        let run_rest = &self.authed_run_rest().await?.client;
729        let run_url = &self.inner.run_url;
730        let url = format!("{run_url}/app/preflight_pay_offer");
731        let req = run_rest.post(url, &req);
732        run_rest.send(req).await
733    }
734
735    async fn get_address(&self) -> Result<GetAddressResponse, NodeApiError> {
736        let run_rest = &self.authed_run_rest().await?.client;
737        let run_url = &self.inner.run_url;
738        let url = format!("{run_url}/app/get_address");
739        let req = run_rest.post(url, &Empty {});
740        run_rest.send(req).await
741    }
742
743    async fn get_payments_by_indexes(
744        &self,
745        _: PaymentCreatedIndexes,
746    ) -> Result<VecBasicPaymentV1, NodeApiError> {
747        unimplemented!("Deprecated")
748    }
749
750    async fn get_new_payments(
751        &self,
752        _: GetNewPayments,
753    ) -> Result<VecBasicPaymentV1, NodeApiError> {
754        unimplemented!("Deprecated")
755    }
756
757    async fn get_updated_payments(
758        &self,
759        req: GetUpdatedPayments,
760    ) -> Result<VecBasicPaymentV2, NodeApiError> {
761        let run_rest = &self.authed_run_rest().await?.client;
762        let run_url = &self.inner.run_url;
763        let url = format!("{run_url}/app/payments/updated");
764        let req = run_rest.get(url, &req);
765        run_rest.send(req).await
766    }
767
768    async fn get_payment_by_id(
769        &self,
770        req: PaymentIdStruct,
771    ) -> Result<MaybeBasicPaymentV2, NodeApiError> {
772        let run_rest = &self.authed_run_rest().await?.client;
773        let run_url = &self.inner.run_url;
774        let url = format!("{run_url}/app/v1/payments/id");
775        let req = run_rest.get(url, &req);
776        run_rest.send(req).await
777    }
778
779    async fn update_payment_note(
780        &self,
781        req: UpdatePaymentNote,
782    ) -> Result<Empty, NodeApiError> {
783        let run_rest = &self.authed_run_rest().await?.client;
784        let run_url = &self.inner.run_url;
785        let url = format!("{run_url}/app/payments/note");
786        let req = run_rest.put(url, &req);
787        run_rest.send(req).await
788    }
789
790    async fn get_revocable_clients(
791        &self,
792        req: GetRevocableClients,
793    ) -> Result<RevocableClients, NodeApiError> {
794        let run_rest = &self.authed_run_rest().await?.client;
795        let run_url = &self.inner.run_url;
796        let url = format!("{run_url}/app/clients");
797        let req = run_rest.get(url, &req);
798        run_rest.send(req).await
799    }
800
801    async fn create_revocable_client(
802        &self,
803        req: CreateRevocableClientRequest,
804    ) -> Result<CreateRevocableClientResponse, NodeApiError> {
805        let run_rest = &self.authed_run_rest().await?.client;
806        let run_url = &self.inner.run_url;
807        let url = format!("{run_url}/app/clients");
808        let req = run_rest.post(url, &req);
809        run_rest.send(req).await
810    }
811
812    async fn update_revocable_client(
813        &self,
814        req: UpdateClientRequest,
815    ) -> Result<UpdateClientResponse, NodeApiError> {
816        let run_rest = &self.authed_run_rest().await?.client;
817        let run_url = &self.inner.run_url;
818        let url = format!("{run_url}/app/clients");
819        let req = run_rest.put(url, &req);
820        run_rest.send(req).await
821    }
822
823    async fn list_broadcasted_txs(
824        &self,
825    ) -> Result<serde_json::Value, NodeApiError> {
826        let run_rest = &self.authed_run_rest().await?.client;
827        let run_url = &self.inner.run_url;
828        let url = format!("{run_url}/app/list_broadcasted_txs");
829        let req = run_rest.get(url, &Empty {});
830        run_rest.send(req).await
831    }
832
833    async fn backup_info(&self) -> Result<BackupInfo, NodeApiError> {
834        let run_rest = &self.authed_run_rest().await?.client;
835        let run_url = &self.inner.run_url;
836        let url = format!("{run_url}/app/backup");
837        let req = run_rest.get(url, &Empty {});
838        run_rest.send(req).await
839    }
840
841    async fn setup_gdrive(
842        &self,
843        req: SetupGDrive,
844    ) -> Result<Empty, NodeApiError> {
845        let run_rest = &self.authed_run_rest().await?.client;
846        let run_url = &self.inner.run_url;
847        let url = format!("{run_url}/app/backup/gdrive");
848        let req = run_rest.post(url, &req);
849        run_rest.send(req).await
850    }
851
852    async fn get_human_bitcoin_address(
853        &self,
854    ) -> Result<HumanBitcoinAddress, NodeApiError> {
855        let run_rest = &self.authed_run_rest().await?.client;
856        let run_url = &self.inner.run_url;
857        let url = format!("{run_url}/app/human_bitcoin_address");
858        let req = run_rest.get(url, &Empty {});
859        run_rest.send(req).await
860    }
861
862    async fn update_human_bitcoin_address(
863        &self,
864        req: UsernameStruct,
865    ) -> Result<HumanBitcoinAddress, NodeApiError> {
866        let run_rest = &self.authed_run_rest().await?.client;
867        let run_url = &self.inner.run_url;
868        let url = format!("{run_url}/app/human_bitcoin_address");
869        let req = run_rest.put(url, &req);
870        run_rest.send(req).await
871    }
872
873    #[allow(deprecated)]
874    async fn get_payment_address(
875        &self,
876    ) -> Result<HumanBitcoinAddress, NodeApiError> {
877        self.get_human_bitcoin_address().await
878    }
879
880    #[allow(deprecated)]
881    async fn update_payment_address(
882        &self,
883        req: UsernameStruct,
884    ) -> Result<HumanBitcoinAddress, NodeApiError> {
885        self.update_human_bitcoin_address(req).await
886    }
887
888    async fn list_nwc_clients(
889        &self,
890    ) -> Result<ListNwcClientResponse, NodeApiError> {
891        let run_rest = &self.authed_run_rest().await?.client;
892        let run_url = &self.inner.run_url;
893        let url = format!("{run_url}/app/nwc_clients");
894        let req = run_rest.get(url, &Empty {});
895        run_rest.send(req).await
896    }
897
898    async fn create_nwc_client(
899        &self,
900        req: CreateNwcClientRequest,
901    ) -> Result<CreateNwcClientResponse, NodeApiError> {
902        let run_rest = &self.authed_run_rest().await?.client;
903        let run_url = &self.inner.run_url;
904        let url = format!("{run_url}/app/nwc_clients");
905        let req = run_rest.post(url, &req);
906        run_rest.send(req).await
907    }
908
909    async fn update_nwc_client(
910        &self,
911        req: UpdateNwcClientRequest,
912    ) -> Result<UpdateNwcClientResponse, NodeApiError> {
913        let run_rest = &self.authed_run_rest().await?.client;
914        let run_url = &self.inner.run_url;
915        let url = format!("{run_url}/app/nwc_clients");
916        let req = run_rest.put(url, &req);
917        run_rest.send(req).await
918    }
919
920    async fn delete_nwc_client(
921        &self,
922        req: NostrPkStruct,
923    ) -> Result<Empty, NodeApiError> {
924        let run_rest = &self.authed_run_rest().await?.client;
925        let run_url = &self.inner.run_url;
926        let url = format!("{run_url}/app/nwc_clients");
927        let req = run_rest.delete(url, &req);
928        run_rest.send(req).await
929    }
930}
931
932// --- impl RunRestClient --- //
933
934impl RunRestClient {
935    fn new(
936        gateway_client: &GatewayClient,
937        run_url: &str,
938        auth_token: TokenWithExpiration,
939        tls_config: rustls::ClientConfig,
940    ) -> anyhow::Result<Self> {
941        let TokenWithExpiration { expiration, token } = auth_token;
942        let (from, to) = (gateway_client.rest.user_agent().clone(), "node-run");
943        let proxy =
944            static_proxy_config(&gateway_client.gateway_url, run_url, token)?;
945        let client = RestClient::client_builder(&from)
946            .proxy(proxy)
947            .use_preconfigured_tls(tls_config.clone())
948            .build()
949            .context("Failed to build client")?;
950        let client = RestClient::from_inner(client, from, to);
951
952        Ok(Self {
953            client,
954            token_expiration: expiration,
955        })
956    }
957
958    /// Returns `true` if we should refresh the token (i.e., it's expired or
959    /// about to expire).
960    fn token_needs_refresh(&self, now: SystemTime) -> bool {
961        auth::token_needs_refresh(now, self.token_expiration)
962    }
963}
964
965/// Build a static [`reqwest::Proxy`] config which proxies requests to the user
966/// node via the lexe gateway CONNECT proxy and authenticates using the provided
967/// bearer auth token.
968///
969/// User nodes are not exposed to the public internet. Instead, a secure
970/// tunnel (TLS) is first established via the lexe gateway proxy to the
971/// user's node only after they have successfully authenticated with Lexe.
972///
973/// Essentially, we have a TLS-in-TLS scheme:
974///
975/// - The outer layer terminates at Lexe's gateway proxy and prevents the public
976///   internet from seeing auth tokens sent to the gateway proxy.
977/// - The inner layer terminates inside the SGX enclave and prevents the Lexe
978///   operators from snooping on or tampering with data sent to/from the app <->
979///   node.
980///
981/// This function sets up a client-side [`reqwest::Proxy`] config which
982/// looks for requests to the user node (i.e., urls starting with one of the
983/// fake DNS names `{mr_short}.provision.lexe.app` or `run.lexe.app`) and
984/// instructs `reqwest` to use an HTTPS CONNECT tunnel over which to send
985/// the requests.
986fn static_proxy_config(
987    gateway_url: &str,
988    node_url: &str,
989    auth_token: BearerAuthToken,
990) -> anyhow::Result<reqwest::Proxy> {
991    let node_url = Url::parse(node_url).context("Invalid node url")?;
992    let gateway_url = gateway_url.to_owned();
993
994    // TODO(phlip9): include "Bearer " prefix in auth token
995    let auth_header = http::HeaderValue::from_maybe_shared(ByteStr::from(
996        format!("Bearer {auth_token}"),
997    ))?;
998
999    let proxy = reqwest::Proxy::custom(move |url| {
1000        // Proxy requests to the user node via the gateway CONNECT proxy
1001        if url_base_eq(url, &node_url) {
1002            Some(gateway_url.clone())
1003        } else {
1004            None
1005        }
1006    })
1007    // Authenticate with the gateway CONNECT proxy
1008    // `Proxy-Authorization: Bearer <token>`
1009    .custom_http_auth(auth_header);
1010
1011    Ok(proxy)
1012}
1013
1014fn url_base_eq(u1: &Url, u2: &Url) -> bool {
1015    u1.scheme() == u2.scheme()
1016        && u1.host() == u2.host()
1017        && u1.port_or_known_default() == u2.port_or_known_default()
1018}
1019
1020#[cfg(test)]
1021mod test {
1022    use super::*;
1023
1024    #[test]
1025    fn test_url_base_eq() {
1026        // multiple disjoint equivalence classes of urls, according to the
1027        // equivalence relation `url_base_eq`.
1028        let eq_classes = vec![
1029            vec![
1030                "https://hello.world",
1031                "https://hello.world/",
1032                "https://hello.world/my_cool_method",
1033                "https://hello.world/my_cool_method&query=params",
1034                "https://hello.world/&query=params",
1035            ],
1036            vec![
1037                "http://hello.world",
1038                "http://hello.world/",
1039                "http://hello.world/my_cool_method",
1040                "http://hello.world/my_cool_method&query=params",
1041                "http://hello.world/&query=params",
1042            ],
1043            vec![
1044                "https://hello.world:8080",
1045                "https://hello.world:8080/",
1046                "https://hello.world:8080/my_cool_method",
1047                "https://hello.world:8080/my_cool_method&query=params",
1048                "https://hello.world:8080/&query=params",
1049            ],
1050            vec![
1051                "https://127.0.0.1:8080",
1052                "https://127.0.0.1:8080/",
1053                "https://127.0.0.1:8080/my_cool_method",
1054                "https://127.0.0.1:8080/my_cool_method&query=params",
1055                "https://127.0.0.1:8080/&query=params",
1056            ],
1057            vec![
1058                "https://[::1]:8080",
1059                "https://[::1]:8080/",
1060                "https://[::1]:8080/my_cool_method",
1061                "https://[::1]:8080/my_cool_method&query=params",
1062                "https://[::1]:8080/&query=params",
1063            ],
1064        ];
1065
1066        let eq_classes = eq_classes
1067            .into_iter()
1068            .map(|eq_class| {
1069                eq_class
1070                    .into_iter()
1071                    .map(|url| Url::parse(url).unwrap())
1072                    .collect::<Vec<_>>()
1073            })
1074            .collect::<Vec<_>>();
1075
1076        let n_classes = eq_classes.len();
1077        let n_urls = eq_classes[0].len();
1078
1079        // all elements of an equivalence class are equal
1080        for eq_class in &eq_classes {
1081            for idx_u1 in 0..n_urls {
1082                // start at `idx_u1` to also check reflexivity
1083                for idx_u2 in idx_u1..n_urls {
1084                    let u1 = &eq_class[idx_u1];
1085                    let u2 = &eq_class[idx_u2];
1086                    assert!(url_base_eq(u1, u2));
1087                    // check symmetry
1088                    assert!(url_base_eq(u2, u1));
1089                }
1090            }
1091        }
1092
1093        // elements from disjoint equivalence classes are not equal
1094        for idx_class1 in 0..(n_classes - 1) {
1095            let eq_class1 = &eq_classes[idx_class1];
1096            for eq_class2 in eq_classes.iter().skip(idx_class1 + 1) {
1097                for u1 in eq_class1 {
1098                    for u2 in eq_class2 {
1099                        // check disjoint
1100                        assert!(!url_base_eq(u1, u2));
1101                        assert!(!url_base_eq(u2, u1));
1102                    }
1103                }
1104            }
1105        }
1106    }
1107}