From 793e136087760483ce3b121fe2f01febb0983db5 Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 13:20:10 +0100 Subject: [PATCH 01/10] basic backend auth fr --- backend/Cargo.lock | 59 ++++++++++++++++++++++ backend/Cargo.toml | 3 +- backend/src/auth.rs | 36 +++++++++++++ backend/src/db.rs | 52 ++++++++++++++++++- backend/src/handlers.rs | 109 +++++++++++++++++++++++++++------------- backend/src/main.rs | 15 +++--- 6 files changed, 228 insertions(+), 46 deletions(-) create mode 100644 backend/src/auth.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 194ebb2..a09a176 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -543,6 +543,7 @@ dependencies = [ "actix-files", "actix-web", "env_logger", + "futures", "log", "rusqlite", "serde", @@ -550,12 +551,65 @@ dependencies = [ "uuid", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -574,8 +628,13 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 900be72..fead35d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -12,4 +12,5 @@ uuid = { version = "1.0", features = ["v4"] } actix-files = "0.6" actix-cors = "0.6" env_logger = "0.10" # Check for the latest version -log = "0.4" \ No newline at end of file +log = "0.4" +futures = "0.3" \ No newline at end of file diff --git a/backend/src/auth.rs b/backend/src/auth.rs new file mode 100644 index 0000000..ca2f9d7 --- /dev/null +++ b/backend/src/auth.rs @@ -0,0 +1,36 @@ +use actix_web::{dev::Payload, http::header::AUTHORIZATION, web, Error, FromRequest, HttpRequest}; +use futures::future::{ready, Ready}; +use rusqlite::Connection; +use std::sync::{Arc, Mutex}; + +pub struct Auth { + pub user_id: String, +} + +impl FromRequest for Auth { + type Error = Error; + type Future = Ready>; + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let db = req + .app_data::>>>() + .expect("Database connection missing"); + + if let Some(auth_header) = req.headers().get(AUTHORIZATION) { + if let Ok(auth_str) = auth_header.to_str() { + if auth_str.starts_with("Bearer ") { + let token = &auth_str[7..]; + let conn = db.lock().unwrap(); + + match super::db::validate_token(&conn, token) { + Ok(Some(user_id)) => return ready(Ok(Auth { user_id })), + Ok(None) | Err(_) => { + return ready(Err(actix_web::error::ErrorUnauthorized("Invalid token"))) + } + } + } + } + } + ready(Err(actix_web::error::ErrorUnauthorized("Missing token"))) + } +} diff --git a/backend/src/db.rs b/backend/src/db.rs index c00758a..1ba665a 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -1,4 +1,4 @@ -use rusqlite::{Connection, Result}; +use rusqlite::{params, Connection, OptionalExtension, Result}; pub fn init_db() -> Result { let conn = Connection::open("form_data.db")?; @@ -24,5 +24,55 @@ pub fn init_db() -> Result { [], )?; + // Add a table for users + conn.execute( + "CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, -- Store a hashed password + token TEXT UNIQUE -- Optional: For token-based auth + )", + [], + )?; + Ok(conn) } + +// Add a function to validate a token +pub fn validate_token(conn: &Connection, token: &str) -> Result> { + let mut stmt = conn.prepare("SELECT id FROM users WHERE token = ?1")?; + let user_id: Option = stmt + .query_row(params![token], |row| row.get(0)) + .optional()?; + Ok(user_id) +} + +// Add a function to authenticate users (for login) +pub fn authenticate_user( + conn: &Connection, + username: &str, + password: &str, +) -> Result> { + let mut stmt = conn.prepare("SELECT id, password FROM users WHERE username = ?1")?; + let mut rows = stmt.query(params![username])?; + + if let Some(row) = rows.next()? { + let user_id: String = row.get(0)?; + let stored_password: String = row.get(1)?; + + // Replace this with a secure password hashing and verification mechanism + if stored_password == password { + return Ok(Some(user_id)); + } + } + Ok(None) +} + +// Add a function to generate and save a token for a user +pub fn generate_token_for_user(conn: &Connection, user_id: &str, token: &str) -> Result<()> { + conn.execute( + "UPDATE users SET token = ?1 WHERE id = ?2", + params![token, user_id], + )?; + Ok(()) +} diff --git a/backend/src/handlers.rs b/backend/src/handlers.rs index 4d03648..db5f2ec 100644 --- a/backend/src/handlers.rs +++ b/backend/src/handlers.rs @@ -1,17 +1,73 @@ use actix_web::{web, HttpResponse, Responder}; use rusqlite::{params, Connection}; +use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use uuid::Uuid; -use crate::models::{Form, Submission}; +use crate::auth::Auth; -// Create a new form +// Structs for requests and responses +#[derive(Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Serialize)] +pub struct LoginResponse { + pub token: String, +} + +// Public: Login handler +pub async fn login( + db: web::Data>>, + login_request: web::Json, +) -> impl Responder { + let conn = db.lock().unwrap(); + let user_id = + match crate::db::authenticate_user(&conn, &login_request.username, &login_request.password) + { + Ok(Some(user_id)) => user_id, + Ok(None) => return HttpResponse::Unauthorized().body("Invalid username or password"), + Err(_) => return HttpResponse::InternalServerError().body("Database error"), + }; + + let token = Uuid::new_v4().to_string(); + if let Err(_) = crate::db::generate_token_for_user(&conn, &user_id, &token) { + return HttpResponse::InternalServerError().body("Failed to generate token"); + } + + HttpResponse::Ok().json(LoginResponse { token }) +} + +// Public: Submit a form +pub async fn submit_form( + db: web::Data>>, + path: web::Path, + submission: web::Form, +) -> impl Responder { + let conn = db.lock().unwrap(); + let submission_id = Uuid::new_v4().to_string(); + let form_id = path.into_inner(); + let submission_json = serde_json::to_string(&submission.into_inner()).unwrap(); + + match conn.execute( + "INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)", + params![submission_id, form_id, submission_json], + ) { + Ok(_) => HttpResponse::Ok().json(submission_id), + Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), + } +} + +// Protected: Create a new form pub async fn create_form( db: web::Data>>, - form: web::Json
, + auth: Auth, + form: web::Json, ) -> impl Responder { - println!("Received form: {:?}", form); - let conn = db.lock().unwrap(); // Lock the Mutex to access the database + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let form_id = Uuid::new_v4().to_string(); let form_json = serde_json::to_string(&form.fields).unwrap(); @@ -24,9 +80,10 @@ pub async fn create_form( } } -// Get all forms -pub async fn get_forms(db: web::Data>>) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database +// Protected: Get all forms +pub async fn get_forms(db: web::Data>>, auth: Auth) -> impl Responder { + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") { Ok(stmt) => stmt, @@ -39,41 +96,22 @@ pub async fn get_forms(db: web::Data>>) -> impl Responder let name: String = row.get(1)?; let fields: String = row.get(2)?; let fields = serde_json::from_str(&fields).unwrap(); - Ok(Form { id, name, fields }) + Ok(crate::models::Form { id, name, fields }) }) .unwrap(); - let forms: Vec = forms_iter.filter_map(|f| f.ok()).collect(); + let forms: Vec = forms_iter.filter_map(|f| f.ok()).collect(); HttpResponse::Ok().json(forms) } -// Submit a form -pub async fn submit_form( - db: web::Data>>, - path: web::Path, - submission: web::Form, -) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database - let submission_id = Uuid::new_v4().to_string(); - let form_id = path.into_inner(); - - let submission_json = serde_json::to_string(&submission.into_inner()).unwrap(); - - match conn.execute( - "INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)", - params![submission_id, form_id, submission_json], - ) { - Ok(_) => HttpResponse::Ok().json(submission_id), - Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), - } -} - -// Get submissions for a form +// Protected: Get submissions for a form pub async fn get_submissions( db: web::Data>>, + auth: Auth, path: web::Path, ) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let form_id = path.into_inner(); let mut stmt = @@ -88,10 +126,11 @@ pub async fn get_submissions( let form_id: String = row.get(1)?; let data: String = row.get(2)?; let data = serde_json::from_str(&data).unwrap(); - Ok(Submission { id, form_id, data }) + Ok(crate::models::Submission { id, form_id, data }) }) .unwrap(); - let submissions: Vec = submissions_iter.filter_map(|s| s.ok()).collect(); + let submissions: Vec = + submissions_iter.filter_map(|s| s.ok()).collect(); HttpResponse::Ok().json(submissions) } diff --git a/backend/src/main.rs b/backend/src/main.rs index 341c4cc..f42dc39 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,22 +1,19 @@ use actix_cors::Cors; -// use actix_files as fs; use actix_web::{web, App, HttpServer}; use std::sync::{Arc, Mutex}; +mod auth; mod db; mod handlers; mod models; #[actix_web::main] async fn main() -> std::io::Result<()> { - // Initialize the database connection env_logger::init(); - let db = Arc::new(Mutex::new( db::init_db().expect("Failed to initialize the database"), )); - // Start the Actix-Web server HttpServer::new(move || { App::new() .wrap( @@ -26,16 +23,16 @@ async fn main() -> std::io::Result<()> { .allow_any_method(), ) .app_data(web::Data::new(db.clone())) - // .service(fs::Files::new("/", "./frontend/public").index_file("index.html")) - .route("/forms", web::post().to(handlers::create_form)) - .route("/forms", web::get().to(handlers::get_forms)) + .route("/login", web::post().to(handlers::login)) // Public: Login .route( "/forms/{id}/submissions", - web::post().to(handlers::submit_form), + web::post().to(handlers::submit_form), // Public: Submit form ) + .route("/forms", web::post().to(handlers::create_form)) // Protected + .route("/forms", web::get().to(handlers::get_forms)) // Protected .route( "/forms/{id}/submissions", - web::get().to(handlers::get_submissions), + web::get().to(handlers::get_submissions), // Protected ) }) .bind("127.0.0.1:8080")? From 1b21ec4f32a6b824c8b6fda236ae4e94043c9daa Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 13:20:27 +0100 Subject: [PATCH 02/10] added basic auth --- frontend/src/lib/api.ts | 50 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9f62858..99e3efe 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,6 +1,15 @@ -// api.ts const API_BASE_URL = 'http://127.0.0.1:8080'; +// A simple function to retrieve the token from local storage or wherever it is stored +function getAuthToken(): string | null { + return localStorage.getItem('auth_token'); // Assuming the token is stored in localStorage +} + +// A simple function to save the token +function setAuthToken(token: string): void { + localStorage.setItem('auth_token', token); +} + /** * Create a new form. * @param name The name of the form. @@ -8,10 +17,12 @@ const API_BASE_URL = 'http://127.0.0.1:8080'; * @returns The ID of the created form. */ export async function createForm(name: string, fields: unknown): Promise { + const token = getAuthToken(); // Get the token from storage const response = await fetch(`${API_BASE_URL}/forms`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers }, body: JSON.stringify({ name, fields }) }); @@ -28,10 +39,12 @@ export async function createForm(name: string, fields: unknown): Promise * @returns An array of forms. */ export async function getForms(): Promise { + const token = getAuthToken(); // Get the token from storage const response = await fetch(`${API_BASE_URL}/forms`, { method: 'GET', headers: { - Accept: 'application/json' + Accept: 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers } }); @@ -49,10 +62,12 @@ export async function getForms(): Promise { * @returns The ID of the created submission. */ export async function submitForm(formId: string, data: unknown): Promise { + const token = getAuthToken(); // Get the token from storage const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers }, body: JSON.stringify(data) }); @@ -70,10 +85,12 @@ export async function submitForm(formId: string, data: unknown): Promise * @returns An array of submissions for the form. */ export async function getSubmissions(formId: string): Promise { + const token = getAuthToken(); // Get the token from storage const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, { method: 'GET', headers: { - Accept: 'application/json' + Accept: 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers } }); @@ -83,3 +100,26 @@ export async function getSubmissions(formId: string): Promise { return await response.json(); } + +/** + * Login and get the authentication token. + * @param username The username. + * @param password The password. + * @returns The authentication token. + */ +export async function login(username: string, password: string): Promise { + const response = await fetch(`${API_BASE_URL}/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + + if (!response.ok) { + throw new Error(`Error logging in: ${response.statusText}`); + } + + const { token } = await response.json(); + setAuthToken(token); // Store the token in localStorage +} From 8b1252292479c8376df2d409753f9900d6ab6b04 Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 13:20:36 +0100 Subject: [PATCH 03/10] test db --- backend/form_data.db | Bin 20480 -> 45056 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/backend/form_data.db b/backend/form_data.db index 663f6585599331c9f5ca034a0765c8b3a084bff7..08cd32ae2c9c953b0f3a2c9aa4e3c39ecbf40a29 100644 GIT binary patch delta 941 zcma)5&ui0A98YJPZfVnJ5$s{sJ=+~t?3?-BwIDd9!k}zyHtN9b z-NSm@-1W?^7k;MiY}A*Vb=X|K-Kay7@;|fubK*F-W5QyFL4irc`45uiy#@1!-bCWUg3@4ZLtO z*yywzEi6S`dSXkF0$ezog3L|&$lCnx?A)WVT#d+|+|F53Nv7%CN-;*Y9K(F-J#M#p zrWS~E!&r;_N#i3ya(KW#naU(sngaRE8}@|lv-I)ccY4uHB-BeCu_{|!#jrTECF>TF zlqCh}eBkl2QWbbC%Ze!&eb1l0I=5~ag>r#RU8ofT|JkO%2{Ph%#0iiWYMfk?IVYY_ zvmFsvu`J5G6fg=VlB!z*!a~JF=8_~TmQvBNqT73fb>80l%NqD0tAuy~i5%pmn#k8U zWq6X$2P%L>fp6d)kiaH*HO#^SxW8~gjh#nBf&kAmRh$6*U-|jr+Z!8@X2~16ILzMq T1da|xn!Kz?`2XrZ;K}e0pGpIT delta 113 zcmZp8z|^pSae}lU8v_FaD-go~(?lI(Q8os>v{$_RKNwhe`xy9s@So-F<2$%nQ9+7# zb02RR6Bje@a|V7{zSX?Xfr2-AH$Rt^W#VAwZ)4!U!M}d9fPxDD#0g<68qA@LlRwC+ KY+k1yqyPZQXdPVu From 25db5f108d136d9a43461caf42f0771dd9197dd1 Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:32:59 +0100 Subject: [PATCH 04/10] deps+test db --- backend/Cargo.lock | 63 ++++++++++++++++++++++++++++++++++++++++++- backend/Cargo.toml | 6 +++-- backend/form_data.db | Bin 45056 -> 45056 bytes 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index a09a176..5cffb71 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -68,7 +68,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash", - "base64", + "base64 0.22.1", "bitflags", "brotli", "bytes", @@ -281,6 +281,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "autocfg" version = "1.4.0" @@ -302,12 +308,30 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bcrypt" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641" +dependencies = [ + "base64 0.13.1", + "blowfish", + "getrandom", + "zeroize", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -323,6 +347,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "brotli" version = "6.0.0" @@ -382,6 +416,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[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 = "convert_case" version = "0.4.0" @@ -542,6 +586,8 @@ dependencies = [ "actix-cors", "actix-files", "actix-web", + "anyhow", + "bcrypt", "env_logger", "futures", "log", @@ -907,6 +953,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -1723,6 +1778,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fead35d..4796d48 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,6 +11,8 @@ serde_json = "1.0" uuid = { version = "1.0", features = ["v4"] } actix-files = "0.6" actix-cors = "0.6" -env_logger = "0.10" # Check for the latest version +env_logger = "0.10" log = "0.4" -futures = "0.3" \ No newline at end of file +futures = "0.3" +bcrypt = "0.13" +anyhow = "1.0" \ No newline at end of file diff --git a/backend/form_data.db b/backend/form_data.db index 08cd32ae2c9c953b0f3a2c9aa4e3c39ecbf40a29..a6446aa6e2ea2e281fea1054b06af824e0ee49ae 100644 GIT binary patch delta 505 zcma)&Jxe1|6ozLK9CS4~5e18w7>6k=yvf{;N$yP*FrcD~5t%|z@Xp*B6D1Ls9kK{P z(^xC{3l@Tf==>QA3%jkhPA3~}EVS@+=kW4A59i_~aq*J4%IfLs`4v6=aBu?|V@hg} zOW&pbrn;%o5-(cRxeFoY5 z!t9aYQPW&DJF*>^RkIbDz2(7tK418_nZ12t69?HC*`$DJmGCMNv5|XCWDJyNp%l+sr$M9NV% zV&TZbD?0>X5eP0xsDvO=wObvFcortsM#OehdCT7}Hcsnewc>|ido9}heX^;d?oO#2 z)En;lNtYjU@~eDS`g2%s?#hbqvC)Gl;uI6aR4^kEmkviZ<)q9UjJ-fS)}EpG9i1PV crs*)$U>Hv~{=vj}a&F>X@vXu7#QFmDADt|Q&;S4c delta 89 zcmZp8z|`=7X@WE(*F+g-My`zsOZfR%dD$5FW%*X~KIbjxW!tQ%@P%hHo9sU(K30Ac t2L2oT>-kOiWBJ=Q3mPc#vuH4fGEU}|RoNV)zrtZNOTZubMF|2x1OP@Z7|;L! From a30f14a0d4de17dbb1cf77b83f1a38aa5380153a Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:33:49 +0100 Subject: [PATCH 05/10] added - better error msgs - default user --- backend/src/db.rs | 70 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/backend/src/db.rs b/backend/src/db.rs index 1ba665a..879c995 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -1,7 +1,10 @@ -use rusqlite::{params, Connection, OptionalExtension, Result}; +use anyhow::{Context, Result as AnyhowResult}; +use bcrypt::{hash, verify, DEFAULT_COST}; // Add bcrypt dependency for password hashing +use rusqlite::{params, Connection, OptionalExtension}; +use uuid::Uuid; // UUID for generating unique IDs // Import anyhow -pub fn init_db() -> Result { - let conn = Connection::open("form_data.db")?; +pub fn init_db() -> AnyhowResult { + let conn = Connection::open("form_data.db").context("Failed to open the database")?; conn.execute( "CREATE TABLE IF NOT EXISTS forms ( @@ -35,15 +38,51 @@ pub fn init_db() -> Result { [], )?; + // Setup initial admin after creating the tables + setup_initial_admin(&conn)?; + Ok(conn) } +pub fn setup_initial_admin(conn: &Connection) -> AnyhowResult<()> { + add_admin_user(conn)?; + Ok(()) +} + +pub fn add_admin_user(conn: &Connection) -> AnyhowResult<()> { + // Check if admin user already exists + let mut stmt = conn + .prepare("SELECT id FROM users WHERE username = ?1") + .context("Failed to prepare query for checking admin user")?; + if stmt.exists(params!["admin"])? { + return Ok(()); + } + + // Generate a UUID for the admin user + let admin_id = Uuid::new_v4().to_string(); + + // Hash the password before storing it + let hashed_password = hash("admin", DEFAULT_COST).context("Failed to hash password")?; + + // Add admin user with hashed password + conn.execute( + "INSERT INTO users (id, username, password) VALUES (?1, ?2, ?3)", + params![admin_id, "admin", hashed_password], + ) + .context("Failed to insert admin user into the database")?; + + Ok(()) +} + // Add a function to validate a token -pub fn validate_token(conn: &Connection, token: &str) -> Result> { - let mut stmt = conn.prepare("SELECT id FROM users WHERE token = ?1")?; +pub fn validate_token(conn: &Connection, token: &str) -> AnyhowResult> { + let mut stmt = conn + .prepare("SELECT id FROM users WHERE token = ?1") + .context("Failed to prepare query for validating token")?; let user_id: Option = stmt .query_row(params![token], |row| row.get(0)) - .optional()?; + .optional() + .context("Failed to retrieve user ID for the given token")?; Ok(user_id) } @@ -52,16 +91,20 @@ pub fn authenticate_user( conn: &Connection, username: &str, password: &str, -) -> Result> { - let mut stmt = conn.prepare("SELECT id, password FROM users WHERE username = ?1")?; - let mut rows = stmt.query(params![username])?; +) -> AnyhowResult> { + let mut stmt = conn + .prepare("SELECT id, password FROM users WHERE username = ?1") + .context("Failed to prepare query for authenticating user")?; + let mut rows = stmt + .query(params![username]) + .context("Failed to execute query for authenticating user")?; if let Some(row) = rows.next()? { let user_id: String = row.get(0)?; let stored_password: String = row.get(1)?; - // Replace this with a secure password hashing and verification mechanism - if stored_password == password { + // Use bcrypt to verify the hashed password + if verify(password, &stored_password).context("Failed to verify password")? { return Ok(Some(user_id)); } } @@ -69,10 +112,11 @@ pub fn authenticate_user( } // Add a function to generate and save a token for a user -pub fn generate_token_for_user(conn: &Connection, user_id: &str, token: &str) -> Result<()> { +pub fn generate_token_for_user(conn: &Connection, user_id: &str, token: &str) -> AnyhowResult<()> { conn.execute( "UPDATE users SET token = ?1 WHERE id = ?2", params![token, user_id], - )?; + ) + .context("Failed to update token for user")?; Ok(()) } From da418169c6711ebb53726e0127cf5ead3d0e92d6 Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:34:25 +0100 Subject: [PATCH 06/10] moved --- frontend/src/routes/{ => (auth)}/create/+page.svelte | 4 ++-- frontend/src/routes/{ => (auth)}/form/[id]/+page.svelte | 0 frontend/src/routes/{ => (auth)}/form/[id]/+page.ts | 0 frontend/src/routes/{ => (auth)/main}/+page.svelte | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename frontend/src/routes/{ => (auth)}/create/+page.svelte (93%) rename frontend/src/routes/{ => (auth)}/form/[id]/+page.svelte (100%) rename frontend/src/routes/{ => (auth)}/form/[id]/+page.ts (100%) rename frontend/src/routes/{ => (auth)/main}/+page.svelte (76%) diff --git a/frontend/src/routes/create/+page.svelte b/frontend/src/routes/(auth)/create/+page.svelte similarity index 93% rename from frontend/src/routes/create/+page.svelte rename to frontend/src/routes/(auth)/create/+page.svelte index 0eaa638..0a260d0 100644 --- a/frontend/src/routes/create/+page.svelte +++ b/frontend/src/routes/(auth)/create/+page.svelte @@ -1,6 +1,6 @@ + +{@render children()} diff --git a/frontend/src/routes/+layout.ts b/frontend/src/routes/+layout.ts new file mode 100644 index 0000000..a3d1578 --- /dev/null +++ b/frontend/src/routes/+layout.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/frontend/src/routes/+page.ts b/frontend/src/routes/+page.ts new file mode 100644 index 0000000..471709e --- /dev/null +++ b/frontend/src/routes/+page.ts @@ -0,0 +1,7 @@ +import { loggedIn } from '$lib/api'; +import { redirect } from '@sveltejs/kit'; + +export async function load() { + const page = loggedIn() ? '/main' : '/login'; + redirect(307, page); +} diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte new file mode 100644 index 0000000..b69d326 --- /dev/null +++ b/frontend/src/routes/login/+page.svelte @@ -0,0 +1,51 @@ + + + diff --git a/frontend/src/routes/login/+page.ts b/frontend/src/routes/login/+page.ts new file mode 100644 index 0000000..22f14b6 --- /dev/null +++ b/frontend/src/routes/login/+page.ts @@ -0,0 +1,8 @@ +import { loggedIn } from '$lib/api'; +import { redirect } from '@sveltejs/kit'; + +export async function load() { + if (loggedIn()) { + redirect(307, '/'); + } +} From a20bfd210134468b9560d3ecef247cd32403e59e Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:35:14 +0100 Subject: [PATCH 08/10] session managment --- frontend/src/lib/api.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 99e3efe..1f0c995 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -10,6 +10,11 @@ function setAuthToken(token: string): void { localStorage.setItem('auth_token', token); } +// A simple function to save the token +function delAuthToken(): void { + localStorage.removeItem('auth_token'); +} + /** * Create a new form. * @param name The name of the form. @@ -123,3 +128,11 @@ export async function login(username: string, password: string): Promise { const { token } = await response.json(); setAuthToken(token); // Store the token in localStorage } + +export function logout() { + delAuthToken(); +} + +export function loggedIn() { + return localStorage.getItem('auth_token') !== null; +} From 24e3421107d3963df654f9bbc9b026d011f8398e Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:35:23 +0100 Subject: [PATCH 09/10] deleted --- frontend/src/lib/index.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/src/lib/index.ts diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/frontend/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. From e225b6dcadadfa3ef249ecdacbc178566214281e Mon Sep 17 00:00:00 2001 From: "Mohamad.Elsena" Date: Thu, 2 Jan 2025 14:36:59 +0100 Subject: [PATCH 10/10] fixed imports --- frontend/src/routes/(auth)/form/[id]/+page.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/(auth)/form/[id]/+page.svelte b/frontend/src/routes/(auth)/form/[id]/+page.svelte index 6bd206f..5b4105a 100644 --- a/frontend/src/routes/(auth)/form/[id]/+page.svelte +++ b/frontend/src/routes/(auth)/form/[id]/+page.svelte @@ -1,7 +1,7 @@