1use std::sync::Arc;
4
5use axum::extract::{FromRef, FromRequestParts};
6use axum::http::request::Parts;
7use axum::http::{header, StatusCode};
8use axum::response::{IntoResponse, Response};
9use axum::Json;
10use libfw_core::auth::AuthError;
11use libfw_core::claims::TokenClaims;
12
13use crate::ServerState;
14
15#[derive(Debug, thiserror::Error)]
17pub enum AuthRejection {
18 #[error("{0}")]
20 Unauthorized(String),
21 #[error("permission denied: {action} on `{path}`")]
23 Forbidden { path: String, action: String },
24}
25
26impl From<AuthError> for AuthRejection {
27 fn from(err: AuthError) -> Self {
28 match err {
29 AuthError::MissingToken => AuthRejection::Unauthorized("missing bearer token".into()),
30 AuthError::Invalid(msg) => AuthRejection::Unauthorized(format!("invalid token: {msg}")),
31 AuthError::Expired => AuthRejection::Unauthorized("token expired".into()),
32 AuthError::Forbidden { path, action } => {
33 AuthRejection::Forbidden { path, action: action.to_string() }
34 }
35 }
36 }
37}
38
39impl IntoResponse for AuthRejection {
40 fn into_response(self) -> Response {
41 let (status, message) = match self {
42 AuthRejection::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
43 AuthRejection::Forbidden { path, action } => (
44 StatusCode::FORBIDDEN,
45 format!("permission denied: {action} on `{path}`"),
46 ),
47 };
48 (status, Json(serde_json::json!({ "error": message }))).into_response()
49 }
50}
51
52pub struct BearerClaims(pub TokenClaims);
58
59impl<S> FromRequestParts<S> for BearerClaims
60where
61 S: Send + Sync,
62 Arc<ServerState>: axum::extract::FromRef<S>,
63{
64 type Rejection = AuthRejection;
65
66 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
67 let state = Arc::<ServerState>::from_ref(state);
68 let header_value = parts
69 .headers
70 .get(header::AUTHORIZATION)
71 .and_then(|v| v.to_str().ok())
72 .ok_or_else(|| AuthRejection::Unauthorized("missing bearer token".into()))?;
73 let token = header_value
74 .strip_prefix("Bearer ")
75 .ok_or_else(|| AuthRejection::Unauthorized("malformed authorization header".into()))?
76 .trim();
77 let claims = state
78 .verifier
79 .verify(token)
80 .map_err(AuthRejection::from)?;
81 Ok(BearerClaims(claims))
82 }
83}