Skip to main content

lexe_node_client/
client.rs

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