Skip to main content

reth_rpc_builder/
auth.rs

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