Skip to content
Clone
b7cd2b1
141 lines 3.7 KB Rust
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use actix_web::dev::Payload;
use actix_web::http::header::AUTHORIZATION;
use actix_web::{FromRequest, HttpMessage, HttpRequest, web};
use crate::claims::AccessClaims;
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 async fn from_http_request(request: &HttpRequest) -> Self {
let Some(verifier) = request
.app_data::<web::Data<Arc<JwtVerifier>>>()
.map(|value| value.get_ref().clone())
.or_else(|| request.extensions().get::<Arc<JwtVerifier>>().cloned())
else {
return Self::anonymous();
};
let token = request
.cookie(verifier.session_cookie())
.map(|cookie| cookie.value().to_string())
.or_else(|| {
request
.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 Self::anonymous();
};
match verifier.verify_access(&token).await {
Ok(claims) => Self {
state: SessionState::Valid(claims),
raw: Some(token),
},
Err(SessionError::Expired) => Self {
state: SessionState::Expired,
raw: Some(token),
},
Err(error) => {
tracing::debug!(error = %error, "rejected bearer token");
Self::anonymous()
}
}
}
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 FromRequest for Session {
type Error = Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
fn from_request(request: &HttpRequest, _payload: &mut Payload) -> Self::Future {
let request = request.clone();
Box::pin(async move { Ok(Self::from_http_request(&request).await) })
}
}