From 982618b9a0a5c035402d7785452462b95b2dc396 Mon Sep 17 00:00:00 2001 From: Philipp Hofer Date: Fri, 11 Apr 2025 12:47:39 +0200 Subject: [PATCH] simply station login dramatically, only allow link 'login' --- Cargo.lock | 130 --------------- Cargo.toml | 1 - src/admin/station/mod.rs | 11 ++ src/lib.rs | 14 +- src/station.rs | 338 ++++++++++----------------------------- 5 files changed, 97 insertions(+), 397 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd54ca2..8856bfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,41 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -168,29 +133,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-extra" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" -dependencies = [ - "axum", - "axum-core", - "bytes", - "cookie", - "futures-util", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "serde", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.74" @@ -309,16 +251,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -340,11 +272,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "aes-gcm", - "base64", "percent-encoding", - "rand", - "subtle", "time", "version_check", ] @@ -420,19 +348,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "der" version = "0.7.9" @@ -705,16 +623,6 @@ dependencies = [ "wasi 0.14.2+wasi-0.2.4", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "gimli" version = "0.31.1" @@ -1080,15 +988,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "itertools" version = "0.11.0" @@ -1324,12 +1223,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "parking" version = "2.2.1" @@ -1413,18 +1306,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -2090,7 +1971,6 @@ name = "stationslauf" version = "0.1.0" dependencies = [ "axum", - "axum-extra", "chrono", "dotenv", "maud", @@ -2479,16 +2359,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "unsafe-libyaml" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index 26e833e..d0d1c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] axum = "0.8" -axum-extra = { version = "0.10", features = [ "cookie", "cookie-private" ]} chrono = { version = "0.4", features = ["serde"]} dotenv = "0.15" maud = { version = "0.27", features = ["axum"] } diff --git a/src/admin/station/mod.rs b/src/admin/station/mod.rs index ace13df..0243d3c 100644 --- a/src/admin/station/mod.rs +++ b/src/admin/station/mod.rs @@ -39,6 +39,17 @@ impl Station { .ok() } + pub async fn find_by_id_and_code(db: &SqlitePool, id: i64, code: &str) -> Option { + sqlx::query_as!( + Self, + "SELECT id, name, notes, amount_people, last_login, pw, lat, lng FROM station WHERE id = ? AND pw = ?", + id, code + ) + .fetch_one(db) + .await + .ok() + } + async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { sqlx::query!("INSERT INTO station(name) VALUES (?)", name) .execute(db) diff --git a/src/lib.rs b/src/lib.rs index cad2ac3..0d64bd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use partials::page; use sqlx::SqlitePool; use std::sync::Arc; use tokio::net::TcpListener; -use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer}; +use tower_sessions::{MemoryStore, SessionManagerLayer}; pub(crate) mod admin; mod partials; @@ -105,13 +105,6 @@ async fn serve_marker_png() -> Response { #[derive(Clone)] struct AppState { db: Arc, - key: Key, -} - -impl FromRef for Key { - fn from_ref(state: &AppState) -> Self { - state.key.clone() - } } impl FromRef for Arc { @@ -125,10 +118,7 @@ pub async fn start(listener: TcpListener, db: SqlitePool) { let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store); - let state = AppState { - db: Arc::new(db), - key: Key::generate(), - }; + let state = AppState { db: Arc::new(db) }; let app = Router::new() .nest("/s", station::routes()) // TODO: maybe switch to "/" diff --git a/src/station.rs b/src/station.rs index 774c809..7dc6a3e 100644 --- a/src/station.rs +++ b/src/station.rs @@ -1,267 +1,97 @@ -use crate::{err, partials, succ, AppState, Station}; -use axum::{ - extract::State, - response::{IntoResponse, Redirect}, - routing::{get, post}, - Form, Router, -}; -use axum_extra::extract::{CookieJar, PrivateCookieJar}; -use maud::{html, Markup, PreEscaped}; -use serde::Deserialize; +use crate::{partials, AppState, Station}; +use axum::{extract::State, routing::get, Router}; +use maud::{html, Markup}; use sqlx::SqlitePool; use std::sync::Arc; -use tower_sessions::{cookie::Cookie, Session}; +use tower_sessions::Session; -async fn station_picker( - State(db): State>, - session: Session, - jar: CookieJar, -) -> Result<(CookieJar, Markup), (CookieJar, impl IntoResponse)> { - let (jar, current_station_cookie) = get_station_cookie(&db, jar).await; - if let Some(station) = current_station_cookie { - return Err((jar, Redirect::to(&format!("/s/{}", station.id)))); - } else { - let stations = Station::all(&db).await; - let content = html! { - h1 { "Wähle deine Station" } - select onchange="window.location.href='/s/' + this.value;" { - option selected value="" { - "Deine Station..." - } - @for station in stations { - option value=(station.id) { (station.name) }; - } - } - - }; - Ok((jar, partials::page(content, session, false).await)) - } -} - -async fn get_station_cookie(db: &SqlitePool, jar: CookieJar) -> (CookieJar, Option) { - let Some(station_id) = jar.get("station_id") else { - return (jar, None); // No station_id cookie - }; - - let station_id = match station_id.value().parse::() { - Ok(number) => number, - Err(_) => { - // got some cookie which isn't a i64 -> destroy and start over again - let jar = jar.remove(Cookie::from("station_id")); - return (jar, None); - } - }; - - let Some(station) = Station::find_by_id(db, station_id).await else { - // got some cookie with an i64 which is no valid station_id -> destroy and start over again - let jar = jar.remove(Cookie::from("station_id")); - return (jar, None); - }; - - (jar, Some(station)) -} - -async fn decide_between_stations( - cookie: &Station, - trying_to_access: &Station, - session: Session, -) -> Markup { - let content = html! { - h1 { "Wähle deine Station" } - ul { - li { - "Die neu aufgerufene " - (PreEscaped("→ ")) - a href=(format!("/s/{}", trying_to_access.id)) { - button { (trying_to_access.name) } - } - } - li { - "Die Alte " - (PreEscaped("→ ")) - a href=(format!("/s/{}", cookie.id)) { - button { (cookie.name) } - } - } - } - }; - partials::page(content, session, false).await -} +//async fn view( +// State(db): State>, +// session: Session, +// jar: CookieJar, +// pjar: PrivateCookieJar, +// axum::extract::Path(id): axum::extract::Path, +//) -> Result<(CookieJar, PrivateCookieJar, Markup), (CookieJar, PrivateCookieJar, impl IntoResponse)> +//{ +// // Station selector +// let (mut jar, current_station_cookie) = get_station_cookie(&db, jar).await; +// if current_station_cookie.is_none() { +// jar = jar.add(Cookie::new("station_id", id.to_string())); +// } +// if let Some(current_station_cookie) = current_station_cookie { +// if current_station_cookie.id != id { +// // user has a cookie, which is a different station than she is trying to access +// if let Some(station) = Station::find_by_id(&db, id).await { +// jar = jar.remove(Cookie::from("station_id")); +// // trying to access valid station id +// err!(session, "Du hast versucht eine neue Station zu öffnen obwohl du bereits eine andere Station offen hattest. Welche möchtest du nun verwenden?"); +// return Ok(( +// jar, +// pjar, +// decide_between_stations(¤t_station_cookie, &station, session).await, +// )); +// } else { +// // user trying to access _in_valid station id -> make her aware + redirect to old +// err!(session, "Du hast versucht eine Station öffnen, die es nicht gibt. Nachdem du vorher schonmal eine andere Station (die es gibt) geöffnet hattest, bist du nun zu dieser weitergeleitet worden. Wenn du das nicht willst, logg dich bitte aus."); +// return Err(( +// jar, +// pjar, +// Redirect::to(&format!("/s/{}", current_station_cookie.id)), +// )); +// } +// } +// } +// let station = Station::find_by_id(&db, id).await.unwrap(); +// +// let mut pjar = pjar; +// +// // PW Checker +// if let Some(pw) = pjar.get("pw") { +// if pw.value() != station.pw { +// pjar = pjar.remove(Cookie::from("station_id")); +// err!(session, "Du hattest einen falschen Code für Station {} gespeichert. Bitte gibt den richtigen ein:", station.name ); +// return Err((jar, pjar, Redirect::to(&format!("/s/code",)))); +// } +// } else { +// return Err((jar, pjar, Redirect::to(&format!("/s/code",)))); +// } +// +// let content = html! { +// nav { +// ul { +// li { strong { (format!("Station {}", station.name)) } } +// } +// ul { +// li { a href="/s/station-logout" { "Logout" } } +// } +// } +// h1 { "test" } +// }; +// +// Ok((jar, pjar, partials::page(content, session, false).await)) +//} async fn view( State(db): State>, session: Session, - jar: CookieJar, - pjar: PrivateCookieJar, - axum::extract::Path(id): axum::extract::Path, -) -> Result<(CookieJar, PrivateCookieJar, Markup), (CookieJar, PrivateCookieJar, impl IntoResponse)> -{ - // Station selector - let (mut jar, current_station_cookie) = get_station_cookie(&db, jar).await; - if current_station_cookie.is_none() { - jar = jar.add(Cookie::new("station_id", id.to_string())); - } - if let Some(current_station_cookie) = current_station_cookie { - if current_station_cookie.id != id { - // user has a cookie, which is a different station than she is trying to access - if let Some(station) = Station::find_by_id(&db, id).await { - jar = jar.remove(Cookie::from("station_id")); - // trying to access valid station id - err!(session, "Du hast versucht eine neue Station zu öffnen obwohl du bereits eine andere Station offen hattest. Welche möchtest du nun verwenden?"); - return Ok(( - jar, - pjar, - decide_between_stations(¤t_station_cookie, &station, session).await, - )); - } else { - // user trying to access _in_valid station id -> make her aware + redirect to old - err!(session, "Du hast versucht eine Station öffnen, die es nicht gibt. Nachdem du vorher schonmal eine andere Station (die es gibt) geöffnet hattest, bist du nun zu dieser weitergeleitet worden. Wenn du das nicht willst, logg dich bitte aus."); - return Err(( - jar, - pjar, - Redirect::to(&format!("/s/{}", current_station_cookie.id)), - )); - } - } - } - let station = Station::find_by_id(&db, id).await.unwrap(); - - let mut pjar = pjar; - - // PW Checker - if let Some(pw) = pjar.get("pw") { - if pw.value() != station.pw { - pjar = pjar.remove(Cookie::from("station_id")); - err!(session, "Du hattest einen falschen Code für Station {} gespeichert. Bitte gibt den richtigen ein:", station.name ); - return Err((jar, pjar, Redirect::to(&format!("/s/code",)))); - } - } else { - return Err((jar, pjar, Redirect::to(&format!("/s/code",)))); - } - - let content = html! { - nav { - ul { - li { strong { (format!("Station {}", station.name)) } } - } - ul { - li { a href="/s/station-logout" { "Logout" } } - } - } - h1 { "test" } - }; - - Ok((jar, pjar, partials::page(content, session, false).await)) -} - -async fn code( - State(db): State>, - session: Session, - jar: CookieJar, - pjar: PrivateCookieJar, -) -> Result<(CookieJar, PrivateCookieJar, Markup), (CookieJar, PrivateCookieJar, impl IntoResponse)> -{ - let (jar, current_station_cookie) = get_station_cookie(&db, jar).await; - let Some(station) = current_station_cookie else { - return Err((jar, pjar, Redirect::to("/s"))); - }; - - let content = html! { - nav { - ul { - li { strong { (format!("Station {}", station.name)) } } - } - ul { - li { a href="/s/station-logout" { "Station wechseln" } } - } - } - h1 { "Code eingeben" } - form action="/s/station-login" method="post" { - fieldset role="group" { - input type="text" name="pw" placeholder="Code" required; - input type="submit" value="Login"; - } - - } - }; - - Ok((jar, pjar, partials::page(content, session, false).await)) -} - -#[derive(Deserialize)] -struct LoginForm { - pw: String, -} - -async fn login( - State(db): State>, - session: Session, - jar: CookieJar, - pjar: PrivateCookieJar, - Form(form): Form, -) -> (CookieJar, PrivateCookieJar, impl IntoResponse) { - let (jar, current_station_cookie) = get_station_cookie(&db, jar).await; - let Some(station) = current_station_cookie else { - return (jar, pjar, Redirect::to("/s")); - }; - - let mut pjar = pjar; - - if form.pw == station.pw { - pjar = pjar.add(Cookie::new("pw", form.pw)); - succ!( - session, - "Erfolgreich eingeloggt, viel Spaß beim bewerten :-)" - ); - return (jar, pjar, Redirect::to(&format!("/s/{}", station.id))); - } else { - err!(session, "Falsches Passwort. Probiere es erneut."); - std::thread::sleep(std::time::Duration::from_secs(1)); - return (jar, pjar, Redirect::to("/s/code")); - } -} -async fn logout(session: Session, mut jar: CookieJar) -> (CookieJar, impl IntoResponse) { - jar = jar.remove(Cookie::from("station_id")); - - succ!(session, "Erfolgreich ausgeloggt!"); - - (jar, Redirect::to("/s")) -} - -async fn quick_login( - State(db): State>, - session: Session, - jar: CookieJar, - pjar: PrivateCookieJar, axum::extract::Path((id, code)): axum::extract::Path<(i64, String)>, -) -> (CookieJar, PrivateCookieJar, impl IntoResponse) { - if let Some(station) = Station::find_by_id(&db, id).await { - if station.pw == code { - succ!( - session, - "Erfolgreich eingeloggt, viel Spaß beim bewerten :-)" - ); - let mut pjar = pjar.remove(Cookie::from("pw")); - let mut jar = jar.remove(Cookie::from("station_id")); - jar = jar.add(Cookie::new("station_id", id.to_string())); - pjar = pjar.add(Cookie::new("pw", code)); +) -> Markup { + let Some(station) = Station::find_by_id_and_code(&db, id, &code).await else { + let content = html! { + article class="error" { + "Falscher Quick-Einlogg-Link. Bitte nochmal scannen oder neu eingeben." + } + }; + return partials::page(content, session, false).await; + }; - return (jar, pjar, Redirect::to(&format!("/s/{id}"))); - } - } - err!( - session, - "Falscher Quick-Einlogg-Link. Bitte nochmal scannen oder deine Station manuell auswählen:" - ); - return (jar, pjar, Redirect::to("/s")); + let content = html! { + h1 { (format!("Station {}", station.name)) } + }; + + partials::page(content, session, false).await } pub(super) fn routes() -> Router { - Router::new() - .route("/", get(station_picker)) - .route("/{id}", get(view)) - .route("/code", get(code)) - .route("/{id}/{code}", get(quick_login)) - .route("/station-login", post(login)) - .route("/station-logout", get(logout)) + Router::new().route("/{id}/{code}", get(view)) }