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            GetHumanBitcoinAddressResponse, GetNewPayments, GetUpdatedPayments,
31            HumanBitcoinAddressV1, ListChannelsResponse, NodeInfo, NodeInfoV1,
32            OpenChannelRequest, OpenChannelResponse, PayInvoiceRequest,
33            PayInvoiceResponse, PayOfferRequest, PayOfferResponse,
34            PayOnchainRequest, PayOnchainResponse, PaymentCreatedIndexes,
35            PaymentIdStruct, PreflightCloseChannelRequest,
36            PreflightCloseChannelResponse, PreflightOpenChannelRequest,
37            PreflightOpenChannelResponse, PreflightPayInvoiceRequest,
38            PreflightPayInvoiceResponse, PreflightPayOfferRequest,
39            PreflightPayOfferResponse, PreflightPayOnchainRequest,
40            PreflightPayOnchainResponse, SetupGDrive, UpdatePersonalNote,
41            UpsertHumanBitcoinAddressResponse,
42        },
43        nwc::{
44            CreateNwcClientRequest, CreateNwcClientResponse,
45            ListNwcClientResponse, NostrPkStruct, UpdateNwcClientRequest,
46            UpdateNwcClientResponse,
47        },
48    },
49    rest::{POST, RequestBuilderExt, RestClient},
50    types::{
51        Empty,
52        payments::{MaybeBasicPaymentV2, VecBasicPaymentV1, VecBasicPaymentV2},
53        username::UsernameStruct,
54    },
55};
56use lexe_common::{
57    api::{
58        auth::{
59            BearerAuthRequestWire, BearerAuthResponse, BearerAuthToken,
60            LexeScope, TokenWithExpiration, UserSignupRequestWire,
61            UserSignupRequestWireV1,
62        },
63        fiat_rates::FiatRates,
64        models::{
65            SignMsgRequest, SignMsgResponse, VerifyMsgRequest,
66            VerifyMsgResponse,
67        },
68        provision::NodeProvisionRequest,
69        revocable_clients::{
70            CreateRevocableClientRequest, CreateRevocableClientResponse,
71            GetRevocableClients, RevocableClient, RevocableClients,
72            UpdateClientRequest, UpdateClientResponse,
73        },
74        user::UserPk,
75        version::{CurrentEnclaves, EnclavesToProvision, NodeEnclave},
76    },
77    byte_str::ByteStr,
78    constants::{self, node_provision_dns},
79    env::DeployEnv,
80};
81use lexe_crypto::{ed25519, rng::Crng};
82use lexe_enclave::enclave::Measurement;
83use lexe_tls::{attest_client, lexe_ca, rustls};
84use reqwest::Url;
85
86use crate::credentials::{ClientCredentials, CredentialsRef};
87
88/// The client to the gateway itself, i.e. requests terminate at the gateway.
89#[derive(Clone)]
90pub struct GatewayClient {
91    rest: RestClient,
92    gateway_url: Cow<'static, str>,
93}
94
95/// The client to the user node.
96///
97/// Requests are proxied via the gateway CONNECT proxies. These proxies avoid
98/// exposing user nodes to the public internet and enforce user authentication
99/// and other request rate limits.
100///
101/// - Requests made to running nodes use the Run-specific [`RestClient`].
102/// - Requests made to provisioning nodes use a [`RestClient`] which is created
103///   on-the-fly. This is because it is necessary to include a TLS config which
104///   checks the server's remote attestation against a [`Measurement`] which is
105///   only known at provisioning time. This is also desirable because provision
106///   requests generally happen only once, so there is no need to maintain a
107///   connection pool after provisioning has complete.
108#[derive(Clone)]
109pub struct NodeClient {
110    inner: Arc<NodeClientInner>,
111}
112
113struct NodeClientInner {
114    /// The user's public key, if available from credentials.
115    user_pk: Option<UserPk>,
116    gateway_client: GatewayClient,
117    /// The [`RestClient`] used to communicate with a Run node.
118    ///
119    /// This is an [`ArcSwapOption`] so that we can atomically swap in a new
120    /// client with a new proxy config when the auth token expires.
121    ///
122    /// Previously, we used this patch to dynamically set the proxy auth header
123    /// with the latest auth token:
124    /// [proxy: allow setting proxy-auth at intercept time](https://github.com/lexe-app/reqwest/commit/dea2dd7a1d3c52e50d1c47803fdc57d73e35c769)
125    /// This approach has the best connection reuse, since the connection pool
126    /// is shared across all tokens; we should only need to reconnect if the
127    /// underlying connection times out.
128    ///
129    /// This approach removes the need for a patch. One downside: it replaces
130    /// the connection pool whenever we need to re-auth. Until we get
131    /// per-request proxy configs in `reqwest`, this is likely the best we can
132    /// do. Though one reconnection per 10 min. is probably ok.
133    run_rest: ArcSwapOption<RunRestClient>,
134    run_url: &'static str,
135    use_sgx: bool,
136    deploy_env: DeployEnv,
137    authenticator: Arc<BearerAuthenticator>,
138    tls_config: rustls::ClientConfig,
139}
140
141/// A [`RestClient`] with required proxy configuration needed to communicate
142/// with a user node.
143struct RunRestClient {
144    client: RestClient,
145    /// When the auth token used in the proxy config expires, or `None` if it
146    /// never expires.
147    token_expiration: Option<SystemTime>,
148}
149
150// --- impl GatewayClient --- //
151
152impl GatewayClient {
153    pub fn new(
154        deploy_env: DeployEnv,
155        gateway_url: impl Into<Cow<'static, str>>,
156        user_agent: impl Into<Cow<'static, str>>,
157    ) -> anyhow::Result<Self> {
158        fn inner(
159            deploy_env: DeployEnv,
160            gateway_url: Cow<'static, str>,
161            user_agent: Cow<'static, str>,
162        ) -> anyhow::Result<GatewayClient> {
163            let tls_config = lexe_ca::app_gateway_client_config(deploy_env);
164            let rest = RestClient::new(user_agent, "gateway", tls_config);
165            Ok(GatewayClient { rest, gateway_url })
166        }
167        inner(deploy_env, gateway_url.into(), user_agent.into())
168    }
169}
170
171impl AppBackendApi for GatewayClient {
172    async fn signup_v2(
173        &self,
174        signed_req: &ed25519::Signed<&UserSignupRequestWire>,
175    ) -> Result<Empty, BackendApiError> {
176        let gateway_url = &self.gateway_url;
177        let req = self
178            .rest
179            .builder(POST, format!("{gateway_url}/app/v2/signup"))
180            .signed_bcs(signed_req)
181            .map_err(BackendApiError::bcs_serialize)?;
182        self.rest.send(req).await
183    }
184
185    async fn signup_v1(
186        &self,
187        _signed_req: &ed25519::Signed<&UserSignupRequestWireV1>,
188    ) -> Result<Empty, BackendApiError> {
189        debug_assert!(false, "Use `signup_v2`");
190        Err(BackendApiError::not_found("Use `/app/v2/signup`"))
191    }
192}
193
194#[async_trait]
195impl BearerAuthBackendApi for GatewayClient {
196    async fn bearer_auth(
197        &self,
198        signed_req: &ed25519::Signed<&BearerAuthRequestWire>,
199    ) -> Result<BearerAuthResponse, BackendApiError> {
200        let gateway_url = &self.gateway_url;
201        let req = self
202            .rest
203            .builder(POST, format!("{gateway_url}/app/bearer_auth"))
204            .signed_bcs(signed_req)
205            .map_err(BackendApiError::bcs_serialize)?;
206        self.rest.send(req).await
207    }
208}
209
210impl AppGatewayApi for GatewayClient {
211    async fn get_fiat_rates(&self) -> Result<FiatRates, GatewayApiError> {
212        let gateway_url = &self.gateway_url;
213        let req = self
214            .rest
215            .get(format!("{gateway_url}/app/v1/fiat_rates"), &Empty {});
216        self.rest.send(req).await
217    }
218
219    async fn enclaves_to_provision(
220        &self,
221        req: &EnclavesToProvisionRequest,
222        auth: BearerAuthToken,
223    ) -> Result<EnclavesToProvision, GatewayApiError> {
224        let gateway_url = &self.gateway_url;
225        let url = format!("{gateway_url}/app/v1/enclaves_to_provision");
226        let req = self.rest.post(url, req).bearer_auth(&auth);
227        self.rest.send(req).await
228    }
229
230    async fn latest_release(&self) -> Result<NodeEnclave, GatewayApiError> {
231        let gateway_url = &self.gateway_url;
232        let req = self
233            .rest
234            .get(format!("{gateway_url}/app/v1/latest_release"), &Empty {});
235        self.rest.send(req).await
236    }
237
238    async fn current_releases(
239        &self,
240    ) -> Result<CurrentEnclaves, GatewayApiError> {
241        let gateway_url = &self.gateway_url;
242        let req = self
243            .rest
244            .get(format!("{gateway_url}/app/v1/current_releases"), &Empty {});
245        self.rest.send(req).await
246    }
247
248    async fn current_enclaves(
249        &self,
250    ) -> Result<CurrentEnclaves, GatewayApiError> {
251        let gateway_url = &self.gateway_url;
252        let req = self
253            .rest
254            .get(format!("{gateway_url}/app/v1/current_enclaves"), &Empty {});
255        self.rest.send(req).await
256    }
257}
258
259// --- impl NodeClient --- //
260
261impl NodeClient {
262    pub fn new(
263        rng: &mut impl Crng,
264        use_sgx: bool,
265        deploy_env: DeployEnv,
266        gateway_client: GatewayClient,
267        credentials: CredentialsRef<'_>,
268    ) -> anyhow::Result<Self> {
269        let run_url = constants::NODE_RUN_URL;
270
271        let gateway_url = &gateway_client.gateway_url;
272        ensure!(
273            gateway_url.starts_with("https://"),
274            "proxy connection must be https: gateway url: {gateway_url}",
275        );
276
277        let user_pk = credentials.user_pk();
278        let authenticator = credentials.bearer_authenticator()?;
279        let tls_config = credentials.tls_config(rng, deploy_env)?;
280        let run_rest = ArcSwapOption::from(None);
281
282        Ok(Self {
283            inner: Arc::new(NodeClientInner {
284                user_pk,
285                gateway_client,
286                run_rest,
287                run_url,
288                use_sgx,
289                deploy_env,
290                authenticator,
291                tls_config,
292            }),
293        })
294    }
295
296    /// Returns the user's public key, if available from the credentials.
297    ///
298    /// Returns `None` if credentials were created before node v0.8.11,
299    /// which didn't include user_pk.
300    pub fn user_pk(&self) -> Option<UserPk> {
301        self.inner.user_pk
302    }
303
304    /// Get an authenticated [`RunRestClient`] for making requests to the user
305    /// node's run endpoint via the gateway CONNECT proxy.
306    ///
307    /// The returned client always has a fresh auth token for the gateway proxy.
308    ///
309    /// In the common case where our token is still fresh, this is a fast atomic
310    /// load of the cached client. If the token is expired, we will request a
311    /// new token, build a new client, and swap it in atomically.
312    async fn authed_run_rest(
313        &self,
314    ) -> Result<Arc<RunRestClient>, NodeApiError> {
315        let now = SystemTime::now();
316
317        // Fast path: we already have a fresh token and client
318        if let Some(run_rest) = self.maybe_authed_run_rest(now) {
319            return Ok(run_rest);
320        }
321
322        // TODO(phlip9): `std::hint::cold_path()` here when that stabilizes
323
324        // Get an unexpired auth token. This is probably a new token, but we may
325        // race with other tasks here, so we could also get a cached token.
326        // Connecting to the user node only requires `GatewayProxy`.
327        let auth_token =
328            self.get_auth_token(now, LexeScope::GatewayProxy).await?;
329
330        // Check again if another task concurrently swapped in a fresh client.
331        // A little hacky, but significantly reduces the chance that we create
332        // multiple clients.
333        if let Some(run_rest) = self.maybe_authed_run_rest(now) {
334            // TODO(phlip9): `std::hint::cold_path()` here when that stabilizes
335            return Ok(run_rest);
336        }
337
338        // Build a new client with the new token
339        let run_rest = RunRestClient::new(
340            &self.inner.gateway_client,
341            self.inner.run_url,
342            auth_token,
343            self.inner.tls_config.clone(),
344        )
345        .map_err(NodeApiError::bad_auth)?;
346        let run_rest = Arc::new(run_rest);
347
348        // Swap it in
349        self.inner.run_rest.swap(Some(run_rest.clone()));
350
351        Ok(run_rest)
352    }
353
354    /// Returns `Some(_)` if we already have an authenticated run rest client
355    /// whose token is unexpired.
356    fn maybe_authed_run_rest(
357        &self,
358        now: SystemTime,
359    ) -> Option<Arc<RunRestClient>> {
360        let maybe_run_rest = self.inner.run_rest.load_full();
361        if let Some(run_rest) = maybe_run_rest
362            && !run_rest.token_needs_refresh(now)
363        {
364            Some(run_rest)
365        } else {
366            None
367        }
368    }
369
370    /// Get an unexpired auth token (maybe cached, maybe new) for the given
371    /// `scope`.
372    async fn get_auth_token(
373        &self,
374        now: SystemTime,
375        scope: LexeScope,
376    ) -> Result<TokenWithExpiration, NodeApiError> {
377        self.inner
378            .authenticator
379            .get_token_with_exp(&self.inner.gateway_client, now, scope)
380            .await
381            // TODO(phlip9): how to best convert `BackendApiError` to
382            //               `NodeApiError`?
383            .map_err(|backend_error| {
384                // Contains backend kind msg and regular msg
385                let msg = format!("{backend_error:#}");
386
387                let BackendApiError {
388                    data, sensitive, ..
389                } = backend_error;
390
391                NodeApiError {
392                    kind: NodeErrorKind::BadAuth,
393                    msg,
394                    data,
395                    sensitive,
396                }
397            })
398    }
399
400    /// Builds a Provision-specific [`RestClient`] which can be used to make a
401    /// provision request to a provisioning node.
402    ///
403    /// This client doesn't automatically refresh its auth token, so avoid
404    /// holding onto this client for too long.
405    fn provision_rest_client(
406        &self,
407        provision_url: &str,
408        auth_token: BearerAuthToken,
409        measurement: Measurement,
410    ) -> anyhow::Result<RestClient> {
411        let proxy = static_proxy_config(
412            &self.inner.gateway_client.gateway_url,
413            provision_url,
414            auth_token,
415        )
416        .context("Invalid proxy config")?;
417
418        let tls_config = attest_client::app_node_provision_client_config(
419            self.inner.use_sgx,
420            self.inner.deploy_env,
421            measurement,
422        );
423
424        let user_agent = self.inner.gateway_client.rest.user_agent().clone();
425        let (from, to) = (user_agent, "node-provision");
426        let reqwest_client = RestClient::client_builder(&from)
427            .proxy(proxy)
428            .use_preconfigured_tls(tls_config)
429            // Provision can take longer than 5 sec. <3 gdrive : )
430            .timeout(Duration::from_secs(30))
431            .build()
432            .context("Failed to build client")?;
433
434        let provision_rest = RestClient::from_inner(reqwest_client, from, to);
435
436        Ok(provision_rest)
437    }
438
439    /// Ask the user node to create a new [`RevocableClient`] and return it
440    /// along with its [`ClientCredentials`].
441    pub async fn create_client_credentials(
442        &self,
443        req: CreateRevocableClientRequest,
444    ) -> anyhow::Result<(RevocableClient, ClientCredentials)> {
445        // Register a new revocable client.
446        let resp = self.create_revocable_client(req.clone()).await?;
447
448        let client = RevocableClient {
449            pubkey: resp.pubkey,
450            created_at: resp.created_at,
451            label: req.label,
452            scope: req.scope,
453            expires_at: req.expires_at,
454            is_revoked: false,
455        };
456
457        let client_credentials = ClientCredentials::from(resp);
458
459        Ok((client, client_credentials))
460    }
461
462    /// Get a [`LexeScope::GatewayProxy`] token for requests to the gateway.
463    //
464    // This helper exists because `GatewayClient::enclaves_to_provision` needs a
465    // token, but `GatewayClient` doesn't hold a `BearerAuthenticator`.
466    pub async fn get_gateway_token(&self) -> anyhow::Result<BearerAuthToken> {
467        let now = SystemTime::now();
468        self.inner
469            .authenticator
470            .get_token(&self.inner.gateway_client, now, LexeScope::GatewayProxy)
471            .await
472            .context("Failed to get gateway token")
473    }
474}
475
476impl AppNodeProvisionApi for NodeClient {
477    async fn provision(
478        &self,
479        measurement: Measurement,
480        data: NodeProvisionRequest,
481    ) -> Result<Empty, NodeApiError> {
482        let now = SystemTime::now();
483        let mr_short = measurement.short();
484        let provision_dns = node_provision_dns(&mr_short);
485        let provision_url = format!("https://{provision_dns}");
486
487        // Create rest client on the fly.
488        let auth_token = self
489            .get_auth_token(now, LexeScope::GatewayProxy)
490            .await?
491            .token;
492        let provision_rest = self
493            .provision_rest_client(&provision_url, auth_token, measurement)
494            .context("Failed to build provision rest client")
495            .map_err(NodeApiError::provision)?;
496
497        let req = provision_rest
498            .post(format!("{provision_url}/app/provision"), &data);
499        provision_rest.send(req).await
500    }
501}
502
503impl AppNodeRunApi for NodeClient {
504    async fn node_info(&self) -> Result<NodeInfo, NodeApiError> {
505        let run_rest = &self.authed_run_rest().await?.client;
506        let run_url = &self.inner.run_url;
507        let url = format!("{run_url}/app/v2/node_info");
508        let req = run_rest.get(url, &Empty {});
509        run_rest.send(req).await
510    }
511
512    async fn node_info_v1(&self) -> Result<NodeInfoV1, NodeApiError> {
513        let run_rest = &self.authed_run_rest().await?.client;
514        let run_url = &self.inner.run_url;
515        let url = format!("{run_url}/app/node_info");
516        let req = run_rest.get(url, &Empty {});
517        run_rest.send(req).await
518    }
519
520    async fn debug_info(&self) -> Result<DebugInfo, NodeApiError> {
521        let run_rest = &self.authed_run_rest().await?.client;
522        let run_url = &self.inner.run_url;
523        let url = format!("{run_url}/app/debug_info");
524        let req = run_rest.get(url, &Empty {});
525        run_rest.send(req).await
526    }
527
528    async fn list_channels(
529        &self,
530    ) -> Result<ListChannelsResponse, NodeApiError> {
531        let run_rest = &self.authed_run_rest().await?.client;
532        let run_url = &self.inner.run_url;
533        let url = format!("{run_url}/app/list_channels");
534        let req = run_rest.get(url, &Empty {});
535        run_rest.send(req).await
536    }
537
538    async fn sign_message(
539        &self,
540        data: SignMsgRequest,
541    ) -> Result<SignMsgResponse, 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/sign_message");
545        let req = run_rest.post(url, &data);
546        run_rest.send(req).await
547    }
548
549    async fn verify_message(
550        &self,
551        data: VerifyMsgRequest,
552    ) -> Result<VerifyMsgResponse, NodeApiError> {
553        let run_rest = &self.authed_run_rest().await?.client;
554        let run_url = &self.inner.run_url;
555        let url = format!("{run_url}/app/verify_message");
556        let req = run_rest.post(url, &data);
557        run_rest.send(req).await
558    }
559
560    async fn open_channel(
561        &self,
562        data: OpenChannelRequest,
563    ) -> Result<OpenChannelResponse, NodeApiError> {
564        let run_rest = &self.authed_run_rest().await?.client;
565        let run_url = &self.inner.run_url;
566        let url = format!("{run_url}/app/open_channel");
567        let req = run_rest.post(url, &data);
568        run_rest.send(req).await
569    }
570
571    async fn preflight_open_channel(
572        &self,
573        data: PreflightOpenChannelRequest,
574    ) -> Result<PreflightOpenChannelResponse, NodeApiError> {
575        let run_rest = &self.authed_run_rest().await?.client;
576        let run_url = &self.inner.run_url;
577        let url = format!("{run_url}/app/preflight_open_channel");
578        let req = run_rest.post(url, &data);
579        run_rest.send(req).await
580    }
581
582    async fn close_channel(
583        &self,
584        data: CloseChannelRequest,
585    ) -> Result<Empty, NodeApiError> {
586        let run_rest = &self.authed_run_rest().await?.client;
587        let run_url = &self.inner.run_url;
588        let url = format!("{run_url}/app/close_channel");
589        let req = run_rest.post(url, &data);
590        run_rest.send(req).await
591    }
592
593    async fn preflight_close_channel(
594        &self,
595        data: PreflightCloseChannelRequest,
596    ) -> Result<PreflightCloseChannelResponse, NodeApiError> {
597        let run_rest = &self.authed_run_rest().await?.client;
598        let run_url = &self.inner.run_url;
599        let url = format!("{run_url}/app/preflight_close_channel");
600        let req = run_rest.post(url, &data);
601        run_rest.send(req).await
602    }
603
604    async fn create_invoice(
605        &self,
606        data: CreateInvoiceRequest,
607    ) -> Result<CreateInvoiceResponse, NodeApiError> {
608        let run_rest = &self.authed_run_rest().await?.client;
609        let run_url = &self.inner.run_url;
610        let url = format!("{run_url}/app/create_invoice");
611        let req = run_rest.post(url, &data);
612        run_rest.send(req).await
613    }
614
615    async fn pay_invoice(
616        &self,
617        req: PayInvoiceRequest,
618    ) -> Result<PayInvoiceResponse, NodeApiError> {
619        let run_rest = &self.authed_run_rest().await?.client;
620        let run_url = &self.inner.run_url;
621        let url = format!("{run_url}/app/pay_invoice");
622        // `pay_invoice` may call `max_flow` which takes a long time.
623        let req = run_rest
624            .post(url, &req)
625            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
626        run_rest.send(req).await
627    }
628
629    async fn preflight_pay_invoice(
630        &self,
631        req: PreflightPayInvoiceRequest,
632    ) -> Result<PreflightPayInvoiceResponse, NodeApiError> {
633        let run_rest = &self.authed_run_rest().await?.client;
634        let run_url = &self.inner.run_url;
635        let url = format!("{run_url}/app/preflight_pay_invoice");
636        // `preflight_pay_invoice` may call `max_flow` which takes a long time.
637        let req = run_rest
638            .post(url, &req)
639            .timeout(constants::MAX_FLOW_TIMEOUT + Duration::from_secs(2));
640        run_rest.send(req).await
641    }
642
643    async fn pay_onchain(
644        &self,
645        req: PayOnchainRequest,
646    ) -> Result<PayOnchainResponse, NodeApiError> {
647        let run_rest = &self.authed_run_rest().await?.client;
648        let run_url = &self.inner.run_url;
649        let url = format!("{run_url}/app/pay_onchain");
650        let req = run_rest.post(url, &req);
651        run_rest.send(req).await
652    }
653
654    async fn preflight_pay_onchain(
655        &self,
656        req: PreflightPayOnchainRequest,
657    ) -> Result<PreflightPayOnchainResponse, NodeApiError> {
658        let run_rest = &self.authed_run_rest().await?.client;
659        let run_url = &self.inner.run_url;
660        let url = format!("{run_url}/app/preflight_pay_onchain");
661        let req = run_rest.post(url, &req);
662        run_rest.send(req).await
663    }
664
665    async fn create_offer(
666        &self,
667        req: CreateOfferRequest,
668    ) -> Result<CreateOfferResponse, NodeApiError> {
669        let run_rest = &self.authed_run_rest().await?.client;
670        let run_url = &self.inner.run_url;
671        let url = format!("{run_url}/app/create_offer");
672        let req = run_rest.post(url, &req);
673        run_rest.send(req).await
674    }
675
676    async fn pay_offer(
677        &self,
678        req: PayOfferRequest,
679    ) -> Result<PayOfferResponse, NodeApiError> {
680        let run_rest = &self.authed_run_rest().await?.client;
681        let run_url = &self.inner.run_url;
682        let url = format!("{run_url}/app/pay_offer");
683        let req = run_rest.post(url, &req);
684        run_rest.send(req).await
685    }
686
687    async fn preflight_pay_offer(
688        &self,
689        req: PreflightPayOfferRequest,
690    ) -> Result<PreflightPayOfferResponse, NodeApiError> {
691        let run_rest = &self.authed_run_rest().await?.client;
692        let run_url = &self.inner.run_url;
693        let url = format!("{run_url}/app/preflight_pay_offer");
694        let req = run_rest.post(url, &req);
695        run_rest.send(req).await
696    }
697
698    async fn get_address(&self) -> Result<GetAddressResponse, NodeApiError> {
699        let run_rest = &self.authed_run_rest().await?.client;
700        let run_url = &self.inner.run_url;
701        let url = format!("{run_url}/app/get_address");
702        let req = run_rest.post(url, &Empty {});
703        run_rest.send(req).await
704    }
705
706    async fn get_payments_by_indexes(
707        &self,
708        _: PaymentCreatedIndexes,
709    ) -> Result<VecBasicPaymentV1, NodeApiError> {
710        unimplemented!("Deprecated")
711    }
712
713    async fn get_new_payments(
714        &self,
715        _: GetNewPayments,
716    ) -> Result<VecBasicPaymentV1, NodeApiError> {
717        unimplemented!("Deprecated")
718    }
719
720    async fn get_updated_payments(
721        &self,
722        req: GetUpdatedPayments,
723    ) -> Result<VecBasicPaymentV2, NodeApiError> {
724        let run_rest = &self.authed_run_rest().await?.client;
725        let run_url = &self.inner.run_url;
726        let url = format!("{run_url}/app/payments/updated");
727        let req = run_rest.get(url, &req);
728        run_rest.send(req).await
729    }
730
731    async fn get_payment_by_id(
732        &self,
733        req: PaymentIdStruct,
734    ) -> Result<MaybeBasicPaymentV2, NodeApiError> {
735        let run_rest = &self.authed_run_rest().await?.client;
736        let run_url = &self.inner.run_url;
737        let url = format!("{run_url}/app/v1/payments/id");
738        let req = run_rest.get(url, &req);
739        run_rest.send(req).await
740    }
741
742    async fn update_personal_note(
743        &self,
744        req: UpdatePersonalNote,
745    ) -> Result<Empty, NodeApiError> {
746        let run_rest = &self.authed_run_rest().await?.client;
747        let run_url = &self.inner.run_url;
748        let url = format!("{run_url}/app/payments/note");
749        let req = run_rest.put(url, &req);
750        run_rest.send(req).await
751    }
752
753    async fn get_revocable_clients(
754        &self,
755        req: GetRevocableClients,
756    ) -> Result<RevocableClients, NodeApiError> {
757        let run_rest = &self.authed_run_rest().await?.client;
758        let run_url = &self.inner.run_url;
759        let url = format!("{run_url}/app/clients");
760        let req = run_rest.get(url, &req);
761        run_rest.send(req).await
762    }
763
764    async fn create_revocable_client(
765        &self,
766        req: CreateRevocableClientRequest,
767    ) -> Result<CreateRevocableClientResponse, NodeApiError> {
768        let run_rest = &self.authed_run_rest().await?.client;
769        let run_url = &self.inner.run_url;
770        let url = format!("{run_url}/app/clients");
771        let req = run_rest.post(url, &req);
772        run_rest.send(req).await
773    }
774
775    async fn update_revocable_client(
776        &self,
777        req: UpdateClientRequest,
778    ) -> Result<UpdateClientResponse, NodeApiError> {
779        let run_rest = &self.authed_run_rest().await?.client;
780        let run_url = &self.inner.run_url;
781        let url = format!("{run_url}/app/clients");
782        let req = run_rest.put(url, &req);
783        run_rest.send(req).await
784    }
785
786    async fn list_broadcasted_txs(
787        &self,
788    ) -> Result<serde_json::Value, 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/list_broadcasted_txs");
792        let req = run_rest.get(url, &Empty {});
793        run_rest.send(req).await
794    }
795
796    async fn backup_info(&self) -> Result<BackupInfo, NodeApiError> {
797        let run_rest = &self.authed_run_rest().await?.client;
798        let run_url = &self.inner.run_url;
799        let url = format!("{run_url}/app/backup");
800        let req = run_rest.get(url, &Empty {});
801        run_rest.send(req).await
802    }
803
804    async fn setup_gdrive(
805        &self,
806        req: SetupGDrive,
807    ) -> Result<Empty, NodeApiError> {
808        let run_rest = &self.authed_run_rest().await?.client;
809        let run_url = &self.inner.run_url;
810        let url = format!("{run_url}/app/backup/gdrive");
811        let req = run_rest.post(url, &req);
812        run_rest.send(req).await
813    }
814
815    async fn get_human_bitcoin_address(
816        &self,
817    ) -> Result<GetHumanBitcoinAddressResponse, 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/v2/human_bitcoin_address");
821        let req = run_rest.get(url, &Empty {});
822        run_rest.send(req).await
823    }
824
825    async fn get_human_bitcoin_address_v1(
826        &self,
827    ) -> Result<HumanBitcoinAddressV1, NodeApiError> {
828        let run_rest = &self.authed_run_rest().await?.client;
829        let run_url = &self.inner.run_url;
830        let url = format!("{run_url}/app/human_bitcoin_address");
831        let req = run_rest.get(url, &Empty {});
832        run_rest.send(req).await
833    }
834
835    async fn upsert_custom_human_bitcoin_address(
836        &self,
837        req: UsernameStruct,
838    ) -> Result<UpsertHumanBitcoinAddressResponse, 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/v2/human_bitcoin_address");
842        let req = run_rest.put(url, &req);
843        run_rest.send(req).await
844    }
845
846    async fn update_human_bitcoin_address_v1(
847        &self,
848        req: UsernameStruct,
849    ) -> Result<HumanBitcoinAddressV1, 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_v1(
859        &self,
860    ) -> Result<HumanBitcoinAddressV1, NodeApiError> {
861        self.get_human_bitcoin_address_v1().await
862    }
863
864    #[allow(deprecated)]
865    async fn update_payment_address_v1(
866        &self,
867        req: UsernameStruct,
868    ) -> Result<HumanBitcoinAddressV1, NodeApiError> {
869        self.update_human_bitcoin_address_v1(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::helpers::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}