use std::sync::Arc;
use topcoat::{
context::Cx,
router::{
Body, Layer, LayerFuture, Next, Path, PathBuf,
error::{forbidden, unauthorized},
request::{method, uri},
},
};
use super::{
login::{login_url, logout_url},
session::{resolve, session_row, session_user},
unauthenticated_error,
};
use crate::panel::{
route_path,
state::{CurrentPanel, PanelState, panels},
};
pub(crate) struct PanelGate {
path: PathBuf,
panel: Arc<PanelState>,
}
impl PanelGate {
pub(crate) fn new(panel: Arc<PanelState>) -> Self {
Self {
path: route_path(&panel.prefix),
panel,
}
}
}
impl Layer for PanelGate {
fn path(&self) -> Option<&Path> {
Some(&self.path)
}
fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
Box::pin(async move {
let cx = cx.with(CurrentPanel(Arc::clone(&self.panel)));
let Some(authenticator) = self.panel.auth.authenticator() else {
return next.run(&cx, body).await;
};
if uri(&cx).path() == login_url(&cx)
&& matches!(
*method(&cx),
http::Method::GET | http::Method::HEAD | http::Method::POST
)
{
return next.run(&cx, body).await;
}
let logout_route =
uri(&cx).path() == logout_url(&cx) && matches!(*method(&cx), http::Method::POST);
match resolve(&cx, &self.panel, authenticator).await? {
Some(signed) if signed.user.can_access_panel() || logout_route => {
next.run(&cx.with(signed), body).await
}
Some(_) => Err(forbidden().into()),
None => Err(unauthenticated_error(&cx)),
}
})
}
}
pub(crate) struct RuntimeGate {
path: PathBuf,
}
impl RuntimeGate {
pub(crate) fn new() -> Self {
Self {
path: route_path(crate::topcoat_compat::RUNTIME_PREFIX),
}
}
}
impl Layer for RuntimeGate {
fn path(&self) -> Option<&Path> {
Some(&self.path)
}
fn handle<'a>(&'a self, cx: &'a Cx, body: Body, next: Next<'a>) -> LayerFuture<'a> {
Box::pin(async move {
let Some(panels) = panels(cx).filter(|panels| panels.any_gates()) else {
return next.run(cx, body).await;
};
let signed = match session_row(cx).await? {
Some(row) => match panels
.by_prefix(&row.panel)
.and_then(|panel| Some((panel, panel.auth.authenticator()?)))
{
Some((panel, authenticator)) => {
session_user(cx, row, panel, authenticator).await?
}
None => None,
},
None => None,
};
match signed {
Some(signed) if signed.user.can_access_panel() => {
let panel = CurrentPanel(Arc::clone(&signed.panel));
next.run(&cx.with_many((signed, panel)), body).await
}
Some(_) => Err(forbidden().into()),
None if panels.all_gate() => Err(unauthorized().into()),
None => next.run(cx, body).await,
}
})
}
}