api_tools/server/axum/security/jwt/
access_token.rs1use crate::{server::axum::response::ApiError, value_objects::datetime::UtcDateTime};
4use axum::{extract::FromRequestParts, http::request::Parts};
5use hyper::{HeaderMap, header};
6use serde::Deserialize;
7
8pub type AccessTokenValue = String;
10
11#[derive(Debug, Clone, PartialEq, Deserialize)]
13pub struct AccessToken {
14 pub token: AccessTokenValue,
16
17 pub expired_at: UtcDateTime,
19}
20
21impl AccessToken {
22 pub fn new(token: String, expired_at: UtcDateTime) -> Self {
38 Self { token, expired_at }
39 }
40
41 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
54impl<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}