Skip to main content

reth_rpc_layer/
jwt_validator.rs

1use crate::{AuthValidator, JwtError, JwtSecret};
2use http::{header, HeaderMap, Response, StatusCode};
3use jsonrpsee_server::{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 the Authorization header.
46fn get_bearer(headers: &HeaderMap) -> Option<&str> {
47    headers.get(header::AUTHORIZATION)?.to_str().ok()?.strip_prefix("Bearer ")
48}
49
50fn err_response(err: JwtError) -> HttpResponse {
51    // We build a response from an error message.
52    // We don't cope with headers or other structured fields.
53    // Then we are safe to "expect" on the result.
54    Response::builder()
55        .status(StatusCode::UNAUTHORIZED)
56        .body(HttpBody::new(err.to_string()))
57        .expect("This should never happen")
58}
59
60#[cfg(test)]
61mod tests {
62    use crate::jwt_validator::get_bearer;
63    use http::{header, HeaderMap};
64
65    #[test]
66    fn auth_header_available() {
67        let jwt = "foo";
68        let bearer = format!("Bearer {jwt}");
69        let mut headers = HeaderMap::new();
70        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
71        let token = get_bearer(&headers).unwrap();
72        assert_eq!(token, jwt);
73        assert_eq!(
74            token.as_ptr(),
75            headers[header::AUTHORIZATION].as_bytes()[bearer.len() - jwt.len()..].as_ptr()
76        );
77    }
78
79    #[test]
80    fn auth_header_not_available() {
81        let headers = HeaderMap::new();
82        let token = get_bearer(&headers);
83        assert!(token.is_none());
84    }
85
86    #[test]
87    fn auth_header_malformed() {
88        let jwt = "foo";
89        let bearer = format!("Bea___rer {jwt}");
90        let mut headers = HeaderMap::new();
91        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
92        let token = get_bearer(&headers);
93        assert!(token.is_none());
94    }
95
96    #[test]
97    fn auth_header_bearer_in_middle() {
98        // Test that "Bearer " must be at the start of the header, not in the middle
99        let jwt = "valid_token";
100        let bearer = format!("NotBearer Bearer {jwt}");
101        let mut headers = HeaderMap::new();
102        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
103        let token = get_bearer(&headers);
104        // Function should return None since "Bearer " is not at the start
105        assert!(token.is_none());
106    }
107
108    #[test]
109    fn auth_header_bearer_without_space() {
110        // Test that "BearerBearer" is not treated as "Bearer "
111        let jwt = "valid_token";
112        let bearer = format!("BearerBearer {jwt}");
113        let mut headers = HeaderMap::new();
114        headers.insert(header::AUTHORIZATION, bearer.parse().unwrap());
115        let token = get_bearer(&headers);
116        // Function should return None since header doesn't start with "Bearer "
117        assert!(token.is_none());
118    }
119}