Skip to content
Clone
b453d5d
134 lines 3.5 KB Rust
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<String>,
}
impl Session {
pub fn anonymous() -> Self {
Self {
state: SessionState::Anonymous,
raw: None,
}
}
pub fn uid(&self) -> Option<i64> {
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<i32> {
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<S> FromRequestParts<S> for Session
where
S: Send + Sync,
{
type Rejection = std::convert::Infallible;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let Some(verifier) = parts.extensions.get::<Arc<JwtVerifier>>().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())
}
}
}
}