use std::future::Future;
use axum::{
http::StatusCode,
response::{IntoResponse, Redirect},
};
use frunk::{HCons, HNil, hlist::HList};
use crate::layers::{LayerContrib, LayerRequest, LayerStep, ViewLayer, cons_tagged};
use crate::plugins::users::middleware::{resolve_auth_headers, roles_allowed};
use crate::plugins::users::state::{AuthContext, UsersState};
use crate::tag::Tagged;
pub struct AuthTag;
pub trait HasUsersState {
fn users_state(&self) -> &UsersState;
}
pub trait AuthSlot {
fn set_auth(&mut self, auth: AuthContext);
fn auth(&self) -> Option<&AuthContext>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AuthLayer;
impl LayerContrib for AuthLayer {
type Contrib = HCons<Tagged<AuthTag, AuthContext>, HNil>;
}
impl<Ctx, Acc> ViewLayer<Ctx, Acc> for AuthLayer
where
Acc: HList + Send,
Ctx: HasUsersState + AuthSlot + Send,
{
type AccOut = HCons<Tagged<AuthTag, AuthContext>, Acc>;
fn run<'a>(
&'a self,
ctx: &'a mut Ctx,
req: &'a mut LayerRequest,
acc: Acc,
) -> impl Future<Output = LayerStep<Self::AccOut>> + Send + 'a
where
Acc: Send + 'a,
{
async move {
match resolve_auth_headers(&req.headers, ctx.users_state()).await {
Some(auth) => {
ctx.set_auth(auth.clone());
req.auth_present = true;
LayerStep::Continue(cons_tagged::<AuthTag, _, _>(auth, acc))
}
None => LayerStep::Done(Redirect::to("/users/login").into_response()),
}
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct OptionalAuthLayer;
impl LayerContrib for OptionalAuthLayer {
type Contrib = HCons<Tagged<AuthTag, Option<AuthContext>>, HNil>;
}
impl<Ctx, Acc> ViewLayer<Ctx, Acc> for OptionalAuthLayer
where
Acc: HList + Send,
Ctx: HasUsersState + AuthSlot + Send,
{
type AccOut = HCons<Tagged<AuthTag, Option<AuthContext>>, Acc>;
fn run<'a>(
&'a self,
ctx: &'a mut Ctx,
req: &'a mut LayerRequest,
acc: Acc,
) -> impl Future<Output = LayerStep<Self::AccOut>> + Send + 'a
where
Acc: Send + 'a,
{
async move {
let auth = resolve_auth_headers(&req.headers, ctx.users_state()).await;
if let Some(ref a) = auth {
ctx.set_auth(a.clone());
req.auth_present = true;
}
LayerStep::Continue(cons_tagged::<AuthTag, _, _>(auth, acc))
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct RoleLayer {
pub roles: &'static [&'static str],
}
impl RoleLayer {
pub const fn allow(roles: &'static [&'static str]) -> Self {
Self { roles }
}
}
impl LayerContrib for RoleLayer {
type Contrib = HNil;
}
impl<Ctx, Acc> ViewLayer<Ctx, Acc> for RoleLayer
where
Acc: HList + Send,
Ctx: AuthSlot + Send,
{
type AccOut = Acc;
fn run<'a>(
&'a self,
ctx: &'a mut Ctx,
_req: &'a mut LayerRequest,
acc: Acc,
) -> impl Future<Output = LayerStep<Self::AccOut>> + Send + 'a
where
Acc: Send + 'a,
{
async move {
let Some(auth) = ctx.auth() else {
return LayerStep::Done(StatusCode::UNAUTHORIZED.into_response());
};
if roles_allowed(auth, self.roles) {
LayerStep::Continue(acc)
} else {
LayerStep::Done(StatusCode::UNAUTHORIZED.into_response())
}
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SuperuserLayer;
impl LayerContrib for SuperuserLayer {
type Contrib = HNil;
}
impl<Ctx, Acc> ViewLayer<Ctx, Acc> for SuperuserLayer
where
Acc: HList + Send,
Ctx: AuthSlot + Send,
{
type AccOut = Acc;
fn run<'a>(
&'a self,
ctx: &'a mut Ctx,
_req: &'a mut LayerRequest,
acc: Acc,
) -> impl Future<Output = LayerStep<Self::AccOut>> + Send + 'a
where
Acc: Send + 'a,
{
async move {
let Some(auth) = ctx.auth() else {
return LayerStep::Done(StatusCode::UNAUTHORIZED.into_response());
};
if auth.user.is_superuser {
LayerStep::Continue(acc)
} else {
LayerStep::Done(StatusCode::UNAUTHORIZED.into_response())
}
}
}
}
impl crate::components::SlotCtx {
pub fn from_auth(auth: &AuthContext) -> Self {
Self {
name: Some(auth.user.name.clone()),
role: Some(auth.role.clone()),
is_superuser: auth.user.is_superuser,
is_staff: auth.is_staff,
}
}
}