use axum::{
http::{Method, StatusCode},
response::{IntoResponse, Redirect},
Extension, Form,
};
use quokka::{
extract::{Extensions, Session},
handler::html::{FormDataStore, FormResponse, TemplateDataLoader},
state::{FromState, ProvideState, Templating},
};
use crate::{
data::{Severity, Toast},
middleware::{LoginData, LoginProviders},
service::page_loader::AdminErrorMessage,
state::AdminState,
};
use super::AdminPageData;
pub const ADMIN_USER_SESSION_KEY: &str = "admin_user_session";
#[derive(Clone, FromState)]
pub struct AdminLoginPageLoader {
#[from_state(
bounds = "State: ProvideState<AdminState<State>> + 'static",
builder = ProvideState::<AdminState<_>>::provide(state).title,
)]
title: String,
#[from_state(
bounds = "State: ProvideState<AdminState<State>> + 'static",
builder = ProvideState::<AdminState<_>>::provide(state).login_providers,
)]
login_providers: LoginProviders,
templating: Templating,
}
pub struct LoginPageData {}
impl<S: Send + Sync + 'static> TemplateDataLoader<S> for AdminLoginPageLoader {
type Args = (Method, Extensions<Toast>);
type Data = AdminPageData<()>;
#[tracing::instrument(
skip_all,
target = "quokka_admin::service::page_loader::admin_login_page_loader::AdminLoginPageLoader"
)]
async fn load_data(&self, (method, mut toasts): Self::Args) -> quokka::Result<Self::Data> {
if method == Method::POST {
toasts.0.push(Severity::Critical.message(
"Login failed",
"The user could not be found or the password is invalid",
));
}
Ok(AdminPageData {
title: self.title.clone(),
subtitle: Some("Login".to_string()),
page: (),
navigation: Default::default(),
sidebar_widgets: Default::default(),
toasts: toasts.0,
error: Default::default(),
})
}
#[tracing::instrument(
skip_all,
target = "quokka_admin::service::page_loader::admin_login_page_loader::AdminLoginPageLoader"
)]
async fn render_error(&self, error: quokka::Error) -> impl axum::response::IntoResponse {
tracing::error!(?error, "Caught error in AdminPageLoader::render_error");
(
StatusCode::from_u16(error.status_code)
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR),
axum::response::Html(self.templating.render(
"quokka-admin/page/login/error.html.hbs",
&AdminPageData::<()> {
title: "Quokka Admin".to_string(),
navigation: Default::default(),
sidebar_widgets: Default::default(),
error: Some(AdminErrorMessage {
message: error.message,
status_code: error.status_code,
}),
..Default::default()
},
)),
)
}
}
impl<S: Send + Sync + 'static> FormDataStore<S> for AdminLoginPageLoader {
type StoreArgs = Extension<Session>;
type AdditionalResponse = Session;
type Body = Form<LoginData>;
async fn store_data(
&self,
Extension(mut session): Self::StoreArgs,
Form(form): Self::Body,
) -> quokka::Result<FormResponse<Self::AdditionalResponse>> {
for provider in &self.login_providers.providers {
match provider.login(&form).await {
Ok(Some(data)) => {
let message = Extensions::new().add(Severity::Success.message(
"Login successful",
format!("Welcome back {}", data.user_identifier),
));
tracing::debug!(
?data,
provider = provider.provider_name(),
"User authenticated"
);
session
.add_extension(ADMIN_USER_SESSION_KEY, data)
.inspect_err(|error| {
tracing::error!(
?error,
"Error while adding login result to user session"
)
})
.ok();
return Ok(FormResponse::Exit(
(session, message, Redirect::to("/admin")).into_response(),
));
}
Err(error) => {
tracing::error!(
?error,
provider = provider.provider_name(),
"Error while logging in user"
);
}
_ => {}
}
}
Ok(FormResponse::Empty)
}
}