cratefield_auth_client/
extractor.rs1use std::sync::Arc;
9
10use axum::extract::FromRequestParts;
11use axum::http::request::Parts;
12use cratefield_core::Problem;
13
14use crate::jwks::AuthClient;
15use crate::verify::{Claims, token_fingerprint};
16
17#[derive(Clone)]
22pub struct AuthState(pub Arc<AuthClient>);
23
24#[derive(Debug, Clone)]
32pub struct Authenticated(pub Claims);
33
34impl<S> FromRequestParts<S> for Authenticated
35where
36 AuthState: axum::extract::FromRef<S>,
37 S: Send + Sync,
38{
39 type Rejection = Problem;
40
41 async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
42 let AuthState(client) = axum::extract::FromRef::from_ref(state);
43 let token = bearer(parts).ok_or_else(unauthorized)?;
44 match client.verify(token).await {
45 Ok(claims) => Ok(Self(claims)),
46 Err(err) => {
47 tracing::info!(
50 reason = %err,
51 token = %token_fingerprint(token),
52 "rejected a bearer token"
53 );
54 Err(unauthorized())
55 }
56 }
57 }
58}
59
60fn bearer(parts: &Parts) -> Option<&str> {
62 let value = parts
63 .headers
64 .get(axum::http::header::AUTHORIZATION)?
65 .to_str()
66 .ok()?;
67 let rest = value
68 .strip_prefix("Bearer ")
69 .or_else(|| value.strip_prefix("bearer "))?;
70 (!rest.is_empty()).then_some(rest)
71}
72
73pub const UNAUTHENTICATED: cratefield_core::ProblemDef = cratefield_core::SLUGS.unauthenticated;
80
81fn unauthorized() -> Problem {
82 Problem::new(&UNAUTHENTICATED)
83}