use crate::Alibi;
use alibi_core::{AuthError, AuthRequest, AuthResult, AuthSchema, HttpMethod};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CurrentSession<S: AuthSchema> {
pub user: S::User,
pub session: S::Session,
}
#[derive(Debug, Clone)]
pub struct OptionalSession<S: AuthSchema>(pub Option<CurrentSession<S>>);
impl<S: AuthSchema> CurrentSession<S> {
pub(super) async fn resolve(
auth: &Alibi<S>,
path: &str,
headers: &http::HeaderMap,
) -> AuthResult<Self> {
let request = AuthRequest::from_parts(
HttpMethod::Get,
path.to_owned(),
request_headers(headers),
None,
HashMap::new(),
);
let token = auth
.session_manager()
.extract_session_token(&request)
.ok_or(AuthError::Unauthenticated)?;
let session = auth
.session_manager()
.get_session(&token)
.await?
.ok_or(AuthError::SessionNotFound)?;
let user = auth
.context()
.session_user(&session)
.await?
.ok_or(AuthError::UserNotFound)?;
Ok(Self { user, session })
}
}
pub(super) fn request_headers(headers: &http::HeaderMap) -> HashMap<String, String> {
let mut joined = HashMap::<String, String>::new();
for (name, value) in headers {
let Ok(value) = value.to_str() else {
continue;
};
_ = joined
.entry(name.to_string())
.and_modify(|existing| {
existing.push_str(if name == http::header::COOKIE {
"; "
} else {
", "
});
existing.push_str(value);
})
.or_insert_with(|| value.to_owned());
}
joined
}
pub(super) fn max_body_bytes<S: AuthSchema>(auth: &Alibi<S>) -> usize {
let limit = auth.body_limit();
if limit.enabled {
limit.max_bytes
} else {
usize::MAX
}
}
pub(super) fn payload_too_large(max_bytes: usize) -> AuthError {
AuthError::payload_too_large(format!("Request body exceeds the {max_bytes}-byte limit"))
}
pub(super) fn check_content_length(headers: &http::HeaderMap, max_bytes: usize) -> AuthResult<()> {
let declared = headers
.get(http::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<usize>().ok());
match declared {
Some(length) if length > max_bytes => Err(payload_too_large(max_bytes)),
_ => Ok(()),
}
}
pub(super) fn auth_request(
method: &http::Method,
uri: &http::Uri,
original_uri: &http::Uri,
headers: &http::HeaderMap,
body: Vec<u8>,
base_url: &str,
) -> AuthResult<AuthRequest> {
let method = match *method {
http::Method::GET => HttpMethod::Get,
http::Method::POST => HttpMethod::Post,
http::Method::PUT => HttpMethod::Put,
http::Method::DELETE => HttpMethod::Delete,
http::Method::PATCH => HttpMethod::Patch,
http::Method::OPTIONS => HttpMethod::Options,
http::Method::HEAD => HttpMethod::Head,
_ => {
return Err(AuthError::InvalidRequest(
"Unsupported HTTP method".to_owned(),
));
}
};
let headers = request_headers(headers);
let scheme = uri.scheme_str().map_or_else(
|| {
url::Url::parse(base_url)
.map_or_else(|_| "http".to_owned(), |url| url.scheme().to_owned())
},
str::to_owned,
);
let url = uri
.authority()
.map(http::uri::Authority::as_str)
.or_else(|| headers.get("host").map(String::as_str))
.and_then(|authority| {
let path = original_uri
.path_and_query()
.map_or("/", http::uri::PathAndQuery::as_str);
url::Url::parse(&format!("{scheme}://{authority}{path}")).ok()
});
let query = uri
.query()
.map(|query| {
url::form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect::<Vec<_>>()
})
.unwrap_or_default();
let body =
(!body.is_empty() || !matches!(method, HttpMethod::Get | HttpMethod::Head)).then_some(body);
let mut request =
AuthRequest::from_parts(method, uri.path().to_owned(), headers, body, HashMap::new());
request.set_query_pairs(query);
Ok(match url {
Some(url) => request.with_url(url),
None => request,
})
}