use std::sync::Arc; use axum::extract::FromRequestParts; use axum::http::header::AUTHORIZATION; use axum::http::request::Parts; use crate::claims::AccessClaims; use crate::cookie::read_cookie; use crate::error::SessionError; use crate::verifier::JwtVerifier; #[derive(Clone)] enum SessionState { Anonymous, Expired, Valid(AccessClaims), } #[derive(Clone)] pub struct Session { state: SessionState, raw: Option, } impl Session { pub fn anonymous() -> Self { Self { state: SessionState::Anonymous, raw: None, } } pub fn uid(&self) -> Option { match &self.state { SessionState::Valid(claims) => Some(claims.sub), _ => None, } } pub fn username(&self) -> Option<&str> { match &self.state { SessionState::Valid(claims) => claims.username.as_deref(), _ => None, } } pub fn session_id(&self) -> Option<&str> { match &self.state { SessionState::Valid(claims) => claims.sid.as_deref(), _ => None, } } pub fn security_version(&self) -> Option { match &self.state { SessionState::Valid(claims) => Some(claims.security_version), _ => None, } } pub fn scopes(&self) -> &[String] { match &self.state { SessionState::Valid(claims) => claims.scopes(), _ => &[], } } pub fn claims(&self) -> Option<&AccessClaims> { match &self.state { SessionState::Valid(claims) => Some(claims), _ => None, } } pub fn token(&self) -> Option<&str> { self.raw.as_deref() } pub fn is_authenticated(&self) -> bool { matches!(self.state, SessionState::Valid(_)) } pub fn is_expired(&self) -> bool { matches!(self.state, SessionState::Expired) } } impl FromRequestParts for Session where S: Send + Sync, { type Rejection = std::convert::Infallible; async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { let Some(verifier) = parts.extensions.get::>().cloned() else { return Ok(Self::anonymous()); }; let token = parts .headers .get(axum::http::header::COOKIE) .and_then(|value| value.to_str().ok()) .and_then(|header| read_cookie(header, verifier.session_cookie()).map(str::to_owned)) .or_else(|| { parts .headers .get(AUTHORIZATION) .and_then(|value| value.to_str().ok()) .and_then(|value| value.strip_prefix("Bearer ")) .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_owned) }); let Some(token) = token else { return Ok(Self::anonymous()); }; match verifier.verify_access(&token).await { Ok(claims) => Ok(Self { state: SessionState::Valid(claims), raw: Some(token), }), Err(SessionError::Expired) => Ok(Self { state: SessionState::Expired, raw: Some(token), }), Err(error) => { tracing::debug!(error = %error, "rejected bearer token"); Ok(Self::anonymous()) } } } }