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            EnclavesToProvisionRequest, GetAddressResponse, GetNewPayments,
30            GetUpdatedPayments, HumanBitcoinAddress, ListChannelsResponse,
31            NodeInfo, OpenChannelRequest, OpenChannelResponse,
32            PayInvoiceRequest, PayInvoiceResponse, PayOfferRequest,
33            PayOfferResponse, PayOnchainRequest, PayOnchainResponse,
34            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/node_info");
545        let req = run_rest.get(url, &Empty {});
546        run_rest.send(req).await
547    }
548
549    async fn list_channels(
550        &self,
551    ) -> Result<ListChannelsResponse, NodeApiError> {
552        let run_rest = &self.authed_run_rest().await?.client;
553        let run_url = &self.inner.run_url;
554        let url = format!("{run_url}/app/list_channels");
555        let req = run_rest.get(url, &Empty {});
556        run_rest.send(req).await
557    }
558
559    async fn sign_message(
560        &self,
561        data: SignMsgRequest,
562    ) -> Result<SignMsgResponse, NodeApiError> {
563        let run_rest = &self.authed_run_rest().await?.client;
564        let run_url = &self.inner.run_url;
565        let url = format!("{run_url}/app/sign_message");
566        let req = run_rest.post(url, &data);
567        run_rest.send(req).await
568    }
569
570    async fn verify_message(
571        &self,
572        data: VerifyMsgRequest,
573    ) -> Result<VerifyMsgResponse, NodeApiError> {
574        let run_rest = &self.authed_run_rest().await?.client;
575        let run_url = &self.inner.run_url;
576        let url = format!("{run_url}/app/verify_message");
577        let req = run_rest.post(url, &data);
578        run_rest.send(req).await
579    }
580
581    async fn open_channel(
582        &self,
583        data: OpenChannelRequest,
584    ) -> Result<OpenChannelResponse, NodeApiError> {
585        let run_rest = &self.authed_run_rest().await?.client;
586        let run_url = &self.inner.run_url;
587        let url = format!("{run_url}/app/open_channel");
588        let req = run_rest.post(url, &data);
589        run_rest.send(req).await
590    }
591
592    async fn preflight_open_channel(
593        &self,
594        data: PreflightOpenChannelRequest,
595    ) -> Result<PreflightOpenChannelResponse, NodeApiError> {
596        let run_rest = &self.authed_run_rest().await?.client;
597        let run_url = &self.inner.run_url;
598        let url = format!("{run_url}/app/preflight_open_channel");
599        let req = run_rest.post(url, &data);
600        run_rest.send(req).await
601    }
602
603    async fn close_channel(
604        &self,
605        data: CloseChannelRequest,
606    ) -> Result<Empty, NodeApiError> {
607        let run_rest = &self.authed_run_rest().await?.client;
608        let run_url = &self.inner.run_url;
609        let url = format!("{run_url}/app/close_channel");
610        let req = run_rest.post(url, &data);
611        run_rest.send(req).await
612    }
613
614    async fn preflight_close_channel(
615        &self,
616        data: PreflightCloseChannelRequest,
617    ) -> Result<PreflightCloseChannelResponse, NodeApiError> {
618        let run_rest = &self.authed_run_rest().await?.client;
619        let run_url = &self.inner.run_url;
620        let url = format!("{run_url}/app/preflight_close_channel");
621        let req = run_rest.post(url, &data);
622        run_rest.send(req).await
623    }
624
625    async fn create_invoice(
626        &self,
627        data: CreateInvoiceRequest,
628    ) -> Result<CreateInvoiceResponse, NodeApiError> {
629        let run_rest = &self.authed_run_rest().await?.client;
630        let run_url = &self.inner.run_url;
631        let url = format!("{run_url}/app/create_invoice");
632        let req = run_rest.post(url, &data);
633        run_rest.send(req).await
634    }
635
636    async fn pay_invoice(
637        &self,
638        req: PayInvoiceRequest,
639    ) -> Result<PayInvoiceResponse, NodeApiError> {
640        let run_rest = &self.authed_run_rest().await?.client;
641        let run_url = &self.inner.run_url;
642        let url = format!("{run_url}/app/pay_invoice");
643        // `pay_invoice` may call `max_flow` which takes a long time.
644        let req = run_rest
645            .post(url, &req)
646            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
647        run_rest.send(req).await
648    }
649
650    async fn preflight_pay_invoice(
651        &self,
652        req: PreflightPayInvoiceRequest,
653    ) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
654        let run_rest = &self.authed_run_rest().await?.client;
655        let run_url = &self.inner.run_url;
656        let url = format!("{run_url}/app/preflight_pay_invoice");
657        // `preflight_pay_invoice` may call `max_flow` which takes a long time.
658        let req = run_rest
659            .post(url, &req)
660            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
661        run_rest.send(req).await
662    }
663
664    async fn pay_onchain(
665        &self,
666        req: PayOnchainRequest,
667    ) -> Result<PayOnchainResponse, NodeApiError> {
668        let run_rest = &self.authed_run_rest().await?.client;
669        let run_url = &self.inner.run_url;
670        let url = format!("{run_url}/app/pay_onchain");
671        let req = run_rest.post(url, &req);
672        run_rest.send(req).await
673    }
674
675    async fn preflight_pay_onchain(
676        &self,
677        req: PreflightPayOnchainRequest,
678    ) -> Result<PreflightPayOnchainResponse, NodeApiError> {
679        let run_rest = &self.authed_run_rest().await?.client;
680        let run_url = &self.inner.run_url;
681        let url = format!("{run_url}/app/preflight_pay_onchain");
682        let req = run_rest.post(url, &req);
683        run_rest.send(req).await
684    }
685
686    async fn create_offer(
687        &self,
688        req: CreateOfferRequest,
689    ) -> Result<CreateOfferResponse, NodeApiError> {
690        let run_rest = &self.authed_run_rest().await?.client;
691        let run_url = &self.inner.run_url;
692        let url = format!("{run_url}/app/create_offer");
693        let req = run_rest.post(url, &req);
694        run_rest.send(req).await
695    }
696
697    async fn pay_offer(
698        &self,
699        req: PayOfferRequest,
700    ) -> Result<PayOfferResponse, NodeApiError> {
701        let run_rest = &self.authed_run_rest().await?.client;
702        let run_url = &self.inner.run_url;
703        let url = format!("{run_url}/app/pay_offer");
704        let req = run_rest.post(url, &req);
705        run_rest.send(req).await
706    }
707
708    async fn preflight_pay_offer(
709        &self,
710        req: PreflightPayOfferRequest,
711    ) -> Result<PreflightPayOfferResponse, NodeApiError> {
712        let run_rest = &self.authed_run_rest().await?.client;
713        let run_url = &self.inner.run_url;
714        let url = format!("{run_url}/app/preflight_pay_offer");
715        let req = run_rest.post(url, &req);
716        run_rest.send(req).await
717    }
718
719    async fn get_address(&self) -> Result<GetAddressResponse, NodeApiError> {
720        let run_rest = &self.authed_run_rest().await?.client;
721        let run_url = &self.inner.run_url;
722        let url = format!("{run_url}/app/get_address");
723        let req = run_rest.post(url, &Empty {});
724        run_rest.send(req).await
725    }
726
727    async fn get_payments_by_indexes(
728        &self,
729        _: PaymentCreatedIndexes,
730    ) -> Result<VecBasicPaymentV1, NodeApiError> {
731        unimplemented!("Deprecated")
732    }
733
734    async fn get_new_payments(
735        &self,
736        _: GetNewPayments,
737    ) -> Result<VecBasicPaymentV1, NodeApiError> {
738        unimplemented!("Deprecated")
739    }
740
741    async fn get_updated_payments(
742        &self,
743        req: GetUpdatedPayments,
744    ) -> Result<VecBasicPaymentV2, NodeApiError> {
745        let run_rest = &self.authed_run_rest().await?.client;
746        let run_url = &self.inner.run_url;
747        let url = format!("{run_url}/app/payments/updated");
748        let req = run_rest.get(url, &req);
749        run_rest.send(req).await
750    }
751
752    async fn get_payment_by_id(
753        &self,
754        req: PaymentIdStruct,
755    ) -> Result<MaybeBasicPaymentV2, NodeApiError> {
756        let run_rest = &self.authed_run_rest().await?.client;
757        let run_url = &self.inner.run_url;
758        let url = format!("{run_url}/app/v1/payments/id");
759        let req = run_rest.get(url, &req);
760        run_rest.send(req).await
761    }
762
763    async fn update_payment_note(
764        &self,
765        req: UpdatePaymentNote,
766    ) -> Result<Empty, NodeApiError> {
767        let run_rest = &self.authed_run_rest().await?.client;
768        let run_url = &self.inner.run_url;
769        let url = format!("{run_url}/app/payments/note");
770        let req = run_rest.put(url, &req);
771        run_rest.send(req).await
772    }
773
774    async fn get_revocable_clients(
775        &self,
776        req: GetRevocableClients,
777    ) -> Result<RevocableClients, NodeApiError> {
778        let run_rest = &self.authed_run_rest().await?.client;
779        let run_url = &self.inner.run_url;
780        let url = format!("{run_url}/app/clients");
781        let req = run_rest.get(url, &req);
782        run_rest.send(req).await
783    }
784
785    async fn create_revocable_client(
786        &self,
787        req: CreateRevocableClientRequest,
788    ) -> Result<CreateRevocableClientResponse, NodeApiError> {
789        let run_rest = &self.authed_run_rest().await?.client;
790        let run_url = &self.inner.run_url;
791        let url = format!("{run_url}/app/clients");
792        let req = run_rest.post(url, &req);
793        run_rest.send(req).await
794    }
795
796    async fn update_revocable_client(
797        &self,
798        req: UpdateClientRequest,
799    ) -> Result<UpdateClientResponse, NodeApiError> {
800        let run_rest = &self.authed_run_rest().await?.client;
801        let run_url = &self.inner.run_url;
802        let url = format!("{run_url}/app/clients");
803        let req = run_rest.put(url, &req);
804        run_rest.send(req).await
805    }
806
807    async fn list_broadcasted_txs(
808        &self,
809    ) -> Result<serde_json::Value, NodeApiError> {
810        let run_rest = &self.authed_run_rest().await?.client;
811        let run_url = &self.inner.run_url;
812        let url = format!("{run_url}/app/list_broadcasted_txs");
813        let req = run_rest.get(url, &Empty {});
814        run_rest.send(req).await
815    }
816
817    async fn backup_info(&self) -> Result<BackupInfo, NodeApiError> {
818        let run_rest = &self.authed_run_rest().await?.client;
819        let run_url = &self.inner.run_url;
820        let url = format!("{run_url}/app/backup");
821        let req = run_rest.get(url, &Empty {});
822        run_rest.send(req).await
823    }
824
825    async fn setup_gdrive(
826        &self,
827        req: SetupGDrive,
828    ) -> Result<Empty, NodeApiError> {
829        let run_rest = &self.authed_run_rest().await?.client;
830        let run_url = &self.inner.run_url;
831        let url = format!("{run_url}/app/backup/gdrive");
832        let req = run_rest.post(url, &req);
833        run_rest.send(req).await
834    }
835
836    async fn get_human_bitcoin_address(
837        &self,
838    ) -> Result<HumanBitcoinAddress, NodeApiError> {
839        let run_rest = &self.authed_run_rest().await?.client;
840        let run_url = &self.inner.run_url;
841        let url = format!("{run_url}/app/human_bitcoin_address");
842        let req = run_rest.get(url, &Empty {});
843        run_rest.send(req).await
844    }
845
846    async fn update_human_bitcoin_address(
847        &self,
848        req: UsernameStruct,
849    ) -> Result<HumanBitcoinAddress, NodeApiError> {
850        let run_rest = &self.authed_run_rest().await?.client;
851        let run_url = &self.inner.run_url;
852        let url = format!("{run_url}/app/human_bitcoin_address");
853        let req = run_rest.put(url, &req);
854        run_rest.send(req).await
855    }
856
857    #[allow(deprecated)]
858    async fn get_payment_address(
859        &self,
860    ) -> Result<HumanBitcoinAddress, NodeApiError> {
861        self.get_human_bitcoin_address().await
862    }
863
864    #[allow(deprecated)]
865    async fn update_payment_address(
866        &self,
867        req: UsernameStruct,
868    ) -> Result<HumanBitcoinAddress, NodeApiError> {
869        self.update_human_bitcoin_address(req).await
870    }
871
872    async fn list_nwc_clients(
873        &self,
874    ) -> Result<ListNwcClientResponse, NodeApiError> {
875        let run_rest = &self.authed_run_rest().await?.client;
876        let run_url = &self.inner.run_url;
877        let url = format!("{run_url}/app/nwc_clients");
878        let req = run_rest.get(url, &Empty {});
879        run_rest.send(req).await
880    }
881
882    async fn create_nwc_client(
883        &self,
884        req: CreateNwcClientRequest,
885    ) -> Result<CreateNwcClientResponse, NodeApiError> {
886        let run_rest = &self.authed_run_rest().await?.client;
887        let run_url = &self.inner.run_url;
888        let url = format!("{run_url}/app/nwc_clients");
889        let req = run_rest.post(url, &req);
890        run_rest.send(req).await
891    }
892
893    async fn update_nwc_client(
894        &self,
895        req: UpdateNwcClientRequest,
896    ) -> Result<UpdateNwcClientResponse, NodeApiError> {
897        let run_rest = &self.authed_run_rest().await?.client;
898        let run_url = &self.inner.run_url;
899        let url = format!("{run_url}/app/nwc_clients");
900        let req = run_rest.put(url, &req);
901        run_rest.send(req).await
902    }
903
904    async fn delete_nwc_client(
905        &self,
906        req: NostrPkStruct,
907    ) -> Result<Empty, NodeApiError> {
908        let run_rest = &self.authed_run_rest().await?.client;
909        let run_url = &self.inner.run_url;
910        let url = format!("{run_url}/app/nwc_clients");
911        let req = run_rest.delete(url, &req);
912        run_rest.send(req).await
913    }
914}
915
916// --- impl RunRestClient --- //
917
918impl RunRestClient {
919    fn new(
920        gateway_client: &GatewayClient,
921        run_url: &str,
922        auth_token: TokenWithExpiration,
923        tls_config: rustls::ClientConfig,
924    ) -> anyhow::Result<Self> {
925        let TokenWithExpiration { expiration, token } = auth_token;
926        let (from, to) = (gateway_client.rest.user_agent().clone(), "node-run");
927        let proxy =
928            static_proxy_config(&gateway_client.gateway_url, run_url, token)?;
929        let client = RestClient::client_builder(&from)
930            .proxy(proxy)
931            .use_preconfigured_tls(tls_config.clone())
932            .build()
933            .context("Failed to build client")?;
934        let client = RestClient::from_inner(client, from, to);
935
936        Ok(Self {
937            client,
938            token_expiration: expiration,
939        })
940    }
941
942    /// Returns `true` if we should refresh the token (i.e., it's expired or
943    /// about to expire).
944    fn token_needs_refresh(&self, now: SystemTime) -> bool {
945        auth::token_needs_refresh(now, self.token_expiration)
946    }
947}
948
949/// Build a static [`reqwest::Proxy`] config which proxies requests to the user
950/// node via the lexe gateway CONNECT proxy and authenticates using the provided
951/// bearer auth token.
952///
953/// User nodes are not exposed to the public internet. Instead, a secure
954/// tunnel (TLS) is first established via the lexe gateway proxy to the
955/// user's node only after they have successfully authenticated with Lexe.
956///
957/// Essentially, we have a TLS-in-TLS scheme:
958///
959/// - The outer layer terminates at Lexe's gateway proxy and prevents the public
960///   internet from seeing auth tokens sent to the gateway proxy.
961/// - The inner layer terminates inside the SGX enclave and prevents the Lexe
962///   operators from snooping on or tampering with data sent to/from the app <->
963///   node.
964///
965/// This function sets up a client-side [`reqwest::Proxy`] config which
966/// looks for requests to the user node (i.e., urls starting with one of the
967/// fake DNS names `{mr_short}.provision.lexe.app` or `run.lexe.app`) and
968/// instructs `reqwest` to use an HTTPS CONNECT tunnel over which to send
969/// the requests.
970fn static_proxy_config(
971    gateway_url: &str,
972    node_url: &str,
973    auth_token: BearerAuthToken,
974) -> anyhow::Result<reqwest::Proxy> {
975    let node_url = Url::parse(node_url).context("Invalid node url")?;
976    let gateway_url = gateway_url.to_owned();
977
978    // TODO(phlip9): include "Bearer " prefix in auth token
979    let auth_header = http::HeaderValue::from_maybe_shared(ByteStr::from(
980        format!("Bearer {auth_token}"),
981    ))?;
982
983    let proxy = reqwest::Proxy::custom(move |url| {
984        // Proxy requests to the user node via the gateway CONNECT proxy
985        if url_base_eq(url, &node_url) {
986            Some(gateway_url.clone())
987        } else {
988            None
989        }
990    })
991    // Authenticate with the gateway CONNECT proxy
992    // `Proxy-Authorization: Bearer <token>`
993    .custom_http_auth(auth_header);
994
995    Ok(proxy)
996}
997
998fn url_base_eq(u1: &Url, u2: &Url) -> bool {
999    u1.scheme() == u2.scheme()
1000        && u1.host() == u2.host()
1001        && u1.port_or_known_default() == u2.port_or_known_default()
1002}
1003
1004#[cfg(test)]
1005mod test {
1006    use super::*;
1007
1008    #[test]
1009    fn test_url_base_eq() {
1010        // multiple disjoint equivalence classes of urls, according to the
1011        // equivalence relation `url_base_eq`.
1012        let eq_classes = vec![
1013            vec![
1014                "https://hello.world",
1015                "https://hello.world/",
1016                "https://hello.world/my_cool_method",
1017                "https://hello.world/my_cool_method&query=params",
1018                "https://hello.world/&query=params",
1019            ],
1020            vec![
1021                "http://hello.world",
1022                "http://hello.world/",
1023                "http://hello.world/my_cool_method",
1024                "http://hello.world/my_cool_method&query=params",
1025                "http://hello.world/&query=params",
1026            ],
1027            vec![
1028                "https://hello.world:8080",
1029                "https://hello.world:8080/",
1030                "https://hello.world:8080/my_cool_method",
1031                "https://hello.world:8080/my_cool_method&query=params",
1032                "https://hello.world:8080/&query=params",
1033            ],
1034            vec![
1035                "https://127.0.0.1:8080",
1036                "https://127.0.0.1:8080/",
1037                "https://127.0.0.1:8080/my_cool_method",
1038                "https://127.0.0.1:8080/my_cool_method&query=params",
1039                "https://127.0.0.1:8080/&query=params",
1040            ],
1041            vec![
1042                "https://[::1]:8080",
1043                "https://[::1]:8080/",
1044                "https://[::1]:8080/my_cool_method",
1045                "https://[::1]:8080/my_cool_method&query=params",
1046                "https://[::1]:8080/&query=params",
1047            ],
1048        ];
1049
1050        let eq_classes = eq_classes
1051            .into_iter()
1052            .map(|eq_class| {
1053                eq_class
1054                    .into_iter()
1055                    .map(|url| Url::parse(url).unwrap())
1056                    .collect::<Vec<_>>()
1057            })
1058            .collect::<Vec<_>>();
1059
1060        let n_classes = eq_classes.len();
1061        let n_urls = eq_classes[0].len();
1062
1063        // all elements of an equivalence class are equal
1064        for eq_class in &eq_classes {
1065            for idx_u1 in 0..n_urls {
1066                // start at `idx_u1` to also check reflexivity
1067                for idx_u2 in idx_u1..n_urls {
1068                    let u1 = &eq_class[idx_u1];
1069                    let u2 = &eq_class[idx_u2];
1070                    assert!(url_base_eq(u1, u2));
1071                    // check symmetry
1072                    assert!(url_base_eq(u2, u1));
1073                }
1074            }
1075        }
1076
1077        // elements from disjoint equivalence classes are not equal
1078        for idx_class1 in 0..(n_classes - 1) {
1079            let eq_class1 = &eq_classes[idx_class1];
1080            for eq_class2 in eq_classes.iter().skip(idx_class1 + 1) {
1081                for u1 in eq_class1 {
1082                    for u2 in eq_class2 {
1083                        // check disjoint
1084                        assert!(!url_base_eq(u1, u2));
1085                        assert!(!url_base_eq(u2, u1));
1086                    }
1087                }
1088            }
1089        }
1090    }
1091}