This commit is contained in:
2026-07-16 18:43:24 +02:00
parent ae0176b49a
commit a04c3cd8e2
5 changed files with 69 additions and 15 deletions
+3
View File
@@ -27,3 +27,6 @@ frontend/node_modules/
/target
.antigravitycli/
.sqlx/
backend/.sqlx/
dist/
+51 -12
View File
@@ -4,14 +4,15 @@ use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier, password_ha
use axum::{
Json,
extract::{Query, State},
http::StatusCode,
response::{IntoResponse, Redirect},
http::{StatusCode, header::SET_COOKIE},
response::{Html, IntoResponse, Response},
};
use jsonwebtoken::{EncodingKey, Header};
use rand::{RngCore, thread_rng};
use serde::{Deserialize, Serialize};
use sqlx::Row;
use crate::AppState;
use crate::{AppState, cookie::jwt::encode_token};
#[derive(Serialize)]
pub struct QrCodeResponse {
@@ -57,12 +58,15 @@ pub async fn generate_qr_token(
pub async fn validate_qr_token(
State(data): State<Arc<AppState>>,
Query(params): Query<AuthQuery>,
) -> Result<impl IntoResponse, StatusCode> {
println!("Incoming token: {}", params.token);
let active_tokens = sqlx::query("SELECT user_id, token_hash FROM qr_codes")
) -> Response {
let active_tokens_res = sqlx::query("SELECT user_id, token_hash FROM qr_codes")
.fetch_all(&data.db)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
.await;
let active_tokens = match active_tokens_res {
Ok(tokens) => tokens,
Err(_) => return StatusCode::INTERNAL_SERVER_ERROR.into_response(),
};
let mut authenticated_user_id: Option<i16> = None;
let argon2 = Argon2::default();
@@ -82,10 +86,45 @@ pub async fn validate_qr_token(
}
}
if let Some(_user_id) = authenticated_user_id {
let redirect_url = format!("{}/", data.env.origin.trim_end_matches('/'));
Ok(Redirect::to(&redirect_url))
if let Some(user_id) = authenticated_user_id {
let base_origin = data.env.origin.trim_end_matches('/');
let header = Header::default();
let id_str = user_id.to_string();
let secret_bytes = data.env.token_secret.as_bytes();
let encoding_key = EncodingKey::from_secret(secret_bytes);
let token = encode_token(&header, id_str, &encoding_key);
let cookie_value = format!(
"token={}; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600",
token
);
let redirect_html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<meta http-equiv="refresh" content="0; url={}/" />
<script type="text/javascript">
window.location.href = "{}/"
</script>
<title>Redirecting...</title>
</head>
<body>
<p>Authenticated! Redirecting you to the dashboard...</p>
</body>
</html>"#,
base_origin, base_origin
);
let mut response = Html(redirect_html).into_response();
if let Ok(header_value) = axum::http::HeaderValue::from_str(&cookie_value) {
response.headers_mut().insert(SET_COOKIE, header_value);
}
response
} else {
Err(StatusCode::UNAUTHORIZED)
StatusCode::UNAUTHORIZED.into_response()
}
}
+13 -1
View File
@@ -1,7 +1,10 @@
use std::sync::Arc;
use axum::{
Router, middleware,
Router,
http::StatusCode,
middleware,
response::IntoResponse,
routing::{get, post},
};
@@ -18,6 +21,14 @@ use crate::{
},
};
async fn debug_fallback_handler(uri: axum::http::Uri) -> impl IntoResponse {
println!(
"[BACKEND DEBUG] Axum Fallback triggered! Unmatched request path: {}",
uri
);
(StatusCode::NOT_FOUND, "Backend route not found")
}
/// Creates the complete router with all API endpoints.
///
/// The router is organized in layers for proper middleware application. Uses [`AppState`]
@@ -90,5 +101,6 @@ pub fn create_router(state: Arc<AppState>) -> Router {
Router::new()
.merge(protected_routes)
.merge(public_routes)
.fallback(debug_fallback_handler)
.with_state(state)
}
+1 -1
View File
@@ -5,7 +5,7 @@
<link data-trunk rel="rust" data-bin="bin" />
<link data-trunk rel="scss" href="src/styles/main.scss" />
<link data-trunk rel="icon" href="src/assets/favicon.ico" type="image/x-icon" />
<link data-trunk rel="copy-dir" href="src/assets" />
<link data-trunk rel="copy-dir" href="src/assets" data-target-path="assets" />
<meta charset="utf-8" />
<title>Yew App</title>
</head>
+1 -1
View File
@@ -136,6 +136,6 @@ pub fn denied_component() -> Html {
#[component(Icon)]
pub fn icon_component() -> Html {
html! {
<img src="assets/csg.png" class="csg-icon" />
<img src="/assets/csg.png" class="csg-icon" />
}
}