QR Codes
This commit is contained in:
@@ -27,3 +27,6 @@ chrono = { workspace = true }
|
||||
chrono-tz = "0.10.4"
|
||||
wasm-bindgen = "0.2.120"
|
||||
theme = { version = "0.0.3", features = ["yew"] }
|
||||
qrcode-generator = "5.0.0"
|
||||
base64 = '0.22'
|
||||
dotenv = '0.15.0'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[serve]
|
||||
# The address to serve on LAN.
|
||||
addresses = ["127.0.0.1","192.168.2.157"] #,"10.150.9.7"]
|
||||
addresses = ["127.0.0.1"] #,"10.150.9.7"]
|
||||
# The address to serve on WAN.
|
||||
# address = "0.0.0.0"
|
||||
# The port to serve on.
|
||||
|
||||
@@ -138,7 +138,6 @@ fn sidebar_shell(props: &SidebarShellProps) -> Html {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Props for the AdminCheckWrapper component.
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct AdminCheckWrapperProps {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//! By re-exporting them here, other parts of the application can import page
|
||||
//! components more conveniently using `crate::pages::*`.
|
||||
pub mod basic_pages;
|
||||
pub mod qr;
|
||||
pub mod setup;
|
||||
pub mod sidebar;
|
||||
pub mod ticket;
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use gloo_net::http::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
struct QrResponse {
|
||||
raw_token: String,
|
||||
}
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct QrManagerProps {
|
||||
pub user_id: i16,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct QrPayload {
|
||||
user_id: i16,
|
||||
}
|
||||
|
||||
#[function_component(QrManager)]
|
||||
pub fn qr_manager(props: &QrManagerProps) -> Html {
|
||||
let qr_image_data = use_state(|| None::<String>);
|
||||
let error_message = use_state(|| None::<String>);
|
||||
let user_id = props.user_id;
|
||||
|
||||
let on_generate_click = {
|
||||
let qr_image_data = qr_image_data.clone();
|
||||
let error_message = error_message.clone();
|
||||
|
||||
Callback::from(move |_| {
|
||||
let qr_image_data = qr_image_data.clone();
|
||||
let error_message = error_message.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let payload = QrPayload { user_id };
|
||||
|
||||
match Request::post("/api/generate_qr")
|
||||
.json(&payload)
|
||||
.unwrap()
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) if response.ok() => {
|
||||
if let Ok(data) = response.json::<QrResponse>().await {
|
||||
// Point scanned link to the public login endpoint with the token
|
||||
let target_url = format!(
|
||||
"{}/api/qr_login?token={}",
|
||||
std::env::var("ORIGIN")
|
||||
.ok()
|
||||
.unwrap_or("http://localhost:8000".to_string()),
|
||||
data.raw_token
|
||||
);
|
||||
|
||||
match qrcode_generator::to_png_to_vec(
|
||||
target_url,
|
||||
qrcode_generator::QrCodeEcc::Medium,
|
||||
250,
|
||||
) {
|
||||
Ok(png_bytes) => {
|
||||
let base64_png = format!(
|
||||
"data:image/png;base64,{}",
|
||||
STANDARD.encode(&png_bytes)
|
||||
);
|
||||
qr_image_data.set(Some(base64_png));
|
||||
error_message.set(None);
|
||||
}
|
||||
Err(_) => {
|
||||
error_message
|
||||
.set(Some("Failed to render QR graphic.".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error_message.set(Some(
|
||||
"Failed to generate token. Are you logged in as an admin?".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div style="text-align: center; padding: 15px; border: 1px dashed #ccc; border-radius: 6px;">
|
||||
<h4>{"Permanent Login Access"}</h4>
|
||||
<p style="font-size: 0.85em; color: #555;">
|
||||
{ format!("Generate a permanent bypass login QR code for User #{}", user_id) }
|
||||
</p>
|
||||
|
||||
<button
|
||||
onclick={on_generate_click}
|
||||
style="padding: 8px 16px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;"
|
||||
>
|
||||
{"Generate QR Code"}
|
||||
</button>
|
||||
|
||||
{ if let Some(ref err) = *error_message {
|
||||
html! { <p style="color: #dc3545; margin-top: 10px; font-size: 0.9em;">{err}</p> }
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
|
||||
{ if let Some(ref qr_src) = *qr_image_data {
|
||||
html! {
|
||||
<div style="margin-top: 15px;">
|
||||
<img src={qr_src.clone()} alt="Login QR Code" style="border: 1px solid #ddd; padding: 8px; background: white;" />
|
||||
<p style="font-size: 0.8em; color: #dc3545; font-weight: bold; margin-top: 8px;">
|
||||
{"Warn the user to keep this QR code secure!"}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ pub fn register_component() -> Html {
|
||||
Ok(response) => {
|
||||
let text = response.text().await.unwrap_or_else(|_| "Unbekannt".into());
|
||||
status.set(Some(text));
|
||||
},
|
||||
}
|
||||
Err(err) => status.set(Some(format!("Netzwerkfehler: {}", err))),
|
||||
}
|
||||
});
|
||||
@@ -688,7 +688,8 @@ pub fn user_by_id_component(props: &UserProps) -> Html {
|
||||
<p><strong>{ "Ist Admin: " }</strong>{ u.is_admin }</p>
|
||||
</div>
|
||||
|
||||
<h1>{ format!("User #{}", u.id) }</h1>
|
||||
<crate::pages::qr::QrManager user_id={u.id}/>
|
||||
|
||||
<form onsubmit={onsubmit}>
|
||||
<div>
|
||||
<label>{ "Vorname" }
|
||||
|
||||
Reference in New Issue
Block a user