Skip to main content

reth_rpc_builder/
auth.rs

1use crate::{
2    error::{RpcError, ServerKind},
3    middleware::{RethAuthHttpMiddleware, RethRpcMiddleware},
4};
5use http::header::AUTHORIZATION;
6use jsonrpsee::{
7    core::{client::SubscriptionClientT, RegisterMethodError},
8    http_client::HeaderMap,
9    server::{AlreadyStoppedError, RpcModule, ServerConfig, ServerConfigBuilder},
10    ws_client::RpcServiceBuilder,
11    Methods,
12};
13use reth_rpc_api::servers::*;
14use reth_rpc_eth_types::EthSubscriptionIdProvider;
15use reth_rpc_layer::{
16    secret_to_bearer_header, AuthClientLayer, AuthLayer, JwtAuthValidator, JwtSecret,
17};
18use reth_rpc_server_types::constants;
19use std::net::{IpAddr, Ipv4Addr, SocketAddr};
20use tower::layer::util::Identity;
21
22pub use jsonrpsee::server::ServerBuilder;
23pub use reth_ipc::server::Builder as IpcServerBuilder;
24
25/// Server configuration for the auth server.
26#[derive(Debug)]
27pub struct AuthServerConfig<RpcMiddleware = Identity, HttpMiddleware = Identity> {
28    /// Where the server should listen.
29    pub(crate) socket_addr: SocketAddr,
30    /// The secret for the auth layer of the server.
31    pub(crate) secret: JwtSecret,
32    /// Configs for JSON-RPC Http.
33    pub(crate) server_config: ServerConfigBuilder,
34    /// Configs for IPC server
35    pub(crate) ipc_server_config: Option<IpcServerBuilder<Identity, Identity>>,
36    /// IPC endpoint
37    pub(crate) ipc_endpoint: Option<String>,
38    /// Configurable RPC middleware
39    pub(crate) rpc_middleware: RpcMiddleware,
40    /// Configurable HTTP transport middleware, applied after JWT authentication.
41    pub(crate) http_middleware: HttpMiddleware,
42}
43
44// === impl AuthServerConfig ===
45
46impl AuthServerConfig {
47    /// Convenience function to create a new `AuthServerConfig`.
48    pub const fn builder(secret: JwtSecret) -> AuthServerConfigBuilder {
49        AuthServerConfigBuilder::new(secret)
50    }
51}
52impl<RpcMiddleware, HttpMiddleware> AuthServerConfig<RpcMiddleware, HttpMiddleware> {
53    /// Returns the address the server will listen on.
54    pub const fn address(&self) -> SocketAddr {
55        self.socket_addr
56    }
57
58    /// Configures the rpc middleware.
59    pub fn with_rpc_middleware<T>(self, rpc_middleware: T) -> AuthServerConfig<T, HttpMiddleware> {
60        let Self {
61            socket_addr,
62            secret,
63            server_config,
64            ipc_server_config,
65            ipc_endpoint,
66            http_middleware,
67            ..
68        } = self;
69        AuthServerConfig {
70            socket_addr,
71            secret,
72            server_config,
73            ipc_server_config,
74            ipc_endpoint,
75            rpc_middleware,
76            http_middleware,
77        }
78    }
79
80    /// Configures the HTTP transport middleware.
81    ///
82    /// This middleware is applied after JWT authentication and before JSON-RPC parsing,
83    /// giving access to the raw HTTP request (headers, body, etc.).
84    pub fn with_http_middleware<T>(self, http_middleware: T) -> AuthServerConfig<RpcMiddleware, T> {
85        let Self {
86            socket_addr,
87            secret,
88            server_config,
89            ipc_server_config,
90            ipc_endpoint,
91            rpc_middleware,
92            ..
93        } = self;
94        AuthServerConfig {
95            socket_addr,
96            secret,
97            server_config,
98            ipc_server_config,
99            ipc_endpoint,
100            rpc_middleware,
101            http_middleware,
102        }
103    }
104
105    /// Convenience function to start a server in one step.
106    ///
107    /// The `HttpMiddleware` type parameter configures additional HTTP transport middleware
108    /// that runs after JWT authentication. When set to `Identity` (the default), only JWT
109    /// authentication is applied.
110    pub async fn start(self, module: AuthRpcModule) -> Result<AuthServerHandle, RpcError>
111    where
112        RpcMiddleware: RethRpcMiddleware,
113        HttpMiddleware: RethAuthHttpMiddleware<RpcMiddleware>,
114    {
115        let Self {
116            socket_addr,
117            secret,
118            server_config,
119            ipc_server_config,
120            ipc_endpoint,
121            rpc_middleware,
122            http_middleware,
123        } = self;
124
125        // Create auth middleware with JWT authentication in front of the user-provided
126        // transport middleware.
127        let middleware =
128            tower::ServiceBuilder::new().layer(AuthHttpLayer::new(secret, http_middleware));
129
130        let rpc_middleware = RpcServiceBuilder::default().layer(rpc_middleware);
131
132        // By default, both http and ws are enabled.
133        let server = ServerBuilder::new()
134            .set_config(server_config.build())
135            .set_http_middleware(middleware)
136            .set_rpc_middleware(rpc_middleware)
137            .build(socket_addr)
138            .await
139            .map_err(|err| RpcError::server_error(err, ServerKind::Auth(socket_addr)))?;
140
141        let local_addr = server
142            .local_addr()
143            .map_err(|err| RpcError::server_error(err, ServerKind::Auth(socket_addr)))?;
144
145        let handle = server.start(module.inner.clone());
146
147        let ipc_handle = if let Some(ipc_server_config) = ipc_server_config {
148            let ipc_endpoint_str = ipc_endpoint
149                .clone()
150                .unwrap_or_else(|| constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string());
151            let ipc_server = ipc_server_config.build(ipc_endpoint_str);
152            let res = ipc_server.start(module.inner).await?;
153            Some(res)
154        } else {
155            None
156        };
157
158        Ok(AuthServerHandle { handle: Some(handle), local_addr, secret, ipc_endpoint, ipc_handle })
159    }
160}
161
162/// A combined tower layer that applies JWT authentication before custom HTTP middleware.
163///
164/// This composes `AuthLayer<JwtAuthValidator>` around a user-provided `HttpMiddleware` into a
165/// single `tower::Layer`. Requests first pass through JWT validation and only authenticated
166/// requests are forwarded into the custom middleware.
167struct AuthHttpLayer<HttpMiddleware> {
168    auth_layer: AuthLayer<JwtAuthValidator>,
169    http_middleware: HttpMiddleware,
170}
171
172impl<HttpMiddleware> AuthHttpLayer<HttpMiddleware> {
173    const fn new(secret: JwtSecret, http_middleware: HttpMiddleware) -> Self {
174        Self { auth_layer: AuthLayer::new(JwtAuthValidator::new(secret)), http_middleware }
175    }
176}
177
178impl<S, HttpMiddleware> tower::Layer<S> for AuthHttpLayer<HttpMiddleware>
179where
180    HttpMiddleware: tower::Layer<S> + Clone,
181    AuthLayer<JwtAuthValidator>: tower::Layer<<HttpMiddleware as tower::Layer<S>>::Service>,
182{
183    type Service = <AuthLayer<JwtAuthValidator> as tower::Layer<
184        <HttpMiddleware as tower::Layer<S>>::Service,
185    >>::Service;
186
187    fn layer(&self, inner: S) -> Self::Service {
188        let http_service = self.http_middleware.layer(inner);
189        self.auth_layer.layer(http_service)
190    }
191}
192
193/// Builder type for configuring an `AuthServerConfig`.
194#[derive(Debug)]
195pub struct AuthServerConfigBuilder<RpcMiddleware = Identity, HttpMiddleware = Identity> {
196    socket_addr: Option<SocketAddr>,
197    secret: JwtSecret,
198    server_config: Option<ServerConfigBuilder>,
199    ipc_server_config: Option<IpcServerBuilder<Identity, Identity>>,
200    ipc_endpoint: Option<String>,
201    rpc_middleware: RpcMiddleware,
202    http_middleware: HttpMiddleware,
203}
204
205// === impl AuthServerConfigBuilder ===
206
207impl AuthServerConfigBuilder {
208    /// Create a new `AuthServerConfigBuilder` with the given `secret`.
209    pub const fn new(secret: JwtSecret) -> Self {
210        Self {
211            socket_addr: None,
212            secret,
213            server_config: None,
214            ipc_server_config: None,
215            ipc_endpoint: None,
216            rpc_middleware: Identity::new(),
217            http_middleware: Identity::new(),
218        }
219    }
220}
221
222impl<RpcMiddleware, HttpMiddleware> AuthServerConfigBuilder<RpcMiddleware, HttpMiddleware> {
223    /// Configures the rpc middleware.
224    pub fn with_rpc_middleware<T>(
225        self,
226        rpc_middleware: T,
227    ) -> AuthServerConfigBuilder<T, HttpMiddleware> {
228        let Self {
229            socket_addr,
230            secret,
231            server_config,
232            ipc_server_config,
233            ipc_endpoint,
234            http_middleware,
235            ..
236        } = self;
237        AuthServerConfigBuilder {
238            socket_addr,
239            secret,
240            server_config,
241            ipc_server_config,
242            ipc_endpoint,
243            rpc_middleware,
244            http_middleware,
245        }
246    }
247
248    /// Configures the HTTP transport middleware.
249    ///
250    /// This middleware is applied after JWT authentication and before JSON-RPC parsing,
251    /// giving access to the raw HTTP request (headers, body, etc.).
252    pub fn with_http_middleware<T>(
253        self,
254        http_middleware: T,
255    ) -> AuthServerConfigBuilder<RpcMiddleware, T> {
256        let Self {
257            socket_addr,
258            secret,
259            server_config,
260            ipc_server_config,
261            ipc_endpoint,
262            rpc_middleware,
263            ..
264        } = self;
265        AuthServerConfigBuilder {
266            socket_addr,
267            secret,
268            server_config,
269            ipc_server_config,
270            ipc_endpoint,
271            rpc_middleware,
272            http_middleware,
273        }
274    }
275
276    /// Set the socket address for the server.
277    pub const fn socket_addr(mut self, socket_addr: SocketAddr) -> Self {
278        self.socket_addr = Some(socket_addr);
279        self
280    }
281
282    /// Set the socket address for the server.
283    pub const fn maybe_socket_addr(mut self, socket_addr: Option<SocketAddr>) -> Self {
284        self.socket_addr = socket_addr;
285        self
286    }
287
288    /// Set the secret for the server.
289    pub const fn secret(mut self, secret: JwtSecret) -> Self {
290        self.secret = secret;
291        self
292    }
293
294    /// Configures the JSON-RPC server
295    ///
296    /// Note: this always configures an [`EthSubscriptionIdProvider`]
297    /// [`IdProvider`](jsonrpsee::server::IdProvider) for convenience.
298    pub fn with_server_config(mut self, config: ServerConfigBuilder) -> Self {
299        self.server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default()));
300        self
301    }
302
303    /// Set the ipc endpoint for the server.
304    pub fn ipc_endpoint(mut self, ipc_endpoint: String) -> Self {
305        self.ipc_endpoint = Some(ipc_endpoint);
306        self
307    }
308
309    /// Configures the IPC server
310    ///
311    /// Note: this always configures an [`EthSubscriptionIdProvider`]
312    pub fn with_ipc_config(mut self, config: IpcServerBuilder<Identity, Identity>) -> Self {
313        self.ipc_server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default()));
314        self
315    }
316
317    /// Build the `AuthServerConfig`.
318    pub fn build(self) -> AuthServerConfig<RpcMiddleware, HttpMiddleware> {
319        AuthServerConfig {
320            socket_addr: self.socket_addr.unwrap_or_else(|| {
321                SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), constants::DEFAULT_AUTH_PORT)
322            }),
323            secret: self.secret,
324            server_config: self.server_config.unwrap_or_else(|| {
325                ServerConfig::builder()
326                    // This needs to large enough to handle large eth_getLogs responses and
327                    // maximum payload bodies limit for
328                    // `engine_getPayloadBodiesByRangeV` ~750MB per
329                    // response should be enough
330                    .max_response_body_size(750 * 1024 * 1024)
331                    // Connections to this server are always authenticated, hence this only
332                    // affects connections from the CL or any other
333                    // client that uses JWT, this should be
334                    // more than enough so that the CL (or multiple CL nodes) will never get
335                    // rate limited
336                    .max_connections(500)
337                    // bump the default request size slightly, there aren't any methods exposed
338                    // with dynamic request params that can exceed this
339                    .max_request_body_size(128 * 1024 * 1024)
340                    .set_id_provider(EthSubscriptionIdProvider::default())
341            }),
342            ipc_server_config: self.ipc_server_config.map(|ipc_server_config| {
343                ipc_server_config
344                    .max_response_body_size(750 * 1024 * 1024)
345                    .max_connections(500)
346                    .max_request_body_size(128 * 1024 * 1024)
347                    .set_id_provider(EthSubscriptionIdProvider::default())
348            }),
349            ipc_endpoint: self.ipc_endpoint,
350            rpc_middleware: self.rpc_middleware,
351            http_middleware: self.http_middleware,
352        }
353    }
354}
355
356/// Holds installed modules for the auth server.
357#[derive(Debug, Clone)]
358pub struct AuthRpcModule {
359    pub(crate) inner: RpcModule<()>,
360}
361
362impl AuthRpcModule {
363    /// Create a new `AuthRpcModule` with the given `engine_api`.
364    pub fn new(engine: impl IntoEngineApiRpcModule) -> Self {
365        Self { inner: engine.into_rpc_module() }
366    }
367
368    /// Get a reference to the inner `RpcModule`.
369    pub const fn module_mut(&mut self) -> &mut RpcModule<()> {
370        &mut self.inner
371    }
372
373    /// Merge the given [Methods] in the configured authenticated methods.
374    ///
375    /// Fails if any of the methods in other is present already.
376    pub fn merge_auth_methods(
377        &mut self,
378        other: impl Into<Methods>,
379    ) -> Result<bool, RegisterMethodError> {
380        self.module_mut().merge(other.into()).map(|_| true)
381    }
382
383    /// Removes the method with the given name from the configured authenticated methods.
384    ///
385    /// Returns `true` if the method was found and removed, `false` otherwise.
386    pub fn remove_auth_method(&mut self, method_name: &'static str) -> bool {
387        self.module_mut().remove_method(method_name).is_some()
388    }
389
390    /// Removes the given methods from the configured authenticated methods.
391    pub fn remove_auth_methods(&mut self, methods: impl IntoIterator<Item = &'static str>) {
392        for name in methods {
393            self.remove_auth_method(name);
394        }
395    }
396
397    /// Replace the given [Methods] in the configured authenticated methods.
398    pub fn replace_auth_methods(
399        &mut self,
400        other: impl Into<Methods>,
401    ) -> Result<bool, RegisterMethodError> {
402        let other = other.into();
403        self.remove_auth_methods(other.method_names());
404        self.merge_auth_methods(other)
405    }
406
407    /// Convenience function for starting a server
408    pub async fn start_server<RpcMiddleware, HttpMiddleware>(
409        self,
410        config: AuthServerConfig<RpcMiddleware, HttpMiddleware>,
411    ) -> Result<AuthServerHandle, RpcError>
412    where
413        RpcMiddleware: RethRpcMiddleware,
414        HttpMiddleware: RethAuthHttpMiddleware<RpcMiddleware>,
415    {
416        config.start(self).await
417    }
418}
419
420/// A handle to the spawned auth server.
421///
422/// When this type is dropped or [`AuthServerHandle::stop`] has been called the server will be
423/// stopped.
424#[derive(Clone, Debug)]
425#[must_use = "Server stops if dropped"]
426pub struct AuthServerHandle {
427    local_addr: SocketAddr,
428    handle: Option<jsonrpsee::server::ServerHandle>,
429    secret: JwtSecret,
430    ipc_endpoint: Option<String>,
431    ipc_handle: Option<jsonrpsee::server::ServerHandle>,
432}
433
434// === impl AuthServerHandle ===
435
436impl AuthServerHandle {
437    /// Creates a new handle that isn't connected to any server.
438    ///
439    /// This can be used to satisfy types that require an engine API.
440    pub fn noop() -> Self {
441        Self {
442            local_addr: SocketAddr::new(
443                IpAddr::V4(Ipv4Addr::LOCALHOST),
444                constants::DEFAULT_AUTH_PORT,
445            ),
446            handle: None,
447            secret: JwtSecret::random(),
448            ipc_endpoint: None,
449            ipc_handle: None,
450        }
451    }
452
453    /// Returns the [`SocketAddr`] of the http server if started.
454    pub const fn local_addr(&self) -> SocketAddr {
455        self.local_addr
456    }
457
458    /// Tell the server to stop without waiting for the server to stop.
459    pub fn stop(self) -> Result<(), AlreadyStoppedError> {
460        if let Some(handle) = self.handle {
461            handle.stop()?;
462        }
463
464        if let Some(ipc_handle) = self.ipc_handle {
465            ipc_handle.stop()?;
466        }
467
468        Ok(())
469    }
470
471    /// Returns the url to the http server
472    pub fn http_url(&self) -> String {
473        format!("http://{}", self.local_addr)
474    }
475
476    /// Returns the url to the ws server
477    pub fn ws_url(&self) -> String {
478        format!("ws://{}", self.local_addr)
479    }
480
481    /// Returns a http client connected to the server.
482    ///
483    /// This client uses the JWT token to authenticate requests.
484    pub fn http_client(
485        &self,
486    ) -> impl SubscriptionClientT + use<> + Clone + Send + Sync + Unpin + 'static {
487        // Create a middleware that adds a new JWT token to every request.
488        let secret_layer = AuthClientLayer::new(self.secret);
489        let middleware = tower::ServiceBuilder::default().layer(secret_layer);
490        jsonrpsee::http_client::HttpClientBuilder::default()
491            .set_http_middleware(middleware)
492            .build(self.http_url())
493            .expect("Failed to create http client")
494    }
495
496    /// Returns a ws client connected to the server. Note that the connection can only be
497    /// be established within 1 minute due to the JWT token expiration.
498    pub async fn ws_client(&self) -> jsonrpsee::ws_client::WsClient {
499        jsonrpsee::ws_client::WsClientBuilder::default()
500            .set_headers(HeaderMap::from_iter([(
501                AUTHORIZATION,
502                secret_to_bearer_header(&self.secret),
503            )]))
504            .build(self.ws_url())
505            .await
506            .expect("Failed to create ws client")
507    }
508
509    /// Returns an ipc client connected to the server.
510    #[cfg(unix)]
511    pub async fn ipc_client(&self) -> Option<jsonrpsee::async_client::Client> {
512        use reth_ipc::client::IpcClientBuilder;
513
514        if let Some(ipc_endpoint) = &self.ipc_endpoint {
515            return Some(
516                IpcClientBuilder::default()
517                    .build(ipc_endpoint)
518                    .await
519                    .expect("Failed to create ipc client"),
520            );
521        }
522        None
523    }
524
525    /// Returns an ipc handle
526    pub fn ipc_handle(&self) -> Option<jsonrpsee::server::ServerHandle> {
527        self.ipc_handle.clone()
528    }
529
530    /// Return an ipc endpoint
531    pub fn ipc_endpoint(&self) -> Option<String> {
532        self.ipc_endpoint.clone()
533    }
534}