use axum::extract::{FromRequestParts, OptionalFromRequestParts};
use axum::http::{StatusCode, request::Parts};
use axum::response::{IntoResponse, Response};
use crate::core::{ClerkAuth, VerificationOutcome};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AuthRejection {
Missing,
Invalid,
Unavailable,
}
impl AuthRejection {
pub fn status_code(self) -> StatusCode {
match self {
Self::Missing | Self::Invalid => StatusCode::UNAUTHORIZED,
Self::Unavailable => StatusCode::SERVICE_UNAVAILABLE,
}
}
pub fn message(self) -> &'static str {
match self {
Self::Missing => "unauthenticated",
Self::Invalid => "invalid Clerk credentials",
Self::Unavailable => "Clerk verification unavailable",
}
}
}
impl IntoResponse for AuthRejection {
fn into_response(self) -> Response {
(self.status_code(), self.message()).into_response()
}
}
impl<S> FromRequestParts<S> for ClerkAuth
where
S: Send + Sync,
{
type Rejection = AuthRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
match parts.extensions.get::<VerificationOutcome>() {
Some(VerificationOutcome::Valid(auth)) => Ok(auth.clone()),
Some(VerificationOutcome::Invalid(_)) => Err(AuthRejection::Invalid),
Some(VerificationOutcome::Unavailable) => Err(AuthRejection::Unavailable),
Some(VerificationOutcome::Missing) => Err(AuthRejection::Missing),
None => {
log_missing_layer();
Err(AuthRejection::Missing)
}
}
}
}
impl<S> OptionalFromRequestParts<S> for ClerkAuth
where
S: Send + Sync,
{
type Rejection = AuthRejection;
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Option<Self>, Self::Rejection> {
match parts.extensions.get::<VerificationOutcome>() {
Some(VerificationOutcome::Valid(auth)) => Ok(Some(auth.clone())),
Some(VerificationOutcome::Unavailable) => Err(AuthRejection::Unavailable),
Some(VerificationOutcome::Missing | VerificationOutcome::Invalid(_)) => Ok(None),
None => {
log_missing_layer();
Ok(None)
}
}
}
}
pub(super) fn log_missing_layer() {
use std::sync::atomic::{AtomicBool, Ordering};
static WARNED: AtomicBool = AtomicBool::new(false);
if WARNED.swap(true, Ordering::Relaxed) {
tracing::debug!(
"no Clerk verification outcome on request; is ClerkAuthLayer installed on this route?"
);
} else {
tracing::warn!(
"no Clerk verification outcome on request; is ClerkAuthLayer installed on this route?"
);
}
}