simple Log In endpoint without encryption

This commit is contained in:
2025-09-22 23:58:07 +02:00
parent 3450c216c0
commit 007071cf12
14 changed files with 680 additions and 60 deletions

View File

@@ -4,8 +4,9 @@ use tokio::net::TcpListener;
mod utils;
mod routes;
mod rooms;
use crate::utils::db_pool::HotelPools;
use r2d2::{Pool};
use r2d2_sqlite::SqliteConnectionManager;
use crate::utils::db_pool::{HotelPool,AppState};
use routes::create_router;
@@ -13,8 +14,22 @@ use routes::create_router;
#[tokio::main]
async fn main() -> std::io::Result<()> {
let hotel_pools = HotelPools::new();
let app = create_router(hotel_pools);
let hotel_pools = HotelPool::new();
let logs_manager = SqliteConnectionManager::file("db/auth.sqlite");
let logs_pool = Pool::builder()
.max_size(5)
.build(logs_manager)
.expect("Failed to build logs pool");
let state = AppState {
hotel_pools,
logs_pool,
jwt_secret: "your_jwt_secret_key s".to_string(), // better: load from env var
};
let app = create_router(state);
let listener = TcpListener::bind("0.0.0.0:3000").await?;
serve(listener, app).into_future().await?;
Ok(())

View File

@@ -3,13 +3,15 @@ use axum::response::IntoResponse;
use axum::http::StatusCode;
use crate::rooms::extractor::UpdateRoomPayload;
use crate::utils::db_pool::*;
use std::sync::Arc;
use r2d2::{Pool};
use r2d2_sqlite::SqliteConnectionManager;
use dashmap::DashMap;
use rusqlite::params;
use crate::utils::db_pool::*;
pub async fn hello_rooms() -> String {
@@ -29,14 +31,12 @@ pub async fn fake_room_update(
}
pub async fn fake_db_update(
State(hotel_pools): State<HotelPools>,
State(state): State<AppState>,
Path(room_id): Path<i32>,
UpdateRoomPayload(payload): UpdateRoomPayload,
) -> impl IntoResponse {
let pool = hotel_pools.get_pool(payload.hotel_id);
let pool = state.hotel_pools.get_pool(payload.hotel_id);
let conn = match pool.get() {
Ok(conn) => conn,
Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")),
@@ -57,23 +57,9 @@ pub async fn fake_db_update(
}
/*
//fake db handler
pub async fn update_room(UpdateRoomPayload(payload): UpdateRoom) -> impl IntoResponse {
match fake_db_update(&payload.token, payload.room_id, &payload.status).await {
Ok(msg) => msg,
Err(err) => format!("Error: {}", err),
}
struct RoomRequest {
item_id: i32,
item_amount: i32,
token: String,
}
async fn fake_db_update(token: &str, room_id: i32, status: &str) -> Result<String, String> {
// Pretend we check the token in the DB
if token != "valid_token" {
return Err("Invalid token".into());
}
// Pretend we update the room status in the DB
Ok(format!("Room {} updated to '{}'", room_id, status))
}
*/

View File

@@ -4,14 +4,18 @@ use axum::{
};
use crate::rooms::handler::*;
use crate::utils::db_pool::HotelPools;
use crate::utils::db_pool::{
HotelPool,
AppState,
};
// ROOTS
pub fn rooms_routes() -> Router<HotelPools> {
pub fn rooms_routes() -> Router {
Router::new()
.route("/", get(hello_rooms) )
.route("/fakeUpdate/{room_id}", put(fake_room_update))
.route("/fake_db_update/{room_id}", put(fake_db_update))
}

View File

@@ -1,24 +1,26 @@
use axum::{
Router,
};
use r2d2_sqlite::SqliteConnectionManager;
use r2d2::{Pool};
use crate::rooms::routes::rooms_routes;
use crate::utils::routes::utils_routes;
pub mod inventory;
use crate::utils::db_pool::HotelPools;
use crate::utils::db_pool::{AppState};
//TODO: add secret fomr dotenv here
pub fn create_router(state: AppState) -> Router {
pub fn create_router(hotel_pools: HotelPools) -> Router {
Router::new()
.nest("/rooms", rooms_routes())
.with_state(hotel_pools) // 👈 hotel_db is passed in as argument
.nest("/auth", utils_routes())
.with_state(state) // 👈 hotel_db is passed in as argument
}
/*
pub fn create_router() -> Router {
Router::new()
.nest("/rooms", rooms_routes())
//.nest("/inventory", inventory::inventory_routes)
}
*/

0
src/services/mod.rs Normal file
View File

227
src/utils/auth.rs Normal file
View File

@@ -0,0 +1,227 @@
use std::time::Duration;
use axum::{
body::{to_bytes, Body},
http::{Request as HttpRequest, StatusCode},
middleware::Next,
response::{Response, IntoResponse},
Json,
extract::{Path, State, FromRequest}
};
use axum::extract::Request as ExtractRequest;
use jsonwebtoken::{decode, DecodingKey, Validation, encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use chrono::{Utc};
use rusqlite::{params, Connection, OptionalExtension};
//use crate::utils::db_pool::;
use crate::utils::db_pool::{
HotelPool,
AppState,
};
pub async fn auth_middleware(
mut req: HttpRequest<Body>,
next: Next,
) -> Result<Response, StatusCode> {
// buffer body
let body = std::mem::take(req.body_mut());
// Buffer into bytes
let bytes = to_bytes(body, 1024 * 1024)
.await
.map_err(|_| StatusCode::BAD_REQUEST)?;
// parse JSON as generic `Value`
let value: Value = serde_json::from_slice(&bytes)
.map_err(|_| StatusCode::BAD_REQUEST)?;
// pull out token
let token = value
.get("token")
.and_then(|t| t.as_str())
.ok_or(StatusCode::UNAUTHORIZED)?;
// verify token
let key = DecodingKey::from_secret("your_jwt_secret_key".as_ref());
let validation = Validation::default();
let claims = decode::<Claims>(token, &key, &validation)
.map_err(|_| StatusCode::UNAUTHORIZED)?
.claims;
// inject claims for downstream handlers
req.extensions_mut().insert(claims);
// restore the original body so the handler can parse it into *its own* struct
req = req.map(|_| Body::from(bytes));
Ok(next.run(req).await)
}
#[derive(Deserialize, Debug)]
pub struct LoginValues {
username : String,
password : String,
hotel_id: i32,
}
pub struct LoginPayload(pub LoginValues);
impl<S> FromRequest<S> for LoginPayload
where S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: ExtractRequest, state: &S) -> Result<Self, Self::Rejection> {
let Json(payload) = Json::<LoginValues>::from_request(req, state)
.await
.map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?;
Ok(LoginPayload(payload))
}
}
#[derive(Deserialize,Debug, Serialize, Clone)]
struct Claims{
id: i32,
hotel_id: i32,
//display_name
username: String,
exp: usize,
}
#[derive(Serialize)]
struct LoginResponse {
token: String,
}
//pub async fn auth_register();
/*
pub async fn auth_loggin(
// State(hotel_pools): State<HotelPools>,
LoginPayload(payload): LoginPayload,
) -> impl IntoResponse {
let pool = .get_pool(payload.hotel_id);
let conn = pool.get().unwrap();
/*
let conn = match pool.get() {
Ok(conn) => conn,
Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")),
};
*/
let mut statement = conn
.prepare("SELECT id, displayname, hotel_id FROM users WHERE username = ? 1 AND password = ?2 ")
.unwrap();
let user_row = statement
.query_row(params![&payload.username, &payload.password], |row| {
let id: i32 = row.get(0)?;
let displayname: String = row.get(1)?;
let hotel_id: i32 = row.get(2)?;
Ok((id, displayname, hotel_id))
})
.optional()
.unwrap(); // returns Ok(Some(...)) or Ok(None)
if let Some((id, displayname, hotel_id)) = user_row {
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::minutes(15))
.unwrap()
.timestamp() as usize;
let claims = Claims {
id,
hotel_id,
username: displayname, // or payload.username if you prefer
exp: expiration,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret("TEST".as_bytes()),
)
.unwrap();
return (StatusCode::OK, Json(LoginResponse { token }));
}
// Fallback if login failed — wrap String in Json<LoginResponse> or use axum::Json
return (StatusCode::UNAUTHORIZED, Json(LoginResponse { token: "".to_string() }));
}
*/
pub async fn clean_auth_loging(
State(state): State<AppState>,
LoginPayload(payload): LoginPayload
) -> impl IntoResponse {
// 1⃣ Get a connection from logs pool
let conn = match state.logs_pool.get() {
Ok(c) => c,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(),
};
let user_row = match conn.query_row(
"SELECT id, password, hotel_id, displayname FROM users WHERE username = ?1",
params![&payload.username],
|row| {
let user_id: i32 = row.get(0)?;
let password: String = row.get(1)?;
let hotel_id: i32 = row.get(2)?;
let displayname: String = row.get(3)?;
Ok((user_id, password, hotel_id, displayname))
},
).optional() {
Ok(opt) => opt,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(),
};
let (user_id, stored_password, hotel_id, displayname) = match user_row {
Some(u) => u,
None => return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(),
};
if payload.password != stored_password {
return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response();
}
let expiration = chrono::Utc::now()
.checked_add_signed(chrono::Duration::hours(15))
.unwrap()
.timestamp() as usize;
let claims = serde_json::json!({
"id": user_id,
"hotel_id": payload.hotel_id,
"username": payload.username,
"exp": expiration
});
let token = match encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(state.jwt_secret.as_ref()),
) {
Ok(t) => t,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(),
};
Json(LoginResponse { token }).into_response()
}
fn internal_error<E: std::fmt::Display>(err: E) -> (StatusCode, String) {
(StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err))
}
/*
match result {
Ok(rows) if rows > 0 => (StatusCode::OK, format!("Logged")),
Ok(_) => (StatusCode::NOT_FOUND, "No used".to_string()),
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {err}")),
}
*/

View File

@@ -6,30 +6,38 @@ use r2d2_sqlite::SqliteConnectionManager;
type HotelId = i32; // or i32 if you want numeric ids
#[derive(Clone)]
pub struct HotelPools {
pools: Arc<DashMap<HotelId, Pool<SqliteConnectionManager>>>,
pub struct AppState {
pub hotel_pools: HotelPool,
pub logs_pool: Pool<SqliteConnectionManager>,
pub jwt_secret: String
}
impl HotelPools {
#[derive(Clone)]
pub struct HotelPool {
hotel_pools: Arc<DashMap<HotelId, Pool<SqliteConnectionManager>>>,
}
impl HotelPool {
pub fn new() -> Self {
Self {
pools: Arc::new(DashMap::new()),
hotel_pools: Arc::new(DashMap::new()),
}
}
pub fn get_pool(&self, hotel_id: i32) -> Pool<SqliteConnectionManager> {
if let Some(pool) = self.pools.get(&hotel_id) {
if let Some(pool) = self.hotel_pools.get(&hotel_id) {
return pool.clone();
}
let db_path = format!("db/{}.sqlite", hotel_id);
let manager = SqliteConnectionManager::file(db_path);
let pool = Pool::builder()
let hotels_manager = SqliteConnectionManager::file(db_path);
let db_pool = Pool::builder()
.max_size(5) // adjust based on load
.build(manager)
.build(hotels_manager)
.expect("Failed to build pool");
self.pools.insert(hotel_id, pool.clone());
pool
self.hotel_pools.insert(hotel_id, db_pool.clone());
db_pool
}
}

View File

@@ -1 +1,3 @@
pub mod db_pool;
pub mod db_pool;
pub mod auth;
pub mod routes;

16
src/utils/routes.rs Normal file
View File

@@ -0,0 +1,16 @@
use axum::{
routing::{get, put, post},
Router,
};
use crate::utils::auth::*;
use crate::utils::db_pool::{HotelPool, AppState};
// ROOTS
pub fn utils_routes() -> Router<AppState> {
Router::new()
.route("/login", put(clean_auth_loging))
//.with_state(state)
}