diff options
author | Vika <vika@fireburn.ru> | 2024-07-08 23:56:57 +0300 |
---|---|---|
committer | Vika <vika@fireburn.ru> | 2024-07-08 23:57:28 +0300 |
commit | b629dc4f7d03c493a4c173d63ef5205b6a24b838 (patch) | |
tree | 2cd2af1c68802903825793c443a908685bd6b06e | |
parent | 4b7832e7a75ef202c60dc6c00f7518975e64f03a (diff) | |
download | kittybox-b629dc4f7d03c493a4c173d63ef5205b6a24b838.tar.zst |
WIP: admin (not wired up yet, and DEFINITELY NOT SECURE, DO NOT WIRE UP)
-rw-r--r-- | src/admin/mod.rs | 114 | ||||
-rw-r--r-- | templates/src/admin.rs | 5 | ||||
-rw-r--r-- | templates/src/lib.rs | 2 |
3 files changed, 121 insertions, 0 deletions
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<tokio::sync::RwLock<HashSet<uuid::Uuid>>> = std::sync::LazyLock::new(|| Default::default()); + + +async fn set_name<D: Storage + 'static>( + Host(host): Host, + Extension(db): Extension<D>, + Form(NameChange { name }): Form<NameChange> +) -> Result<(), StorageError> { + db.set_setting::<SiteName>(&host, name).await +} + +async fn get_name<D: Storage + 'static>(Host(host): Host, Extension(db): Extension<D>) -> Result<String, StorageError> { + db.get_setting::<SiteName>(&host).await.map(|name| name.as_ref().to_owned()) +} + +async fn change_password<A: AuthBackend>( + Host(host): Host, + Extension(auth): Extension<A>, + Form(PasswordChange { old_password, new_password }): Form<PasswordChange> +) -> 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<D: Storage + 'static, A: AuthBackend>( + Host(host): Host, + cookies: CookieJar, + Extension(db): Extension<D>, + Extension(auth): Extension<A> +) -> axum::response::Response { + + let page = kittybox_frontend_renderer::admin::AdminHome {}; + + (page.to_string().into_response()) +} + + +pub fn router<D: Storage + 'static, A: AuthBackend>(db: D, auth: A) -> axum::Router { + axum::Router::new() + .nest("/.kittybox/admin", axum::Router::new() + // routes go here + .route( + "/", + axum::routing::get(dashboard::<D, A>) + ) + .route( + "/api/settings/name", + axum::routing::post(set_name::<D>) + .get(get_name::<D>) + ) + .route("/api/settings/password", axum::routing::post(change_password::<A>)) + .layer(Extension(db)) + .layer(Extension(auth)) + ) +} diff --git a/templates/src/admin.rs b/templates/src/admin.rs new file mode 100644 index 0000000..2837b34 --- /dev/null +++ b/templates/src/admin.rs @@ -0,0 +1,5 @@ +markup::define! { + AdminHome { + p { "This is an example admin homepage." } + } +} diff --git a/templates/src/lib.rs b/templates/src/lib.rs index 8d5d5fa..dd263e9 100644 --- a/templates/src/lib.rs +++ b/templates/src/lib.rs @@ -9,6 +9,8 @@ pub use login::LoginPage; mod mf2; pub use mf2::{Entry, VCard, Feed, Food, POSTS_PER_PAGE}; +pub mod admin; + pub mod assets { use axum::response::{IntoResponse, Response}; use axum::extract::Path; |