reth_rpc_layer/
jwt_validator.rs

1use crate::{AuthValidator, JwtError, JwtSecret};
2use http::{header, HeaderMap, Response, StatusCode};
3use jsonrpsee_http_client::{HttpBody, HttpResponse};
4use tracing::error;
5
6/// Implements JWT validation logics and integrates
7/// to an Http [`AuthLayer`][crate::AuthLayer]
8/// by implementing the [`AuthValidator`] trait.
9#[derive(Debug, Clone)]
10pub struct JwtAuthValidator {
11    secret: JwtSecret,
12}
13
14impl JwtAuthValidator {
15    /// Creates a new instance of [`JwtAuthValidator`].
16    /// Validation logics are implemented by the `secret`
17    /// argument (see [`JwtSecret`]).
18    pub const fn new(secret: JwtSecret) -> Self {
19        Self { secret }
20    }
21}
22
23impl AuthValidator for JwtAuthValidator {
24    fn validate(&self, headers: &HeaderMap) -> Result<(), HttpResponse> {
25        match get_bearer(headers) {
26            Some(jwt) => match self.secret.validate(&jwt) {
27                Ok(_) => Ok(()),
28                Err(e) => {
29                    error!(target: "engine::jwt-validator", "Invalid JWT: {e}");
30                    let response = err_response(e);
31                    Err(response)
32                }
33            },
34            None => {
35                let e = JwtError::MissingOrInvalidAuthorizationHeader;
36                error!(target: "engine::jwt-validator", "Invalid JWT: {e}");
37                let response = err_response(e);
38                Err(response)
39            }
40        }
41    }
42}
43
44/// This is an utility function that retrieves a bearer
45/// token from an authorization Http header.
46fn get_bearer(headers: &HeaderMap) -> Option<String> {
47    let header = headers.get(header::AUTHORIZATION)?;
48    let auth: &str = header.to_str().ok()?;
49    let prefix = "Bearer ";
50
51    if !auth.starts_with(prefix) {
52        return None;
53    }
54
55    let token: &str = &auth[prefix.len()..];
56    Some(token.into())
57}
58
59fn err_response(err: JwtError) -> HttpResponse {
60    // We build a response from an error message.
61    // We don't cope with headers or other structured fields.
62    // Then we are safe to "expect" on the result.
63    Response::builder()
64        .status(StatusCode::UNAUTHORIZED)
65        .body(HttpBody::new(err.to_string()))
66        .expect("This should never happen")
67}
68
69#[cfg(test)]
70mod tests {
71    use crate::jwt_validator::get_bearer;
72    use http::{header, HeaderMap};
73
74    #[test]
75    fn auth_header_available() {
76        let jwt = "foo";
77        let bearer = format!("Bearer {jwt}");
78        let mut headers = HeaderMap::new();
79        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
80        let token = get_bearer(&headers).unwrap();
81        assert_eq!(token, jwt);
82    }
83
84    #[test]
85    fn auth_header_not_available() {
86        let headers = HeaderMap::new();
87        let token = get_bearer(&headers);
88        assert!(token.is_none());
89    }
90
91    #[test]
92    fn auth_header_malformed() {
93        let jwt = "foo";
94        let bearer = format!("Bea___rer {jwt}");
95        let mut headers = HeaderMap::new();
96        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
97        let token = get_bearer(&headers);
98        assert!(token.is_none());
99    }
100
101    #[test]
102    fn auth_header_bearer_in_middle() {
103        // Test that "Bearer " must be at the start of the header, not in the middle
104        let jwt = "valid_token";
105        let bearer = format!("NotBearer Bearer {jwt}");
106        let mut headers = HeaderMap::new();
107        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
108        let token = get_bearer(&headers);
109        // Function should return None since "Bearer " is not at the start
110        assert!(token.is_none());
111    }
112
113    #[test]
114    fn auth_header_bearer_without_space() {
115        // Test that "BearerBearer" is not treated as "Bearer "
116        let jwt = "valid_token";
117        let bearer = format!("BearerBearer {jwt}");
118        let mut headers = HeaderMap::new();
119        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
120        let token = get_bearer(&headers);
121        // Function should return None since header doesn't start with "Bearer "
122        assert!(token.is_none());
123    }
124}