From e32c1f9492b645016446e50922d0f278da219ce4 Mon Sep 17 00:00:00 2001 From: Colin McKechney Date: Thu, 27 Apr 2023 23:59:14 -0400 Subject: [PATCH] beginnings of authentication --- Cargo.lock | 211 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 + src/api/mod.rs | 2 + src/api/user.rs | 133 ++++++++++++++++++++++++++++++ src/main.rs | 71 ++++++++-------- src/security.rs | 81 ------------------- 6 files changed, 384 insertions(+), 118 deletions(-) create mode 100644 src/api/mod.rs create mode 100644 src/api/user.rs delete mode 100644 src/security.rs diff --git a/Cargo.lock b/Cargo.lock index b400687..218619c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "actix-cors" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.3.1" @@ -30,7 +45,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash 0.8.3", - "base64", + "base64 0.21.0", "bitflags", "brotli", "bytes", @@ -58,6 +73,22 @@ dependencies = [ "zstd", ] +[[package]] +name = "actix-identity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1224c9f9593dc27c9077b233ce04adedc1d7febcfc35ee9f53ea3c24df180bec" +dependencies = [ + "actix-service", + "actix-session", + "actix-utils", + "actix-web", + "anyhow", + "futures-core", + "serde", + "tracing", +] + [[package]] name = "actix-macros" version = "0.2.3" @@ -120,6 +151,23 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "actix-session" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da8b818ae1f11049a4d218975345fe8e56ce5a5f92c11f972abcff5ff80e87" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "anyhow", + "async-trait", + "derive_more", + "serde", + "serde_json", + "tracing", +] + [[package]] name = "actix-utils" version = "3.0.1" @@ -193,6 +241,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" name = "adv_db" version = "0.1.0" dependencies = [ + "actix-cors", + "actix-identity", + "actix-session", "actix-web", "chrono", "env_logger", @@ -200,9 +251,45 @@ dependencies = [ "oracle", "rand", "serde", + "serde_json", "sha2", ] +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -259,12 +346,35 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64" version = "0.21.0" @@ -358,6 +468,16 @@ dependencies = [ "winapi", ] +[[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 = "codespan-reporting" version = "0.11.1" @@ -380,7 +500,14 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ + "aes-gcm", + "base64 0.20.0", + "hkdf", + "hmac", "percent-encoding", + "rand", + "sha2", + "subtle", "time 0.3.20", "version_check", ] @@ -416,9 +543,19 @@ 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 = "cxx" version = "1.0.94" @@ -519,6 +656,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -640,6 +778,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "h2" version = "0.3.18" @@ -680,6 +828,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.9" @@ -759,6 +925,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -944,6 +1119,12 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "oracle" version = "0.5.7" @@ -1021,6 +1202,18 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1253,6 +1446,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.109" @@ -1421,6 +1620,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "url" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 445348c..b8e99d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-cors = "0.6.4" +actix-identity = "0.5.2" +actix-session = { version = "0.7.2", features = ["cookie-session"] } actix-web = "4.3.1" chrono = "0.4.24" env_logger = "0.10.0" @@ -13,4 +16,5 @@ log = "0.4.17" oracle = "0.5.7" rand = "0.8.5" serde = { version = "1.0.160", features = ["derive"] } +serde_json = "1.0.96" sha2 = "0.10.6" diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..34b2ad4 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,2 @@ +pub mod user; + diff --git a/src/api/user.rs b/src/api/user.rs new file mode 100644 index 0000000..9a7d291 --- /dev/null +++ b/src/api/user.rs @@ -0,0 +1,133 @@ +use sha2::{Sha256, Digest}; +use rand::{prelude::Rng, distributions::Alphanumeric }; +use oracle::{Connection, Error}; +use log::{info, warn, error}; +use actix_identity::Identity; +use actix_web::{web, Responder, HttpRequest, HttpMessage, HttpResponse}; +use serde::{Deserialize, Serialize}; + + +static SQL_USERNAME: &str = "group09_user"; +static SQL_PASSWORD: &str = "group09_user"; +static SALT_LEN: usize = 16; + +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct Entry { + net_id: String, + password: String +} + +#[derive(Deserialize, Serialize, Debug, Default)] +pub struct User { + id: String, + first_name: String, + last_name: String +} + + +pub async fn login(request: HttpRequest, body: web::Json) -> impl Responder { + //TODO: finish login, for now will simply login for everyone + let net_id: &str = body.net_id.as_str(); + let password: &str = body.password.as_str(); + + //Identity::login(&request.extensions(), "User1".into()); + println!("{:?}", request); + println!("{:?}", body); + match authenticate(net_id, password) { + Some(user) => { + Identity::login(&request.extensions(), net_id.into()); + web::Json(user) + }, + None => web::Json(User::default()) + } +} + +pub async fn logout(user: Identity) -> impl Responder { + user.logout(); + HttpResponse::Ok() +} + +fn authenticate(username: &str, password: &str) -> Option { + + info!("Authenticating user: {}", username); + + let conn = match Connection::connect(SQL_USERNAME,SQL_PASSWORD, ""){ + Ok(c) => c, + Err(e) => { + error!("unable to open connection to server: {}", e); + return None; + } + }; + let mut stmt = match conn.statement("select * from student where net_id = :1").build(){ + Ok(s) => s, + Err(e) => { + error!("unable to build statement: {}", e); + return None; + } + }; + + let row = stmt.query_row_as::<(String, String, String, String, String)>(&[&username]).unwrap_or_default(); + + let true_pword = row.3; + let salt = row.4; + + let mut hasher = Sha256::new(); + hasher.update(password); + hasher.update(salt); + let hash = hasher.finalize(); + + let mut tmp: String = String::new(); + for value in hash{ + tmp += &format!("{:x}", value); + } + + conn.close().unwrap_or_default(); + + + if true_pword.eq(&tmp) { + info!("User {} successfully authenticated", username); + Some( User { id: row.0, first_name: row.1,last_name: row.2} ) + } + else{ + warn!("User {} failed authentication", username); + None + } + +} + + + + +fn create_user(username: &str, password: &str, first_name: &str, last_name: &str) -> Result<(), Error> { + + info!("Creating user: {}", username); + let conn = Connection::connect(SQL_USERNAME, SQL_PASSWORD, "")?; + let mut stmt = conn.statement("insert into student values(:net_id, :first_name, :last_name, :password, :salt)").build()?; + + let salt: String = rand::thread_rng().sample_iter(&Alphanumeric).take(SALT_LEN).map(char::from).collect(); + let mut hasher = Sha256::new(); + hasher.update(&password); + hasher.update(&salt); + let hash = hasher.finalize(); + + let mut hash_string = String::new(); + + for value in hash{ + hash_string += &format!("{:x}", value); + } + + match stmt.execute_named(&[("net_id", &username), ("first_name", &first_name), ("last_name", &last_name), ("password", &hash_string), ("salt", &salt)]) { + Ok(_) => { + info!("User {} successfully created", username); + conn.commit()?; + }, + Err(_) => { + warn!("Failed to create user {}", username); + conn.rollback()?; + } + }; + + conn.close()?; + Ok(()) + +} diff --git a/src/main.rs b/src/main.rs index 760cf94..be26b33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,14 @@ use log::{info, warn, error}; use env_logger::Env; -use actix_web::{web, get, post, web::Json, App, HttpResponse, HttpServer, Responder}; +use actix_web::{web, get, post, web::Json, App, HttpResponse, HttpServer, Responder, middleware, cookie::Key}; use serde::{Deserialize, Serialize}; -mod security; +use actix_cors::Cors; +use actix_identity::IdentityMiddleware; +use actix_session::{SessionMiddleware, storage::CookieSessionStore}; +mod api; -static PORT: u16 = 8009; +static PORT: u16 = 5000; +const ALLOWED_ORIGIN: &str = "http://localhost"; #[derive(Default, Debug, Serialize, Deserialize, Clone)] @@ -21,46 +25,41 @@ async fn main() -> std::io::Result<()> { let env = Env::default().filter_or("LOG_LEVEL", "info"); env_logger::init_from_env(env); - let _ = HttpServer::new( || { + let secret_key = Key::generate(); + + let _ = HttpServer::new(move || { App::new() - .service(index) - .service(login) - .service(homepage) - .service(plan_page) - }) - .bind(("0.0.0.0", PORT))? + .wrap(middleware::Logger::default()) + .wrap( + Cors::default() + .allowed_origin(ALLOWED_ORIGIN) + .allowed_methods(vec!["GET","POST","DELETE"]) + .supports_credentials() + ) + .wrap(IdentityMiddleware::default()) + .wrap(SessionMiddleware::new(CookieSessionStore::default(), secret_key.clone())) + .service( + web::scope("/api") + .service( + web::resource("/auth") + .route(web::post().to(api::user::login)) + .route(web::delete().to(api::user::logout)) + ) + .route("/", web::get().to(api_index)) + ) + .route("/", web::get().to(index)) + }) + .bind(("127.0.0.1", PORT))? .run() .await; - //Temporary for testing purposes, should write something to make a random salt - let username = "cmckechn"; - let password = "password"; - //proof of concept tests, create_user should fail in this instance because user was already - //created - security::authenticate(username, password).unwrap(); - security::create_user("test", "test_create", "test_first", "test_last").unwrap(); - security::authenticate("test", "test_create").unwrap(); Ok(()) } -#[get("/")] +async fn api_index() -> impl Responder { + HttpResponse::Ok().body("api") +} async fn index() -> impl Responder { - HttpResponse::Ok().body("Hello world!") + HttpResponse::Ok().body("/") } -#[get("/login")] -async fn login(json: Json) -> Result { - Ok(format!("{} {}", json.net_id, json.password)) -} - -#[get("/{net_id}/home")] -async fn homepage(path: web::Path) -> impl Responder { - let net_id = path.into_inner(); - HttpResponse::Ok().body(format!("You have reached the homepage of {} user", net_id) ) -} - -#[get("/{net_id}/plans")] -async fn plan_page(path: web::Path) -> impl Responder { - let net_id = path.into_inner(); - HttpResponse::Ok().body(format!("You have reached the plan page of {}", net_id)) -} diff --git a/src/security.rs b/src/security.rs deleted file mode 100644 index da8b1d7..0000000 --- a/src/security.rs +++ /dev/null @@ -1,81 +0,0 @@ -use sha2::{Sha256, Digest}; -use rand::{prelude::Rng, distributions::Alphanumeric }; -use oracle::{Connection, Error}; -use log::{info, warn}; - -static SQL_USERNAME: &str = "group09_user"; -static SQL_PASSWORD: &str = "group09_user"; -static SALT_LEN: usize = 16; - -pub fn authenticate(username: &str, password: &str) -> Result { - - info!("Authenticating user: {}", username); - - let conn = Connection::connect(SQL_USERNAME,SQL_PASSWORD, "")?; - let mut stmt = conn.statement("select password, salt from student where net_id = :1").build()?; - let row = stmt.query_row_as::<(String, String)>(&[&username])?; - - - let true_pword = row.0; - let salt = row.1; - - let mut hasher = Sha256::new(); - hasher.update(password); - hasher.update(salt); - let hash = hasher.finalize(); - - let mut tmp: String = String::new(); - for value in hash{ - tmp += &format!("{:x}", value); - } - - conn.close()?; - - - if true_pword.eq(&tmp) { - info!("User {} successfully authenticated", username); - Ok(true) - } - else{ - warn!("User {} failed authentication", username); - Ok(false) - } - -} - - - - -pub fn create_user(username: &str, password: &str, first_name: &str, last_name: &str) -> Result<(), Error> { - - info!("Creating user: {}", username); - let conn = Connection::connect(SQL_USERNAME, SQL_PASSWORD, "")?; - let mut stmt = conn.statement("insert into student values(:net_id, :first_name, :last_name, :password, :salt)").build()?; - - let salt: String = rand::thread_rng().sample_iter(&Alphanumeric).take(SALT_LEN).map(char::from).collect(); - let mut hasher = Sha256::new(); - hasher.update(&password); - hasher.update(&salt); - let hash = hasher.finalize(); - - let mut hash_string = String::new(); - - for value in hash{ - hash_string += &format!("{:x}", value); - } - - match stmt.execute_named(&[("net_id", &username), ("first_name", &first_name), ("last_name", &last_name), ("password", &hash_string), ("salt", &salt)]) { - Ok(_) => { - info!("User {} successfully created", username); - conn.commit()?; - }, - Err(_) => { - warn!("Failed to create user {}", username); - conn.rollback()?; - } - }; - - conn.close()?; - Ok(()) - -}