use autumn_web::auth::impersonation;
use autumn_web::consent::safe_redirect_target;
use autumn_web::session::Session;
use autumn_web::{AppState, AutumnResult};
use axum::extract::{FromRequestParts, State};
use axum::http::request::Parts;
use axum::response::{IntoResponse, Redirect, Response};
use maud::Markup;
use serde::Deserialize;
use crate::routes::AdminPrefix;
use crate::templates::{ImpersonationBanner, impersonation_banner};
#[derive(Debug, Clone, Default)]
pub struct AdminImpersonation(impersonation::Impersonation);
impl AdminImpersonation {
#[must_use]
pub const fn state(&self) -> Option<&impersonation::ImpersonationState> {
self.0.state()
}
#[must_use]
pub fn banner(
&self,
admin_prefix: &str,
csrf_token: &str,
csrf_form_field: &str,
) -> Option<ImpersonationBanner> {
self.0
.state()
.map(|state| ImpersonationBanner::new(state, admin_prefix, csrf_token, csrf_form_field))
}
}
impl FromRequestParts<AppState> for AdminImpersonation {
type Rejection = std::convert::Infallible;
async fn from_request_parts(
parts: &mut Parts,
state: &AppState,
) -> Result<Self, Self::Rejection> {
let inner = impersonation::Impersonation::from_request_parts(parts, state).await?;
Ok(Self(inner))
}
}
pub async fn impersonation_banner_for(
state: &AppState,
session: &Session,
admin_prefix: &str,
csrf_token: &str,
csrf_form_field: &str,
) -> Option<Markup> {
let active = impersonation::impersonation_state(state, session).await?;
Some(impersonation_banner(&ImpersonationBanner::new(
&active,
admin_prefix,
csrf_token,
csrf_form_field,
)))
}
#[derive(Debug, Deserialize)]
pub struct BeginForm {
user_id: String,
#[serde(default)]
return_to: Option<String>,
}
pub async fn impersonate_begin(
State(state): State<AppState>,
session: Session,
axum::extract::Form(form): axum::extract::Form<BeginForm>,
) -> AutumnResult<Response> {
impersonation::begin_impersonation(&state, &session, form.user_id).await?;
let target = form.return_to.as_deref().unwrap_or("/");
Ok(Redirect::to(safe_redirect_target(target)).into_response())
}
pub async fn impersonate_stop(
State(state): State<AppState>,
session: Session,
axum::Extension(AdminPrefix(prefix)): axum::Extension<AdminPrefix>,
body: axum::body::Bytes,
) -> AutumnResult<Response> {
impersonation::end_impersonation(&state, &session).await?;
let return_to = form_urlencoded::parse(&body)
.find(|(key, _)| key == "return_to")
.map(|(_, value)| value.into_owned())
.unwrap_or_default();
let destination = if return_to.is_empty() {
safe_redirect_target(&prefix).to_owned()
} else {
safe_redirect_target(&return_to).to_owned()
};
Ok(Redirect::to(&destination).into_response())
}