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
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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::<Vec<_>>();
|
||||
|
||||
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)?))
|
||||
|
|
|
|||
|
|
@ -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<AppState>,
|
||||
auth: AuthSession,
|
||||
messages: Messages,
|
||||
new_api_key: Form<NewApiKeyForm>,
|
||||
) -> WebResult<impl IntoResponse> {
|
||||
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"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,26 @@
|
|||
</div>
|
||||
</nav>
|
||||
<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">
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue