Skip to main content

quarlus_security/
extractor.rs

1use std::sync::Arc;
2
3use quarlus_core::http::extract::{FromRef, FromRequestParts};
4use quarlus_core::http::header::{Parts, AUTHORIZATION};
5use tracing::{debug, warn};
6
7use crate::error::SecurityError;
8use crate::identity::{AuthenticatedUser, IdentityBuilder};
9use crate::jwt::JwtValidator;
10
11/// Extract a Bearer token from the Authorization header value.
12fn extract_bearer_token(header_value: &str) -> Result<&str, SecurityError> {
13    let parts: Vec<&str> = header_value.splitn(2, ' ').collect();
14    if parts.len() != 2 {
15        return Err(SecurityError::InvalidAuthScheme);
16    }
17    if !parts[0].eq_ignore_ascii_case("Bearer") {
18        return Err(SecurityError::InvalidAuthScheme);
19    }
20    Ok(parts[1])
21}
22
23/// Extract and validate a JWT identity from request parts.
24///
25/// This is the shared extraction logic used by [`AuthenticatedUser`]'s
26/// `FromRequestParts` implementation. Use it to implement `FromRequestParts`
27/// for your own identity type backed by a custom [`IdentityBuilder`].
28///
29/// # Example
30///
31/// ```ignore
32/// impl<S> FromRequestParts<S> for DbUser
33/// where
34///     S: Send + Sync,
35///     Arc<JwtValidator<DbIdentityBuilder>>: FromRef<S>,
36/// {
37///     type Rejection = quarlus_core::AppError;
38///
39///     async fn from_request_parts(
40///         parts: &mut Parts,
41///         state: &S,
42///     ) -> Result<Self, Self::Rejection> {
43///         quarlus_security::extract_jwt_identity::<S, DbIdentityBuilder>(parts, state).await
44///     }
45/// }
46/// ```
47pub async fn extract_jwt_identity<S, B>(
48    parts: &mut Parts,
49    state: &S,
50) -> Result<B::Identity, quarlus_core::AppError>
51where
52    S: Send + Sync,
53    B: IdentityBuilder + 'static,
54    Arc<JwtValidator<B>>: FromRef<S>,
55{
56    // 1. Extract the Authorization header
57    let auth_header = parts.headers.get(AUTHORIZATION).ok_or_else(|| {
58        warn!(uri = %parts.uri, "Missing Authorization header");
59        SecurityError::MissingAuthHeader
60    })?;
61
62    let auth_value = auth_header
63        .to_str()
64        .map_err(|_| SecurityError::InvalidAuthScheme)?;
65
66    // 2. Extract the Bearer token
67    let token = extract_bearer_token(auth_value)?;
68
69    // 3. Get the JwtValidator from state
70    let validator: Arc<JwtValidator<B>> = Arc::from_ref(state);
71
72    // 4. Validate the token and build the identity
73    let identity = validator.validate(token).await.map_err(|e| {
74        warn!(uri = %parts.uri, error = %e, "JWT validation failed");
75        quarlus_core::AppError::from(e)
76    })?;
77
78    debug!(uri = %parts.uri, "Authenticated request");
79    Ok(identity)
80}
81
82/// Axum extractor implementation for `AuthenticatedUser`.
83///
84/// This extracts the JWT from the `Authorization: Bearer <token>` header,
85/// validates it using the `JwtValidator` from the application state,
86/// and returns an `AuthenticatedUser` on success.
87///
88/// The application state must implement `FromRef<S>` for `Arc<JwtValidator>`.
89///
90/// For custom identity types, use [`extract_jwt_identity`] to implement
91/// `FromRequestParts` with minimal boilerplate.
92///
93/// # Example
94///
95/// ```ignore
96/// async fn protected_handler(user: AuthenticatedUser) -> impl IntoResponse {
97///     format!("Hello, {}!", user.sub)
98/// }
99/// ```
100impl<S> FromRequestParts<S> for AuthenticatedUser
101where
102    S: Send + Sync,
103    Arc<JwtValidator>: quarlus_core::http::extract::FromRef<S>,
104{
105    type Rejection = quarlus_core::AppError;
106
107    async fn from_request_parts(
108        parts: &mut Parts,
109        state: &S,
110    ) -> Result<Self, Self::Rejection> {
111        extract_jwt_identity::<S, crate::identity::DefaultIdentityBuilder>(parts, state).await
112    }
113}