From b629dc4f7d03c493a4c173d63ef5205b6a24b838 Mon Sep 17 00:00:00 2001 From: Vika Date: Mon, 8 Jul 2024 23:56:57 +0300 Subject: WIP: admin (not wired up yet, and DEFINITELY NOT SECURE, DO NOT WIRE UP) --- src/admin/mod.rs | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/admin/mod.rs (limited to 'src') diff --git a/src/admin/mod.rs b/src/admin/mod.rs new file mode 100644 index 0000000..abc4515 --- /dev/null +++ b/src/admin/mod.rs @@ -0,0 +1,114 @@ +//! Admin dashboard for Kittybox and its related API routes. +// This needs to be rewritten to have proper authentication. +// +// The cookies will be shared with the login system, and should check +// if the login is the same as the website owner. If it is, the admin +// dashboard and API should be available. +// +// Alternatively, the API could check for an access token with _some +// kind_ of privileged scope, like `kittybox:admin` (namespaced to +// prevent collisions with future well-known scopes). +use std::collections::HashSet; + +use axum::extract::Host; +use axum::response::{Response, IntoResponse}; +use axum::{Extension, Form}; +use axum_extra::extract::CookieJar; +use hyper::StatusCode; + +use crate::database::settings::{SiteName, Setting}; +use crate::database::{Storage, StorageError}; +use crate::indieauth::backend::AuthBackend; + +#[derive(serde::Deserialize)] +struct NameChange { + name: String +} + +#[derive(serde::Deserialize)] +struct PasswordChange { + old_password: String, + new_password: String +} + +static SESSION_STORE: std::sync::LazyLock>> = std::sync::LazyLock::new(|| Default::default()); + + +async fn set_name( + Host(host): Host, + Extension(db): Extension, + Form(NameChange { name }): Form +) -> Result<(), StorageError> { + db.set_setting::(&host, name).await +} + +async fn get_name(Host(host): Host, Extension(db): Extension) -> Result { + db.get_setting::(&host).await.map(|name| name.as_ref().to_owned()) +} + +async fn change_password( + Host(host): Host, + Extension(auth): Extension, + Form(PasswordChange { old_password, new_password }): Form +) -> StatusCode { + let website = url::Url::parse(&format!("https://{host}/")).unwrap(); + if auth.verify_password(&website, old_password).await.is_ok() { + if let Err(err) = auth.enroll_password(&website, new_password).await { + tracing::error!("Error changing password: {}", err); + StatusCode::INTERNAL_SERVER_ERROR + } else { + StatusCode::OK + } + } else { + StatusCode::BAD_REQUEST + } +} + +impl axum::response::IntoResponse for StorageError { + fn into_response(self) -> axum::response::Response { + let code = match self.kind() { + crate::database::ErrorKind::Backend => StatusCode::INTERNAL_SERVER_ERROR, + crate::database::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN, + crate::database::ErrorKind::JsonParsing => StatusCode::BAD_REQUEST, + crate::database::ErrorKind::NotFound => StatusCode::NOT_FOUND, + crate::database::ErrorKind::BadRequest => StatusCode::BAD_REQUEST, + crate::database::ErrorKind::Conflict => StatusCode::CONFLICT, + crate::database::ErrorKind::Other => StatusCode::INTERNAL_SERVER_ERROR, + }; + + (code, self.to_string()).into_response() + } +} + + +async fn dashboard( + Host(host): Host, + cookies: CookieJar, + Extension(db): Extension, + Extension(auth): Extension +) -> axum::response::Response { + + let page = kittybox_frontend_renderer::admin::AdminHome {}; + + (page.to_string().into_response()) +} + + +pub fn router(db: D, auth: A) -> axum::Router { + axum::Router::new() + .nest("/.kittybox/admin", axum::Router::new() + // routes go here + .route( + "/", + axum::routing::get(dashboard::) + ) + .route( + "/api/settings/name", + axum::routing::post(set_name::) + .get(get_name::) + ) + .route("/api/settings/password", axum::routing::post(change_password::)) + .layer(Extension(db)) + .layer(Extension(auth)) + ) +} -- cgit 1.4.1