From c937ae8d83a5c5bba3a999ca0086fe835d447d10 Mon Sep 17 00:00:00 2001 From: Romain Mallard Date: Sat, 11 Oct 2025 08:25:20 +0200 Subject: [PATCH] added refresh token generator --- Cargo.lock | 96 ++++++++++++++++++++++++++++++++----- Cargo.toml | 7 ++- db/auth.sqlite | Bin 28672 -> 36864 bytes db/auth.sqlite-shm | Bin 32768 -> 32768 bytes db/auth.sqlite-wal | Bin 8272 -> 20632 bytes src/note_endpoint_json.txt | 51 ++++++++++++++++++++ src/utils/auth.rs | 85 ++++++++++++++++++++++++++++++-- src/utils/routes.rs | 5 ++ 8 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 src/note_endpoint_json.txt diff --git a/Cargo.lock b/Cargo.lock index 26490ad..90a3175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,11 +58,12 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" +checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" dependencies = [ "axum-core", + "axum-macros", "base64", "bytes", "form_urlencoded", @@ -78,8 +79,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" +checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", @@ -106,13 +106,47 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "axum-extra" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "headers", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde_core", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -244,6 +278,17 @@ dependencies = [ "inout", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -478,6 +523,30 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + [[package]] name = "hotel-api-rs" version = "0.1.0" @@ -485,11 +554,14 @@ dependencies = [ "anyhow", "argon2", "axum", + "axum-extra", + "base64", "bcrypt", "chrono", "dashmap", "dotenvy", "futures-util", + "headers", "jsonwebtoken", "r2d2", "r2d2_sqlite", @@ -498,6 +570,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "uuid", ] [[package]] @@ -1247,9 +1320,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", @@ -1307,9 +1380,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.26.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", @@ -1355,6 +1428,7 @@ dependencies = [ "getrandom 0.3.3", "js-sys", "rand", + "serde", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 866cadf..14b8a3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,10 @@ edition = "2024" publish = false [dependencies] -axum = {version = "0.8.4", features = ["ws"]} +axum = {version = "0.8.6", features = ["ws", "macros"]} +axum-extra = { version = "0.10.3", features = ["typed-header", "cookie"] } +headers = "0.4" + tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -24,6 +27,8 @@ anyhow = "1.0.100" argon2 = {version = "0.5.3"} rand_core = {version = "0.6.4", features = ["getrandom"]} futures-util = {version = "0.3.31"} +uuid = {version = "1.18.1", features = ["serde"] } +base64 = "0.22.1" diff --git a/db/auth.sqlite b/db/auth.sqlite index e189df3412b6493f2e09ac7eb3b8a7d965eb0f0a..61c0241f4b3781751d97a11754368b13b9125d66 100644 GIT binary patch delta 665 zcmZp8z}T>WX@ayMI|Bm)ClJE`(?lI(S#}1!E)ib-9}FBkCm8s$`Ooq%;xFg-;gjS! z!Fy+8qYqDglOQ|0xTYp!b8JasQch}7YFbfhaYlSees*eJF)EM4Imp#9#8n~0(aFbE z0aZ>xgG)gnGesfTHOSM^MEW6=eoNQO*`=F7CNiQAS11 z#-_%`?!FN@=~bptg+WnefPvkoS zw0;{OYbr~lKL^NCcgihI1DeGMGHbG*|4NXxoST39vnsIhg3_A6X2FCf{1YdLv2mer E0Yj6;dH?_b delta 141 zcmZozz|`=7ae}lU8v_FaI}pPF<3t@}Nj3((E)ib-9}H}Kk_>#={Ac+W@t5=a@JVhK z6j;c+S(5JsBQH<`Ge08(|1b=tT=h?$XbIte5sZd~BFe*l4yxNzgl1&IUG#DUvRaM)o7#P-@Ia_EA-WwZ&(R3bDzEt3g2gflXscZ z*TO%BZ-uXi#nUfO-VNOuHW>X7fB*y_009U<00Izz00jO{VE0TQ!0|kL^F3Q#Z|GgU z*43>I#co~GP0OkE+m8ZX84&O|Jb1Av{7SA^ zCd)#3mUi?ky{pjPd_fj22}M$nWKt-JqSNQ@in5_v8$=ea()L4rn!aUJb!X(UE_X_` zuABDpzOKGSk53~Dk|YYb0$CK6awSnFOF3~(nBdwJ##h|73Yo4LTIE3U5H!h^WGP>ugI0usOiG3PYDsv^N!$sXPp#~U9S>-$mUX?= zrPI6I#{)FOYB$sy|2WA;%hnt8lpX9N&yV@JEH}v8JuD^VzWDjk;h{-4bc`Pv9-44N zM|t1y(6}4wT+2Jx_fGNT1>X4X`v8nLynvGvc=EhJ=#(cf5ITjtfO}T3fdB*` z009U<00Izz00d4f@Gs;A`h~sB!S0o3BXfYfz@W>15|x?W&qYS8^_&t4x{E--Fky}1`&K`KVUXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD&@u!{8Q2*l z8~vp@i}G_5GxJmui_-J+j51SH%4`iSRdRuZiK&@RiLH@NfvurRL3u$)l6Og{lb>5z zSyn(wX@I_GN}x(co|~6jXmDmmKt-8BP?WPpnu~jGRg_VYv$3hMvAb_XPI{GTRAEq5 zS$R-?Zf0J7X#o>EgJ`3_D9Bo40+t5kmKA#^mbg?En5B9Jc=~3zl@xiTs}uxOC6*bu z>IatO6&V$!Of`XO&l`6&vUIMmm*68fBO|f-EUyWM>cu*%+B$nqNx5 z%FGCh2sg(<=bQ-Fh-5?W(8{o|)Ko_m<3RHg{iMLm!W37tl(b6Y2yItwXKx>m$RsaI zi$d+Js>smLNF&R%O5bFll}5mLladsN1#Myq(C1L^17%o&m>o7=5Fx!#ZrL7}SzP?z zf#U*482GjLCjzNaFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0)sXLQdt`P mISBNxDIZ5j1KPq!z!u5}4wyim;Ur)U<%0#lxa0&64*&qL*!Tkg diff --git a/src/note_endpoint_json.txt b/src/note_endpoint_json.txt new file mode 100644 index 0000000..73acf09 --- /dev/null +++ b/src/note_endpoint_json.txt @@ -0,0 +1,51 @@ +#[derive(Deserialize, Debug)] +pub struct GetMessagesValues { + pub conv_id: u32, + pub timestamp: Option, +} + + +pub async fn get_message( + State(state): State, + AuthClaims { user_id, hotel_id, username }: AuthClaims, + Json(payload): Json, // ✅ no custom payload wrapper +) -> Result { + + let pool = state.hotel_pools.get_pool(hotel_id); + + let conn = pool.get() + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Pool error".to_string()))?; + + let from_time = match payload.timestamp.as_deref() { + Some("0") | None => "1970-01-01 00:00:00", + Some(ts) => ts, + }; + + let mut stmt = conn.prepare( + "SELECT id, sender_id, content, sent_at + FROM message + WHERE conversation_id = ?1 + AND sent_at > ?2 + ORDER BY sent_at DESC + LIMIT 50" + ).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Prepare failed".to_string()))?; + + let messages = stmt.query_map( + params![payload.conv_id, from_time], + |row| { + Ok(Message { + id: row.get(0)?, + sender_id: row.get(1)?, + content: row.get(2)?, + sent_at: row.get(3)?, + }) + } + ) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? + .collect::, _>>() + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; + + +} + + diff --git a/src/utils/auth.rs b/src/utils/auth.rs index d5b7cae..2689d2a 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -1,13 +1,19 @@ use std::time::Duration; use axum::{ body::{to_bytes, Body}, - http::{Request as HttpRequest, StatusCode, }, + http::{Request as HttpRequest, StatusCode, header::{SET_COOKIE, HeaderValue} }, http::request::Parts, middleware::Next, response::{Response, IntoResponse}, Json, extract::{Path, State, FromRequest, FromRequestParts, Extension} }; + +use axum_extra::extract::TypedHeader; +//use axum_extra::TypedHeader; + +use headers::UserAgent; + use axum::extract::FromRef; use axum::extract::Request as ExtractRequest; use jsonwebtoken::{decode, DecodingKey, Validation, encode, EncodingKey, Header, Algorithm}; @@ -16,14 +22,19 @@ use serde_json::Value; use chrono::{Utc}; use rusqlite::{params, Connection, OptionalExtension}; -use rand_core::OsRng; +use rand_core::{RngCore, OsRng}; use argon2::{ password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2}; -//use crate::utils::db_pool::; +use uuid::Uuid; + + //use crate::utils::db_pool::; use crate::utils::db_pool::{HotelPool,AppState}; +use base64::{engine::general_purpose, Engine as _}; + + #[derive(Clone)] pub struct JwtKeys { pub encoding: EncodingKey, @@ -386,9 +397,77 @@ pub async fn clean_auth_loging( Json(LoginResponse { token }).into_response() } +#[derive(Deserialize, Debug)] +pub struct CreateRefreshTokenValue { + pub username: String, + pub password: String, + pub device_id: Uuid, + pub timestamp: Option, +} +#[axum::debug_handler] +pub async fn create_refresh_token( + State(state): State, + user_agent: Option>, + Json(payload): Json +) -> Result { // ← Add Result here + let user_agent_str = user_agent + .map(|ua| ua.to_string()) + .unwrap_or_else(|| "Unknown".to_string()); + + let device_id_str = payload.device_id.to_string(); + + let argon2 = Argon2::default(); + let salt = SaltString::generate(&mut OsRng); + let mut bytes = [0u8; 64]; + OsRng.fill_bytes(&mut bytes); + + let hashed_token = argon2 + .hash_password(&bytes, &salt) + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .to_string(); + + let raw_token = general_purpose::STANDARD.encode(&bytes); + + let conn = state.logs_pool.get() + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?; + + let user_row = conn.query_row( + "SELECT id, password FROM users WHERE username = ?1", + params![&payload.username], + |row| { + let user_id: i32 = row.get(0)?; + let password: String = row.get(1)?; + Ok((user_id, password)) + }, + ).optional() + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB get user id error".to_string()))?; + + let (user_id, stored_hash) = user_row + .ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?; + + if !verify_password(&payload.password, &stored_hash) { + return Err((StatusCode::UNAUTHORIZED, "Invalid credentials".to_string())); + } + + conn.execute( + "INSERT INTO refresh_token (user_id, token_hash, device_id, user_agent) VALUES (?1, ?2, ?3, ?4)", + params![user_id, hashed_token, device_id_str, user_agent_str], + ) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB insert error".to_string()))?; + + let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Path=/", raw_token); + + let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response(); + response.headers_mut().insert( + SET_COOKIE, + HeaderValue::from_str(&cookie_value).unwrap(), + ); + + Ok(response) // ← Wrap in Ok() +} fn internal_error(err: E) -> (StatusCode, String) { diff --git a/src/utils/routes.rs b/src/utils/routes.rs index c451268..9e00831 100644 --- a/src/utils/routes.rs +++ b/src/utils/routes.rs @@ -6,6 +6,9 @@ use axum::{ use crate::utils::auth::*; use crate::utils::db_pool::{HotelPool, AppState, }; use crate::utils::websocket::ws_handler; +use headers::UserAgent; +use axum_extra::TypedHeader; + // ROOTS pub fn utils_routes() -> Router { @@ -17,6 +20,8 @@ pub fn utils_routes() -> Router { .route("/tokentest", put(token_tester)) .route("/force_update_password", put(ForceUpdatePassword)) .route("/update_password", put(UpdatePassword)) + + .route("/create_refresh", post(create_refresh_token)) //.with_state(state) } \ No newline at end of file