diff --git a/Cargo.lock b/Cargo.lock index db11b3b..2d4f033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,6 +209,22 @@ dependencies = [ "syn", ] +[[package]] +name = "axum-messages" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d67ce6e7bc1e1e71f2a4e86d418045a29c63c4ebb631f3d9bb2f81c4958ea391" +dependencies = [ + "axum-core", + "http", + "parking_lot", + "serde", + "serde_json", + "tower", + "tower-sessions-core", + "tracing", +] + [[package]] name = "base64" version = "0.22.1" @@ -1410,12 +1426,14 @@ dependencies = [ "anyhow", "axum", "axum-login", + "axum-messages", "displaydoc", "notify-debouncer-full", "password-auth", "rand 0.9.2", "serde", "serde_json", + "sha2", "sqlx", "tera", "thiserror 2.0.17", diff --git a/nixie-server/Cargo.toml b/nixie-server/Cargo.toml index cb79760..53d3982 100644 --- a/nixie-server/Cargo.toml +++ b/nixie-server/Cargo.toml @@ -27,3 +27,5 @@ time = "0.3.45" rand = "0.9.2" serde_json.workspace = true tower-http = { version = "0.6.8", features = ["normalize-path", "fs"] } +sha2 = "0.10.9" +axum-messages = "0.8.0" diff --git a/nixie-server/src/main.rs b/nixie-server/src/main.rs index 130271b..166fa70 100644 --- a/nixie-server/src/main.rs +++ b/nixie-server/src/main.rs @@ -9,6 +9,8 @@ use axum::response::IntoResponse; use axum::routing::get; use axum_login::AuthManagerLayerBuilder; use axum_login::AuthnBackend; +use axum_messages::Messages; +use axum_messages::MessagesManagerLayer; use displaydoc::Display; use notify_debouncer_full::DebouncedEvent; use notify_debouncer_full::notify::EventKind; @@ -169,6 +171,7 @@ async fn run() -> anyhow::Result<()> { ServeDir::new("public").append_index_html_on_directories(false), ) .route("/", get(show_index)) + .layer(MessagesManagerLayer) .layer(auth_layer) .layer(NormalizePathLayer::trim_trailing_slash()) .layer(livereload) @@ -245,6 +248,7 @@ async fn shutdown_signal(handle: AbortHandle) { #[derive(Debug, FromRequestParts)] pub struct Renderer { auth: AuthSession, + messages: Messages, } impl Renderer { @@ -259,6 +263,16 @@ impl Renderer { main_context.insert("current_user", user); } + let messages = self + .messages + .clone() + .map(|message| serde_json::json!({ "level": message.level.to_string().to_ascii_lowercase(), "text": message.message})) + .collect::>(); + + if !messages.is_empty() { + main_context.insert("messages", &messages); + } + main_context.extend(context.into().unwrap_or_default()); Ok(Html(TERA.read().unwrap().render(name, &main_context)?)) diff --git a/nixie-server/src/settings/mod.rs b/nixie-server/src/settings/mod.rs index 253f7ea..9c78c3e 100644 --- a/nixie-server/src/settings/mod.rs +++ b/nixie-server/src/settings/mod.rs @@ -6,6 +6,7 @@ use axum::response::Redirect; use axum::routing::get; use axum::routing::post; use axum_login::login_required; +use axum_messages::Messages; use password_auth::generate_hash; use password_auth::verify_password; use rand::distr::Alphanumeric; @@ -13,11 +14,11 @@ use rand::distr::SampleString; use rand::rng; use serde::Deserialize; use serde::Serialize; +use sha2::Digest; use sqlx::prelude::FromRow; use tera::Context; use time::Date; use time::OffsetDateTime; -use time::Time; use crate::AppState; use crate::AuthSession; @@ -141,12 +142,20 @@ struct NewApiKeyForm { async fn create_new_api_key( app_state: State, auth: AuthSession, + messages: Messages, new_api_key: Form, ) -> WebResult { - let values = Alphanumeric.sample_string(&mut rng(), 32); - let token = tokio::task::spawn_blocking(|| generate_hash(values)) - .await - .unwrap(); + let api_key = Alphanumeric + .sample_string(&mut rng(), 32) + .to_ascii_lowercase(); + + let token = tokio::task::spawn_blocking({ + let api_key = api_key.clone(); + move || sha2::Sha256::digest(&api_key) + }) + .await + .unwrap() + .to_vec(); let expiration_date = Date::parse( &new_api_key.expiration_date, @@ -154,8 +163,9 @@ async fn create_new_api_key( ) .unwrap(); - let expiration_date = - OffsetDateTime::new_utc(expiration_date, Time::from_hms(0, 0, 0).unwrap()); + let expiration_date = expiration_date + .format(&time::format_description::well_known::iso8601::Iso8601::DATE) + .unwrap(); sqlx::query("INSERT INTO api_keys (user_id, token, name, expiration_date, permissions, revoked) VALUES (?, ?, ?, ?, ?, false)") .bind(auth.user.unwrap().id()) @@ -166,6 +176,8 @@ async fn create_new_api_key( .execute(&app_state.db) .await?; + messages.success(format!("Your API Token is created: {api_key}. It will only be displayed once. Be sure to copy it now.")); + Ok(Redirect::to("/settings/api_keys")) } diff --git a/nixie-server/templates/base.html.tera b/nixie-server/templates/base.html.tera index 4c8cbcb..7762c4f 100644 --- a/nixie-server/templates/base.html.tera +++ b/nixie-server/templates/base.html.tera @@ -42,6 +42,26 @@
+
+ {% for message in messages | default(value=[])%} + {% if message.level == "info" %} + {% set color = "bg-cyan-300" %} + {% set border_color = "border-cyan-900" %} + {% elif message.level == "success" %} + {% set color = "bg-blue-300" %} + {% set border_color = "border-blue-900" %} + {% elif message.level == "error" %} + {% set color = "bg-blue-300" %} + {% set border_color = "border-blue-900" %} + {% else %} + {% set color = "bg-zinc-300" %} + {% set border_color = "border-zinc-900" %} + {% endif %} +
+ {{ message.text }} +
+ {% endfor %} +
{% block content %}{% endblock content %}