Skip to main content

reth_rpc_layer/
auth_client_layer.rs

1use crate::{Claims, JwtSecret};
2use alloy_json_rpc::RequestPacket;
3use http::{header::AUTHORIZATION, HeaderValue};
4use std::{
5    task::{Context, Poll},
6    time::{Duration, SystemTime, UNIX_EPOCH},
7};
8use tower::{Layer, Service};
9
10/// A layer for alloy RPC clients that adds a new JWT token to every request using
11/// `AuthClientService`.
12#[derive(Debug)]
13pub struct AuthClientLayer {
14    secret: JwtSecret,
15}
16
17impl AuthClientLayer {
18    /// Create a new `AuthClientLayer` with the given `secret`.
19    pub const fn new(secret: JwtSecret) -> Self {
20        Self { secret }
21    }
22}
23
24impl<S> Layer<S> for AuthClientLayer {
25    type Service = AuthClientService<S>;
26
27    fn layer(&self, inner: S) -> Self::Service {
28        AuthClientService::new(self.secret, inner)
29    }
30}
31
32/// Automatically authenticates every client request with the given `secret`.
33#[derive(Debug, Clone)]
34pub struct AuthClientService<S> {
35    secret: JwtSecret,
36    inner: S,
37}
38
39impl<S> AuthClientService<S> {
40    const fn new(secret: JwtSecret, inner: S) -> Self {
41        Self { secret, inner }
42    }
43}
44
45impl<S> Service<RequestPacket> for AuthClientService<S>
46where
47    S: Service<RequestPacket>,
48{
49    type Response = S::Response;
50    type Error = S::Error;
51    type Future = S::Future;
52
53    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
54        self.inner.poll_ready(cx)
55    }
56
57    fn call(&mut self, mut request: RequestPacket) -> Self::Future {
58        // HTTP transports merge the headers of all requests in a packet, so a batch only needs the
59        // header once.
60        let first = match &mut request {
61            RequestPacket::Single(request) => Some(request),
62            RequestPacket::Batch(requests) => requests.first_mut(),
63        };
64        if let Some(first) = first {
65            first.headers_mut().insert(AUTHORIZATION, secret_to_bearer_header(&self.secret));
66        }
67        self.inner.call(request)
68    }
69}
70
71/// Helper function to convert a secret into a Bearer auth header value with claims according to
72/// <https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md#jwt-claims>.
73/// The token is valid for 60 seconds.
74pub fn secret_to_bearer_header(secret: &JwtSecret) -> HeaderValue {
75    format!(
76        "Bearer {}",
77        secret
78            .encode(&Claims {
79                iat: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap() +
80                    Duration::from_secs(60))
81                .as_secs(),
82                exp: None,
83            })
84            .unwrap()
85    )
86    .try_into()
87    .unwrap()
88}