Add messages so users can copy out the API key after creating it
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
7a5233e385
commit
6a5acaab32
5 changed files with 73 additions and 7 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -209,6 +209,22 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
|
@ -1410,12 +1426,14 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-login",
|
"axum-login",
|
||||||
|
"axum-messages",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"notify-debouncer-full",
|
"notify-debouncer-full",
|
||||||
"password-auth",
|
"password-auth",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tera",
|
"tera",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,5 @@ time = "0.3.45"
|
||||||
rand = "0.9.2"
|
rand = "0.9.2"
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tower-http = { version = "0.6.8", features = ["normalize-path", "fs"] }
|
tower-http = { version = "0.6.8", features = ["normalize-path", "fs"] }
|
||||||
|
sha2 = "0.10.9"
|
||||||
|
axum-messages = "0.8.0"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ use axum::response::IntoResponse;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum_login::AuthManagerLayerBuilder;
|
use axum_login::AuthManagerLayerBuilder;
|
||||||
use axum_login::AuthnBackend;
|
use axum_login::AuthnBackend;
|
||||||
|
use axum_messages::Messages;
|
||||||
|
use axum_messages::MessagesManagerLayer;
|
||||||
use displaydoc::Display;
|
use displaydoc::Display;
|
||||||
use notify_debouncer_full::DebouncedEvent;
|
use notify_debouncer_full::DebouncedEvent;
|
||||||
use notify_debouncer_full::notify::EventKind;
|
use notify_debouncer_full::notify::EventKind;
|
||||||
|
|
@ -169,6 +171,7 @@ async fn run() -> anyhow::Result<()> {
|
||||||
ServeDir::new("public").append_index_html_on_directories(false),
|
ServeDir::new("public").append_index_html_on_directories(false),
|
||||||
)
|
)
|
||||||
.route("/", get(show_index))
|
.route("/", get(show_index))
|
||||||
|
.layer(MessagesManagerLayer)
|
||||||
.layer(auth_layer)
|
.layer(auth_layer)
|
||||||
.layer(NormalizePathLayer::trim_trailing_slash())
|
.layer(NormalizePathLayer::trim_trailing_slash())
|
||||||
.layer(livereload)
|
.layer(livereload)
|
||||||
|
|
@ -245,6 +248,7 @@ async fn shutdown_signal(handle: AbortHandle) {
|
||||||
#[derive(Debug, FromRequestParts)]
|
#[derive(Debug, FromRequestParts)]
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
|
messages: Messages,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
|
|
@ -259,6 +263,16 @@ impl Renderer {
|
||||||
main_context.insert("current_user", user);
|
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::<Vec<_>>();
|
||||||
|
|
||||||
|
if !messages.is_empty() {
|
||||||
|
main_context.insert("messages", &messages);
|
||||||
|
}
|
||||||
|
|
||||||
main_context.extend(context.into().unwrap_or_default());
|
main_context.extend(context.into().unwrap_or_default());
|
||||||
|
|
||||||
Ok(Html(TERA.read().unwrap().render(name, &main_context)?))
|
Ok(Html(TERA.read().unwrap().render(name, &main_context)?))
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use axum::response::Redirect;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum_login::login_required;
|
use axum_login::login_required;
|
||||||
|
use axum_messages::Messages;
|
||||||
use password_auth::generate_hash;
|
use password_auth::generate_hash;
|
||||||
use password_auth::verify_password;
|
use password_auth::verify_password;
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
|
|
@ -13,11 +14,11 @@ use rand::distr::SampleString;
|
||||||
use rand::rng;
|
use rand::rng;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use sha2::Digest;
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
use time::Date;
|
use time::Date;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use time::Time;
|
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use crate::AuthSession;
|
use crate::AuthSession;
|
||||||
|
|
@ -141,12 +142,20 @@ struct NewApiKeyForm {
|
||||||
async fn create_new_api_key(
|
async fn create_new_api_key(
|
||||||
app_state: State<AppState>,
|
app_state: State<AppState>,
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
|
messages: Messages,
|
||||||
new_api_key: Form<NewApiKeyForm>,
|
new_api_key: Form<NewApiKeyForm>,
|
||||||
) -> WebResult<impl IntoResponse> {
|
) -> WebResult<impl IntoResponse> {
|
||||||
let values = Alphanumeric.sample_string(&mut rng(), 32);
|
let api_key = Alphanumeric
|
||||||
let token = tokio::task::spawn_blocking(|| generate_hash(values))
|
.sample_string(&mut rng(), 32)
|
||||||
.await
|
.to_ascii_lowercase();
|
||||||
.unwrap();
|
|
||||||
|
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(
|
let expiration_date = Date::parse(
|
||||||
&new_api_key.expiration_date,
|
&new_api_key.expiration_date,
|
||||||
|
|
@ -154,8 +163,9 @@ async fn create_new_api_key(
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expiration_date =
|
let expiration_date = expiration_date
|
||||||
OffsetDateTime::new_utc(expiration_date, Time::from_hms(0, 0, 0).unwrap());
|
.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)")
|
sqlx::query("INSERT INTO api_keys (user_id, token, name, expiration_date, permissions, revoked) VALUES (?, ?, ?, ?, ?, false)")
|
||||||
.bind(auth.user.unwrap().id())
|
.bind(auth.user.unwrap().id())
|
||||||
|
|
@ -166,6 +176,8 @@ async fn create_new_api_key(
|
||||||
.execute(&app_state.db)
|
.execute(&app_state.db)
|
||||||
.await?;
|
.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"))
|
Ok(Redirect::to("/settings/api_keys"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,26 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<main class="grow px-2 flex flex-col inset-shadow-sm">
|
<main class="grow px-2 flex flex-col inset-shadow-sm">
|
||||||
|
<div class="m-4 flex flex-col">
|
||||||
|
{% 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 %}
|
||||||
|
<div class="p-4 {{color}} rounded shadow-md border {{border_color}}">
|
||||||
|
{{ message.text }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<div class="grow flex mx-auto container">
|
<div class="grow flex mx-auto container">
|
||||||
{% block content %}{% endblock content %}
|
{% block content %}{% endblock content %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue