From e62ff9aa7a418faa1f5c3e56238a8c784168be45 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sun, 22 Dec 2024 09:17:00 -0500 Subject: [PATCH] Check username and password --- Cargo.lock | 59 +++++++++++++++++++ visions/server/Cargo.toml | 1 + visions/server/src/core.rs | 9 +++ visions/server/src/database.rs | 55 +++++++++++++++++ visions/server/src/handlers.rs | 22 +++++++ visions/server/src/main.rs | 55 ++++++++++++----- visions/server/src/types.rs | 3 + visions/ui/src/App.tsx | 2 +- visions/ui/src/client.ts | 6 ++ .../providers/StateProvider/StateProvider.tsx | 9 +++ 10 files changed, 204 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ac8da4..075a08b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -932,6 +932,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1882,6 +1895,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.30" @@ -2049,6 +2068,17 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -2824,6 +2854,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3813,6 +3853,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.64" @@ -4294,6 +4343,7 @@ dependencies = [ "lazy_static", "mime", "mime_guess", + "pretty_env_logger", "result-extended", "rusqlite", "rusqlite_migration", @@ -4476,6 +4526,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/visions/server/Cargo.toml b/visions/server/Cargo.toml index 0d02396..c9339a8 100644 --- a/visions/server/Cargo.toml +++ b/visions/server/Cargo.toml @@ -15,6 +15,7 @@ include_dir = { version = "0.7.4" } lazy_static = { version = "1.5.0" } mime = { version = "0.3.17" } mime_guess = { version = "2.0.5" } +pretty_env_logger = "0.5.0" result-extended = { path = "../../result-extended" } rusqlite = { version = "0.32.1" } rusqlite_migration = { version = "1.3.1", features = ["from-directory"] } diff --git a/visions/server/src/core.rs b/visions/server/src/core.rs index 79fbda9..2593128 100644 --- a/visions/server/src/core.rs +++ b/visions/server/src/core.rs @@ -220,6 +220,15 @@ impl Core { Err(err) => fatal(err), } } + + pub async fn auth(&self, username: String, password: String) -> ResultExt { + let state = self.0.write().await; + match state.db.user_by_username(username).await { + Ok(Some(row)) if (row.password == password) => ok(row.id), + Ok(_) => error(AppError::AuthFailed), + Err(err) => fatal(err), + } + } } #[cfg(test)] diff --git a/visions/server/src/database.rs b/visions/server/src/database.rs index 25b27b7..9e3502e 100644 --- a/visions/server/src/database.rs +++ b/visions/server/src/database.rs @@ -26,6 +26,7 @@ enum Request { Charsheet(CharacterId), Games, User(UserId), + UserByUsername(String), Users, SaveUser(Option, String, String, bool, bool), } @@ -180,6 +181,8 @@ pub struct CharsheetRow { pub trait Database: Send + Sync { async fn user(&mut self, _: UserId) -> Result, FatalError>; + async fn user_by_username(&self, _: String) -> Result, FatalError>; + async fn save_user( &mut self, user_id: Option, @@ -290,6 +293,31 @@ impl DiskDb { } } + fn user_by_username(&self, username: String) -> Result, FatalError> { + let mut stmt = self + .conn + .prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE name=?") + .map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?; + let items: Vec = stmt + .query_map([username.as_str()], |row| { + Ok(UserRow { + id: row.get(0).unwrap(), + name: row.get(1).unwrap(), + password: row.get(2).unwrap(), + admin: row.get(3).unwrap(), + enabled: row.get(4).unwrap(), + }) + }) + .unwrap() + .collect::, rusqlite::Error>>() + .unwrap(); + match &items[..] { + [] => Ok(None), + [item] => Ok(Some(item.clone())), + _ => Err(FatalError::NonUniqueDatabaseKey(username.to_owned())), + } + } + fn users(&self) -> Result, FatalError> { let mut stmt = self .conn @@ -448,6 +476,13 @@ async fn db_handler(db: DiskDb, requestor: Receiver) { err => panic!("{:?}", err), } } + Request::UserByUsername(username) => { + let user = db.user_by_username(username); + match user { + Ok(user) => tx.send(DatabaseResponse::User(user)).await.unwrap(), + err => panic!("{:?}", err), + } + } Request::SaveUser(user_id, username, password, admin, enabled) => { let user_id = db.save_user(user_id, username.as_ref(), password.as_ref(), admin, enabled); match user_id { @@ -514,6 +549,26 @@ impl Database for DbConn { } } + async fn user_by_username(&self, username: String) -> Result, FatalError> { + let (tx, rx) = bounded::(1); + + let request = DatabaseRequest { + tx, + req: Request::UserByUsername(username), + }; + + match self.conn.send(request).await { + Ok(()) => (), + Err(_) => return Err(FatalError::DatabaseConnectionLost), + }; + + match rx.recv().await { + Ok(DatabaseResponse::User(user)) => Ok(user), + Ok(_) => Err(FatalError::MessageMismatch), + Err(_) => Err(FatalError::DatabaseConnectionLost), + } + } + async fn save_user( &mut self, user_id: Option, diff --git a/visions/server/src/handlers.rs b/visions/server/src/handlers.rs index 4392f3a..a3a3754 100644 --- a/visions/server/src/handlers.rs +++ b/visions/server/src/handlers.rs @@ -3,6 +3,7 @@ use std::future::Future; use futures::{SinkExt, StreamExt}; use result_extended::{error, ok, return_error, ResultExt}; use serde::{Deserialize, Serialize}; +use typeshare::typeshare; use warp::{http::Response, http::StatusCode, reply::Reply, ws::Message}; use crate::{ @@ -255,3 +256,24 @@ pub async fn handle_set_admin_password(core: Core, password: String) -> impl Rep }) .await } + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[typeshare] +pub struct AuthRequest { + username: String, + password: String, +} + +pub async fn handle_auth(core: Core, auth_request: AuthRequest) -> impl Reply { + handler(async move { + let userid = return_error!(core.auth(auth_request.username, auth_request.password).await); + + ok(Response::builder() + .header("Access-Control-Allow-Origin", "*") + .header("Access-Control-Allow-Methods", "*") + .header("Access-Control-Allow-Headers", "content-type") + .header("Content-Type", "application/json") + .body(serde_json::to_vec(&userid).unwrap()) + .unwrap()) + }).await +} diff --git a/visions/server/src/main.rs b/visions/server/src/main.rs index 894e2cc..f4e4fa4 100644 --- a/visions/server/src/main.rs +++ b/visions/server/src/main.rs @@ -8,10 +8,14 @@ use asset_db::{AssetId, FsAssets}; use authdb::AuthError; use database::DbConn; use handlers::{ - handle_available_images, handle_connect_websocket, handle_file, handle_get_charsheet, handle_get_users, handle_register_client, handle_server_status, handle_set_admin_password, handle_set_background_image, handle_unregister_client, RegisterRequest + handle_auth, handle_available_images, handle_connect_websocket, handle_file, + handle_get_charsheet, handle_get_users, handle_register_client, handle_server_status, + handle_set_admin_password, handle_set_background_image, handle_unregister_client, + RegisterRequest, }; use warp::{ // header, + filters::{method, path}, http::{Response, StatusCode}, reply::Reply, Filter, @@ -99,17 +103,16 @@ async fn handle_rejection(err: warp::Rejection) -> Result) => return ( { manager.setAdminPassword(password); - }} onAuth={(username, password) => console.log(username, password)}> + }} onAuth={(username, password) => manager.auth(username, password)}> {children} ); diff --git a/visions/ui/src/client.ts b/visions/ui/src/client.ts index 274ec77..8fa92f9 100644 --- a/visions/ui/src/client.ts +++ b/visions/ui/src/client.ts @@ -64,6 +64,12 @@ export class Client { return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify(password) }); } + async auth(username: string, password: string) { + const url = new URL(this.base); + url.pathname = `api/v1/auth` + return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify({ 'username': username, 'password': password }) }); + } + async status() { const url = new URL(this.base); url.pathname = `/api/v1/status`; diff --git a/visions/ui/src/providers/StateProvider/StateProvider.tsx b/visions/ui/src/providers/StateProvider/StateProvider.tsx index 8806702..e22ebe3 100644 --- a/visions/ui/src/providers/StateProvider/StateProvider.tsx +++ b/visions/ui/src/providers/StateProvider/StateProvider.tsx @@ -49,6 +49,15 @@ class StateManager { await this.client.setAdminPassword(password); await this.status(); } + + async auth(username: string, password: string) { + if (!this.client || !this.dispatch) return; + + let resp = await this.client.auth(username, password); + let userid = await resp.json(); + console.log("userid retrieved", userid); + this.dispatch({ type: "SetAuthState", content: { type: "Authed", userid } }); + } } export const StateContext = createContext<[AppState, StateManager]>([initialState(), new StateManager(undefined, undefined)]);