cargo_smith/auth/
jwt.rs

1use actix_web::dev::{ServiceRequest, ServiceResponse};
2use std::{rc::Rc, task::{Context, Poll}};
3use actix_web::Error;
4use actix_service::{Service, Transform};
5use futures::{future::{ok, LocalBoxFuture, Ready}};
6use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
7
8use crate::auth::claims::Claims;
9
10pub struct JwtMiddleware {
11    secret_key: String
12}
13
14impl JwtMiddleware{
15    pub fn new(secret_key: String) -> Self {
16        Self {
17            secret_key
18        }
19    }
20}
21
22impl<S, B> Transform<S, ServiceRequest> for JwtMiddleware
23where
24    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
25    S::Future: 'static,
26    B: 'static
27{
28    type Response = ServiceResponse<B>;
29    type Error = Error;
30    type Transform = JwtMiddlewareService<S>;
31    type InitError = ();
32    type Future = Ready<Result<Self::Transform, Self::InitError>>;
33
34    fn new_transform(&self, service: S) -> Self::Future {
35        ok(JwtMiddlewareService {
36            service: Rc::new(service),
37            secret_key: self.secret_key.clone()
38        })
39    }
40}
41
42pub struct JwtMiddlewareService<S> {
43    service: Rc<S>,
44    secret_key: String,
45}
46
47impl<S, B> Service<ServiceRequest> for JwtMiddlewareService<S>
48where
49    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
50    S::Future: 'static,
51{
52    type Response = ServiceResponse<B>;
53    type Error = Error;
54    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
55
56    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
57        self.service.poll_ready(cx)
58    }
59
60    fn call(&self, req: ServiceRequest) -> Self::Future {
61        let service = Rc::clone(&self.service);
62        
63        let auth_header = match req.headers().get("Authorization") {
64            Some(header) => header,
65            None => {
66                return Box::pin(async {
67                    Err(actix_web::error::ErrorUnauthorized("Authorization header missing"))
68                })
69            }
70        };
71
72        let auth_str = match auth_header.to_str() {
73            Ok(str) => str,
74            Err(_) => {
75                return Box::pin(async {
76                    Err(actix_web::error::ErrorUnauthorized("Invalid Authorization header encoding"))
77                })
78            }
79        };
80
81        if !auth_str.starts_with("Bearer ") {
82            return Box::pin(async {
83                Err(actix_web::error::ErrorUnauthorized("Authorization header must start with 'Bearer '"))
84            })
85        }
86
87        let token = &auth_str[7..];
88
89        if token.is_empty() {
90            return Box::pin(async {
91                Err(actix_web::error::ErrorUnauthorized("Empty token"))
92            });
93        }
94
95        let mut validation = Validation::new(Algorithm::HS256);
96        validation.validate_exp = true;
97
98        let token_data = decode::<Claims<serde_json::Value>>( // Concrete type
99                token,
100                &DecodingKey::from_secret(self.secret_key.as_bytes()),
101                &validation
102            );
103
104        match token_data {
105            Ok(_data) => {
106                return Box::pin(service.call(req))
107            },
108            Err(err) => {
109                let error_msg = match err.kind() {
110                    jsonwebtoken::errors::ErrorKind::ExpiredSignature => "Token expired",
111                    jsonwebtoken::errors::ErrorKind::InvalidToken => "Invalid token",
112                    jsonwebtoken::errors::ErrorKind::InvalidSignature => "Invalid token signature",
113                    jsonwebtoken::errors::ErrorKind::InvalidEcdsaKey => "Invalid key",
114                    jsonwebtoken::errors::ErrorKind::InvalidAlgorithm => "Invalid algorithm",
115                    jsonwebtoken::errors::ErrorKind::InvalidIssuer => "Invalid issuer",
116                    jsonwebtoken::errors::ErrorKind::InvalidAudience => "Invalid audience",
117                    jsonwebtoken::errors::ErrorKind::InvalidSubject => "Invalid subject",
118                    jsonwebtoken::errors::ErrorKind::ImmatureSignature => "Token not yet valid",
119                    _ => "Invalid token", // Handles malformed_jwt_structure and other cases
120                };
121
122                Box::pin(async move {
123                    Err(actix_web::error::ErrorUnauthorized(error_msg))
124                })
125            }
126        }
127    }
128}