#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub use axess_core::{
AuthSession,
axum::{
self,
http::{self, Uri},
},
tracing,
};
#[cfg(feature = "authz")]
#[doc(hidden)]
pub mod __cedar {
pub use cedar_policy::{Context, Entities, EntityUid};
}
#[cfg(feature = "authz")]
#[doc(hidden)]
pub mod __authz {
pub use axess_core::authz::{
AuthzDecision, AuthzDenied, PolicyEvaluator, PolicyStore, RequestEntityProvider,
};
}
#[cfg(feature = "authz")]
#[doc(hidden)]
pub mod __macro_support;
#[cfg(feature = "authz")]
mod authz_macro;
fn update_query(uri: &Uri, new_query: String) -> Result<Uri, http::Error> {
let query = form_urlencoded::parse(uri.query().map(|q| q.as_bytes()).unwrap_or_default());
let updated_query = form_urlencoded::Serializer::new(new_query)
.extend_pairs(query)
.finish();
let mut parts = uri.clone().into_parts();
parts.path_and_query = Some(format!("{}?{}", uri.path(), updated_query).parse()?);
Ok(Uri::from_parts(parts)?)
}
#[doc(hidden)]
pub fn url_with_redirect_query(
url: &str,
redirect_field: &str,
redirect_uri: Uri,
) -> Result<Uri, http::Error> {
let uri = url.parse::<Uri>()?;
if uri.query().is_some_and(|q| q.contains(redirect_field)) {
return Ok(uri);
};
let redirect_uri_string = redirect_uri.to_string();
let redirect_uri_encoded: String =
form_urlencoded::byte_serialize(redirect_uri_string.as_bytes()).collect();
let redirect_query = format!("{redirect_field}={redirect_uri_encoded}");
update_query(&uri, redirect_query)
}
#[macro_export]
macro_rules! predicate_required {
($predicate:expr, url = $login_url:expr, param = $redirect_field:expr) => {
$crate::predicate_required!($predicate, login_url = $login_url, redirect_field = $redirect_field)
};
($predicate:expr, $alternative:expr) => {{
use axum::{
middleware::{from_fn, Next},
response::IntoResponse,
};
from_fn(
|auth_session: $crate::AuthSession, req, next: Next| async move {
if $predicate(auth_session).await {
next.run(req).await
} else {
$alternative.into_response()
}
},
)
}};
($predicate:expr, login_url = $login_url:expr, redirect_field = $redirect_field:expr) => {{
use axum::{
extract::OriginalUri,
middleware::{from_fn, Next},
response::{IntoResponse, Redirect},
};
from_fn(
|auth_session: $crate::AuthSession,
OriginalUri(original_uri): OriginalUri,
req,
next: Next| async move {
if $predicate(auth_session).await {
next.run(req).await
} else {
match $crate::url_with_redirect_query(
$login_url,
$redirect_field,
original_uri
) {
Ok(login_url) => {
Redirect::temporary(&login_url.to_string()).into_response()
}
Err(err) => {
$crate::tracing::error!(err = %err, "Failed to build redirect URL");
$crate::axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
},
)
}};
}
#[macro_export]
macro_rules! require_authn {
(url = $login_url:expr, param = $redirect_field:expr) => {
$crate::require_authn!($login_url, $redirect_field)
};
(url = $login_url:expr) => {
$crate::require_authn!($login_url, "next")
};
($login_url:expr, $redirect_field:expr) => {{
use axum::{
extract::OriginalUri,
middleware::{from_fn, Next},
response::{IntoResponse, Redirect},
};
from_fn(
|auth_session: $crate::AuthSession,
OriginalUri(original_uri): OriginalUri,
req,
next: Next| async move {
if auth_session.is_authenticated().await {
next.run(req).await
} else {
match $crate::url_with_redirect_query(
$login_url,
$redirect_field,
original_uri
) {
Ok(login_url) => {
Redirect::temporary(&login_url.to_string()).into_response()
}
Err(err) => {
$crate::tracing::error!(err = %err, "Failed to build login redirect URL");
$crate::axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
},
)
}};
($login_url:expr) => {
$crate::require_authn!($login_url, "next")
};
() => {{
use axum::{
middleware::{from_fn, Next},
response::IntoResponse,
};
from_fn(
|auth_session: $crate::AuthSession, req, next: Next| async move {
if auth_session.is_authenticated().await {
next.run(req).await
} else {
$crate::axum::http::StatusCode::UNAUTHORIZED.into_response()
}
},
)
}};
}
#[macro_export]
macro_rules! require_partial_authn {
(url = $login_url:expr, param = $redirect_field:expr) => {
$crate::require_partial_authn!(login_url = $login_url, redirect_field = $redirect_field)
};
(url = $login_url:expr) => {
$crate::require_partial_authn!(login_url = $login_url, redirect_field = "next")
};
() => {{
async fn is_partial_authenticated(auth_session: $crate::AuthSession) -> bool {
auth_session.auth_state().await.is_authenticating()
}
$crate::predicate_required!(
is_partial_authenticated,
$crate::axum::http::StatusCode::UNAUTHORIZED
)
}};
(login_url = $login_url:expr, redirect_field = $redirect_field:expr) => {{
async fn is_partial_authenticated(auth_session: $crate::AuthSession) -> bool {
auth_session.auth_state().await.is_authenticating()
}
$crate::predicate_required!(
is_partial_authenticated,
login_url = $login_url,
redirect_field = $redirect_field
)
}};
(login_url = $login_url:expr) => {
$crate::require_partial_authn!(login_url = $login_url, redirect_field = "next")
};
}