api_tools/server/axum/security/jwt/
access_token.rs

1//! Access token entity
2
3use crate::{server::axum::response::ApiError, value_objects::datetime::UtcDateTime};
4use axum::{extract::FromRequestParts, http::request::Parts};
5use hyper::{HeaderMap, header};
6use serde::Deserialize;
7
8/// Access Token Value represents the value of the access token
9pub type AccessTokenValue = String;
10
11/// Access Token
12#[derive(Debug, Clone, PartialEq, Deserialize)]
13pub struct AccessToken {
14    /// Token
15    pub token: AccessTokenValue,
16
17    /// Expiration time
18    pub expired_at: UtcDateTime,
19}
20
21impl AccessToken {
22    /// Create a new access token
23    ///
24    /// # Example
25    ///
26    /// ```
27    /// use api_tools::server::axum::security::jwt::access_token::AccessToken;
28    /// use api_tools::value_objects::datetime::UtcDateTime;
29    ///
30    /// let token = "my_access_token".to_string();
31    /// let expired_at = UtcDateTime::now();
32    /// let access_token = AccessToken::new(token, expired_at.clone());
33    ///
34    /// assert_eq!(access_token.token, "my_access_token".to_string());
35    /// assert_eq!(access_token.expired_at, expired_at);
36    /// ```
37    pub fn new(token: String, expired_at: UtcDateTime) -> Self {
38        Self { token, expired_at }
39    }
40
41    /// Extract bearer token from headers
42    pub fn extract_bearer_token_from_headers(headers: &HeaderMap) -> Option<Self> {
43        headers
44            .get(header::AUTHORIZATION)
45            .and_then(|h| h.to_str().ok())
46            .and_then(|h| {
47                let words = h.split("Bearer").collect::<Vec<&str>>();
48                words.get(1).map(|w| w.trim())
49            })
50            .map(|token| AccessToken::new(token.to_string(), UtcDateTime::now()))
51    }
52}
53
54/// JWT extractor from HTTP headers
55impl<S> FromRequestParts<S> for AccessToken
56where
57    S: Send + Sync,
58{
59    type Rejection = ApiError;
60
61    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
62        Self::extract_bearer_token_from_headers(&parts.headers)
63            .ok_or(ApiError::Unauthorized("Missing or invalid token".to_string()))
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use axum::http::HeaderValue;
71
72    #[test]
73    fn test_extract_bearer_token_from_headers() {
74        let mut headers = HeaderMap::new();
75        headers.insert(header::AUTHORIZATION, HeaderValue::from_static("Bearer my_token"));
76
77        let token = AccessToken::extract_bearer_token_from_headers(&headers);
78        assert!(token.is_some());
79        assert_eq!(token.unwrap().token, "my_token");
80    }
81
82    #[test]
83    fn test_extract_bearer_token_from_headers_invalid() {
84        let mut headers = HeaderMap::new();
85        headers.insert(header::AUTHORIZATION, HeaderValue::from_static("Invalid my_token"));
86
87        let token = AccessToken::extract_bearer_token_from_headers(&headers);
88        assert!(token.is_none());
89    }
90}