pub mod adapt;
use axum::Router;
use axum::body::Body;
use axum::http::StatusCode;
use axum::http::header::LOCATION;
use axum::http::request::Parts;
use axum::response::Response;
use axum::extract::{Extension, FromRequestParts};
pub use ed25519_dalek::VerifyingKey;
use std::future::ready;
use std::sync::Arc;
use uuid::Uuid;
pub use ubersession_core::cookie::{CookieHeaderSource, CookieName};
use ubersession_core::header_string::{HeaderString, HeaderStringChar, StaticHeaderString};
pub use ubersession_core::host_name::{HostName, HostNameSource};
pub use ubersession_core::session_token::{SessionToken, SessionTokenLoader};
pub use ubersession_core::uri::UriPath;
#[derive(Clone, Debug)]
pub struct AxumSessionExtractionSettings {
workflow_path: HeaderString,
cookie: CookieName,
verifying_key: VerifyingKey,
host_name: Option<HostName>
}
#[derive(Clone, Debug)]
struct ExtractSettingsWrapper(Arc<AxumSessionExtractionSettings>);
const DEFAULT_WORKFLOW_PATH: StaticHeaderString = StaticHeaderString::from_static("/_session/flow");
const FORWARD_SLASH: HeaderStringChar = HeaderStringChar::from_static('/');
const FLOW: StaticHeaderString = StaticHeaderString::from_static("flow");
impl AxumSessionExtractionSettings {
pub fn new(verifying_key: VerifyingKey) -> Self {
Self {
workflow_path: DEFAULT_WORKFLOW_PATH.to_header_string(),
cookie: CookieName::escape_str("UBERSESSION"),
verifying_key: verifying_key,
host_name: None
}
}
pub fn with_path_prefix(mut self, uri: UriPath) -> Self {
self.workflow_path = uri.header_string();
if !self.workflow_path.as_str().ends_with('/') {
self.workflow_path.push(FORWARD_SLASH);
}
self.workflow_path.push_str(&FLOW.to_header_string());
self
}
pub fn with_cookie(mut self, cookie: CookieName) -> Self {
self.cookie = cookie;
self
}
pub fn with_host_name(mut self, host_name: HostName) -> Self {
self.host_name = Some(host_name);
self
}
pub fn setup_router(self, router: Router) -> Router {
router.layer(Extension(ExtractSettingsWrapper(Arc::new(self))))
}
}
fn extract_session_from_parts(settings: &AxumSessionExtractionSettings, parts: &Parts) -> Option<SessionToken> {
let http_host =
if let Some(host_name) = parts.headers.extract_host_name() {
host_name
} else {
return None;
};
let cookie_value =
if let Some(cookie_value) = parts.headers.extract_cookie(&settings.cookie) {
if let Ok(cookie_value) = cookie_value.unescape_str() {
cookie_value
} else {
return None;
}
} else {
return None;
};
let verification_host = settings.host_name.as_ref().unwrap_or(&http_host);
SessionTokenLoader::new(verification_host.clone(), settings.verifying_key).attempt_load(&cookie_value)
}
fn from_request_parts(parts: &Parts) -> Result<SessionToken, Response> {
let settings = {
let m_settings: Option<&ExtractSettingsWrapper> = parts.extensions.get();
if let Some(settings) = m_settings {
&settings.0
} else {
let mut response = Response::new(Body::empty());
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
return Err(response);
}
};
if let Some(session_token) = extract_session_from_parts(settings, parts) {
Ok(session_token.clone())
} else {
let mut response = Response::new(Body::empty());
*response.status_mut() = StatusCode::SEE_OTHER;
response.headers_mut().insert(LOCATION, settings.workflow_path.clone().into());
Err(response)
}
}
pub struct RequiredSessionToken(pub SessionToken);
impl<S> FromRequestParts<S> for RequiredSessionToken {
type Rejection = Response;
fn from_request_parts(parts: &mut Parts, _state: &S) -> impl Future<Output = Result<Self, Response>> {
ready(from_request_parts(parts).map(|x| Self(x)))
}
}
pub struct RequiredSessionId(pub Uuid);
impl<S> FromRequestParts<S> for RequiredSessionId {
type Rejection = Response;
fn from_request_parts(parts: &mut Parts, _state: &S) -> impl Future<Output = Result<Self, Response>> {
ready(from_request_parts(parts).map(|x| Self(x.id)))
}
}