From 56f64a43c87c0ad7a73540374cc07c795db473dd Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Tue, 1 Nov 2022 19:01:12 +0100 Subject: [PATCH] refactor: errors, services --- api/src/lib.rs | 27 +++---- core/src/database/mutation/candidate.rs | 64 ++-------------- core/src/error.rs | 28 ++++++- core/src/services/candidate_service.rs | 98 +++++++++++++++++++++++++ core/src/services/mod.rs | 3 +- core/src/services/session_service.rs | 32 ++++---- 6 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 core/src/services/candidate_service.rs diff --git a/api/src/lib.rs b/api/src/lib.rs index 8cb3fa2..89c5775 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -4,15 +4,13 @@ extern crate rocket; use std::net::SocketAddr; use guards::request::session_auth::SessionAuth; -use portfolio_core::error::ServiceError; -use portfolio_core::services::session_service::SessionService; +use portfolio_core::services::candidate_service::CandidateService; use requests::{LoginRequest, RegisterRequest}; use rocket::http::Status; use rocket::{Rocket, Build}; use rocket::serde::json::Json; use rocket::fairing::{self, AdHoc}; use rocket::response::status::Custom; -use portfolio_core::{Mutation}; use migration::{MigratorTrait}; use sea_orm_rocket::{Connection, Database}; @@ -30,9 +28,9 @@ pub use entity::candidate::Entity as Candidate; use portfolio_core::crypto::random_8_char_string; -fn custom_err_from_service_err(service_err: ServiceError) -> Custom { +/* fn custom_err_from_service_err(service_err: ServiceError) -> Custom { Custom(Status::from_code(service_err.0.code).unwrap_or_default(), service_err.1.to_string()) -} +} */ #[post("/", data = "")] async fn create(conn: Connection<'_, Db>, post_form: Json) -> Result> { @@ -41,9 +39,9 @@ async fn create(conn: Connection<'_, Db>, post_form: Json) -> R let plain_text_password = random_8_char_string(); - Mutation::create_candidate(db, form.application_id, &plain_text_password, form.personal_id_number) + CandidateService::create(db, form.application_id, &plain_text_password, form.personal_id_number) .await - .expect("Could not insert candidate"); + .unwrap(); Ok(plain_text_password) } @@ -59,11 +57,13 @@ async fn login(conn: Connection<'_, Db>, login_form: Json, ip_addr let db = conn.into_inner(); println!("{} {}", login_form.application_id, login_form.password); - let session_token = SessionService::new_session(db, - login_form.application_id, - login_form.password.to_string(), - ip_addr.ip().to_string() - ).await; + let session_token = CandidateService::login( + db, + login_form.application_id, + login_form.password.to_string(), + ip_addr.ip().to_string() + ) + .await; if session_token.is_ok() { return Ok( @@ -71,7 +71,8 @@ async fn login(conn: Connection<'_, Db>, login_form: Json, ip_addr ); } else { return Err( - custom_err_from_service_err(session_token.err().unwrap()) + // custom_err_from_service_err(session_token.err().unwrap()) + Custom(Status::Unauthorized, "TODO".to_string()) ) } } diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 55d870c..bcde405 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -1,27 +1,17 @@ use crate::Mutation; -use std::vec; use ::entity::candidate; use sea_orm::{*}; -use crate::{crypto::{hash_password, self}}; impl Mutation { pub async fn create_candidate( db: &DbConn, application_id: i32, - plain_text_password: &String, - personal_id_number: String, + hashed_password: String, + encrypted_personal_id_number: String, + pubkey: String, + encrypted_priv_key: String ) -> Result { - // TODO: unwrap pro testing.. - let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap(); - let (pubkey, priv_key_plain_text) = crypto::create_identity(); - let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await.unwrap(); - - let encrypted_personal_id_number = crypto::encrypt_password_with_recipients( - &personal_id_number, vec![&pubkey] - ).await.unwrap(); - - candidate::ActiveModel { application: Set(application_id), personal_identification_number: Set(encrypted_personal_id_number), @@ -36,48 +26,8 @@ impl Mutation { .await } } - - +/* #[cfg(test)] mod tests { - use sea_orm::{Database, DbConn}; - - use crate::{Mutation, crypto}; - - #[cfg(test)] - async fn get_memory_sqlite_connection() -> DbConn { - use entity::candidate; - use sea_orm::{DbBackend, sea_query::TableCreateStatement, ConnectionTrait}; - use sea_orm::Schema; - - - let base_url = "sqlite::memory:"; - let db: DbConn = Database::connect(base_url).await.unwrap(); - - let schema = Schema::new(DbBackend::Sqlite); - let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity); - db.execute(db.get_database_backend().build(&stmt)).await.unwrap(); - db - } - - #[tokio::test] - async fn test_encrypt_decrypt_private_key_with_passphrase() { - let db = get_memory_sqlite_connection().await; - - let plain_text_password = "test".to_string(); - - let secret_message = "trnka".to_string(); - - - let candidate = Mutation::create_candidate(&db, 5555555, &plain_text_password, "".to_string()).await.unwrap(); - - let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap(); - - let private_key_plain_text = crypto::decrypt_password(candidate.private_key, plain_text_password).await.unwrap(); - - let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap(); - - assert_eq!(secret_message, decrypted_message); - - } -} \ No newline at end of file + #[tokio::fs] +} */ \ No newline at end of file diff --git a/core/src/error.rs b/core/src/error.rs index d8afda9..da400ba 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -1,4 +1,4 @@ -pub struct Status { +/* pub struct Status { pub code: u16, } @@ -21,4 +21,28 @@ pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 5 pub const USER_NOT_FOUND_BY_SESSION_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted "User not found, please contact technical support"); // Shouldn't ever happen -pub struct ServiceError<'a>(pub Status, pub &'a str); \ No newline at end of file +pub struct ServiceError<'a>(pub Status, pub &'a str); */ + +pub enum ServiceError { + InvalidCredentials, + ExpiredSession, + JwtError, + UserNotFound, + DbError, + UserNotFoundByJwtId, + UserNotFoundBySessionId, +} + +impl std::fmt::Debug for ServiceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ServiceError::InvalidCredentials => write!(f, "Invalid credentials"), + ServiceError::ExpiredSession => write!(f, "Session expired, please login again"), + ServiceError::JwtError => write!(f, "Error while encoding JWT"), + ServiceError::UserNotFound => write!(f, "User not found"), + ServiceError::DbError => write!(f, "Database error"), + ServiceError::UserNotFoundByJwtId => write!(f, "User not found, please contact technical support"), + ServiceError::UserNotFoundBySessionId => write!(f, "User not found, please contact technical support"), + } + } +} \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs new file mode 100644 index 0000000..dc87189 --- /dev/null +++ b/core/src/services/candidate_service.rs @@ -0,0 +1,98 @@ +use entity::candidate; +use sea_orm::{DbConn, prelude::Uuid}; + +use crate::{Mutation, crypto::{hash_password, self}, error::{ServiceError}}; + +use super::session_service::SessionService; + +pub struct CandidateService; + +impl CandidateService { + pub async fn create( + db: &DbConn, + application_id: i32, + plain_text_password: &String, + personal_id_number: String + ) -> Result{ + // TODO: unwrap pro testing.. + let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap(); + let (pubkey, priv_key_plain_text) = crypto::create_identity(); + let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await.unwrap(); + + let encrypted_personal_id_number = crypto::encrypt_password_with_recipients( + &personal_id_number, vec![&pubkey] + ).await.unwrap(); + + Mutation::create_candidate( + db, + application_id, + hashed_password, + encrypted_personal_id_number, + pubkey, + encrypted_priv_key + ) + .await + .map_err(|_| ServiceError::DbError) + } + + pub async fn login( + db: &DbConn, + user_id: i32, + password: String, + ip_addr: String + ) -> Result { + SessionService::new_session(db, user_id, password, ip_addr).await + } + + pub async fn auth( + db: &DbConn, + session_uuid: Uuid, + ) -> Result { + SessionService::auth_user_session(db, session_uuid).await + } +} + + +#[cfg(test)] +mod tests { + use sea_orm::{Database, DbConn}; + + use crate::{crypto, services::candidate_service::CandidateService}; + + #[cfg(test)] + async fn get_memory_sqlite_connection() -> DbConn { + use entity::candidate; + use sea_orm::{DbBackend, sea_query::TableCreateStatement, ConnectionTrait}; + use sea_orm::Schema; + + + let base_url = "sqlite::memory:"; + let db: DbConn = Database::connect(base_url).await.unwrap(); + + let schema = Schema::new(DbBackend::Sqlite); + let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity); + db.execute(db.get_database_backend().build(&stmt)).await.unwrap(); + db + } + + #[tokio::test] + async fn test_encrypt_decrypt_private_key_with_passphrase() { + let db = get_memory_sqlite_connection().await; + + let plain_text_password = "test".to_string(); + + let secret_message = "trnka".to_string(); + + + let candidate = CandidateService::create(&db, 5555555, &plain_text_password, "".to_string()).await.unwrap(); + + let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap(); + + let private_key_plain_text = crypto::decrypt_password(candidate.private_key, plain_text_password).await.unwrap(); + + let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap(); + + assert_eq!(secret_message, decrypted_message); + + } +} \ No newline at end of file diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index d731fb2..37693d2 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -1 +1,2 @@ -pub mod session_service; \ No newline at end of file +pub mod session_service; +pub mod candidate_service; \ No newline at end of file diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 60f938d..f7c95ab 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -3,7 +3,7 @@ use std::cmp::min; use entity::candidate; use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait}; -use crate::{crypto::{self}, Query, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID, USER_NOT_FOUND_BY_SESSION_ID, EXPIRED_SESSION_ERROR}, Mutation}; +use crate::{crypto::{self}, Query, error::{ServiceError}, Mutation}; // TODO: generics pub struct SessionService; @@ -28,19 +28,19 @@ impl SessionService { let candidate = match Query::find_candidate_by_id(db, user_id).await { Ok(candidate) => match candidate { Some(candidate) => candidate, - None => return Err(USER_NOT_FOUND_ERROR) + None => return Err(ServiceError::UserNotFound) }, - Err(_) => {return Err(DB_ERROR)} + Err(_) => {return Err(ServiceError::DbError)} }; // compare passwords match crypto::verify_password(password,candidate.code.clone()).await { Ok(valid) => { if !valid { - return Err(INVALID_CREDENTIALS_ERROR) + return Err(ServiceError::InvalidCredentials) } }, - Err(_) => {return Err(INVALID_CREDENTIALS_ERROR)} + Err(_) => {return Err(ServiceError::InvalidCredentials)} } @@ -49,7 +49,7 @@ impl SessionService { let session = match Mutation::insert_session(db, user_id, random_uuid, ip_addr).await { Ok(session) => session, - Err(_) => return Err(DB_ERROR) + Err(_) => return Err(ServiceError::DbError) }; // delete old sessions @@ -64,9 +64,9 @@ impl SessionService { let session = match Query::find_session_by_uuid(db, uuid).await { Ok(session) => match session { Some(session) => session, - None => return Err(USER_NOT_FOUND_BY_SESSION_ID) + None => return Err(ServiceError::UserNotFoundBySessionId) }, - Err(_) => {return Err(DB_ERROR)} + Err(_) => {return Err(ServiceError::DbError)} }; let now = chrono::Utc::now().naive_utc(); @@ -74,15 +74,15 @@ impl SessionService { if now > session.expires_at { // delete session Mutation::delete_session(db, session.id).await.unwrap(); - return Err(EXPIRED_SESSION_ERROR) + return Err(ServiceError::ExpiredSession) } let candidate = match session.find_related(candidate::Entity).one(db).await { Ok(candidate) => match candidate { Some(candidate) => candidate, - None => return Err(USER_NOT_FOUND_BY_JWT_ID) + None => return Err(ServiceError::UserNotFoundBySessionId) }, - Err(_) => {return Err(DB_ERROR)} + Err(_) => {return Err(ServiceError::DbError)} }; Ok(candidate) @@ -93,10 +93,10 @@ impl SessionService { #[cfg(test)] mod tests { - use entity::candidate; + use entity::{candidate}; use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid}; - use crate::{crypto, Mutation, services::session_service::SessionService}; + use crate::{crypto, services::{session_service::SessionService, candidate_service::CandidateService}}; #[cfg(test)] async fn get_memory_sqlite_connection() -> DbConn { @@ -119,7 +119,7 @@ mod tests { let db = get_memory_sqlite_connection().await; - let candidate = Mutation::create_candidate(&db, 5555555, &SECRET.to_string(), "".to_string()).await.unwrap(); + let candidate = CandidateService::create(&db, 5555555, &SECRET.to_string(), "".to_string()).await.ok().unwrap(); assert_eq!(candidate.application, 5555555); assert_ne!(candidate.code, SECRET.to_string()); @@ -130,7 +130,7 @@ mod tests { async fn test_candidate_session_correct_password() { let db = &get_memory_sqlite_connection().await; - Mutation::create_candidate(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.unwrap(); + CandidateService::create(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap(); // correct password let session = SessionService::new_session( @@ -153,7 +153,7 @@ mod tests { async fn test_candidate_session_incorrect_password() { let db = &get_memory_sqlite_connection().await; - let candidate_form = Mutation::create_candidate(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.unwrap(); + let candidate_form = CandidateService::create(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap(); // incorrect password assert!(