From fb076081145b42f1335939b95b714577ac2d8581 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 13:18:08 +0100 Subject: [PATCH 01/16] fix: status code --- api/src/routes/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index b6008dc..aac339f 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -148,7 +148,7 @@ pub async fn get_candidate( let candidate = Query::find_candidate_by_id(db, id) .await - .map_err(|e| to_custom_error(ServiceError::Forbidden))? // TODO better error handling + .map_err(|e| to_custom_error(ServiceError::DbError(e)))? .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; let details = ApplicationService::decrypt_all_details( From d7974773938a02f38c73aea09733f5ccf959d6e7 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 13:22:13 +0100 Subject: [PATCH 02/16] feat: age no recipients error --- core/src/crypto.rs | 3 +-- core/src/error.rs | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index c8344f1..de9c921 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -222,8 +222,7 @@ async fn age_encrypt_with_recipients( return Ok(()); } else { - // TODO: Error handling - unreachable!("No recipients provided"); + return Err(ServiceError::AgeNoRecipientsError); } } diff --git a/core/src/error.rs b/core/src/error.rs index 5949ec9..4be2274 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -40,6 +40,8 @@ pub enum ServiceError { CandidateDetailsNotSet, #[error("Tokio join error")] TokioJoinError(#[from] tokio::task::JoinError), + #[error("Age no recipients error")] + AgeNoRecipientsError, #[error("Age encrypt error")] AgeEncryptError(#[from] age::EncryptError), #[error("Age decrypt error")] @@ -88,6 +90,7 @@ impl ServiceError { ServiceError::CryptoEncryptFailed => 500, ServiceError::CryptoDecryptFailed => 500, ServiceError::CandidateDetailsNotSet => 500, + ServiceError::AgeNoRecipientsError => 500, ServiceError::AgeEncryptError(_) => 500, ServiceError::AgeDecryptError(_) => 500, ServiceError::AgeKeyError(_) => 500, From 53a0dc9f2c3c5c810193758a6e8f873a5bf9cf16 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 13:27:26 +0100 Subject: [PATCH 03/16] fix: status codes --- core/src/error.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index 4be2274..b3ec724 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -12,10 +12,8 @@ pub enum ServiceError { Unauthorized, #[error("Forbidden")] Forbidden, - #[error("Session expired, please login agai")] + #[error("Session expired, please login again")] ExpiredSession, - #[error("Error while encoding JWT")] - JwtError, #[error("User already exists")] UserAlreadyExists, #[error("Candidate not found")] @@ -73,16 +71,18 @@ pub enum ServiceError { impl ServiceError { pub fn code(&self) -> u16 { match self { + // 40X ServiceError::InvalidApplicationId => 400, - ServiceError::InvalidCredentials => 401, + ServiceError::ParentOverflow => 400, ServiceError::Unauthorized => 401, - ServiceError::Forbidden => 403, + ServiceError::InvalidCredentials => 401, ServiceError::ExpiredSession => 401, - ServiceError::JwtError => 500, - ServiceError::UserAlreadyExists => 409, + ServiceError::Forbidden => 403, ServiceError::CandidateNotFound => 404, + ServiceError::IncompletePortfolio => 406, + ServiceError::UserAlreadyExists => 409, + // 500 ServiceError::ParentNotFound => 500, - ServiceError::ParentOverflow => 400, // TODO: correct error code ServiceError::DbError(_) => 500, ServiceError::UserNotFoundByJwtId => 500, ServiceError::UserNotFoundBySessionId => 500, @@ -101,8 +101,6 @@ impl ServiceError { ServiceError::TokioJoinError(_) => 500, ServiceError::AesError(_) => 500, ServiceError::ArgonConfigError(_) => 500, - //TODO: Correct code - ServiceError::IncompletePortfolio => 406, ServiceError::ZipError(_) => 500, ServiceError::CsvError(_) => 500, ServiceError::CsvIntoInnerError => 500, From 23562f492c2b9171582fb6058ad7c4693ff072cb Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 13:34:40 +0100 Subject: [PATCH 04/16] fix: code cleanup --- core/src/error.rs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index b3ec724..b4870d1 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -21,15 +21,11 @@ pub enum ServiceError { #[error("Parrent not found")] ParentNotFound, #[error("Database error")] - ParentOverflow, - #[error("Too many parents")] DbError(#[from] sea_orm::DbErr), - #[error("User not found, please contact technical support")] - UserNotFoundByJwtId, + #[error("Too many parents")] + ParentOverflow, #[error("User not found, please contact technical support")] UserNotFoundBySessionId, - #[error("Crypto hash failed, please contact technical support")] - CryptoHashFailed, #[error("Crypto encryption failed, please contact technical support")] CryptoEncryptFailed, #[error("Crypto decryption failed, please contact technical support")] @@ -84,9 +80,7 @@ impl ServiceError { // 500 ServiceError::ParentNotFound => 500, ServiceError::DbError(_) => 500, - ServiceError::UserNotFoundByJwtId => 500, ServiceError::UserNotFoundBySessionId => 500, - ServiceError::CryptoHashFailed => 500, ServiceError::CryptoEncryptFailed => 500, ServiceError::CryptoDecryptFailed => 500, ServiceError::CandidateDetailsNotSet => 500, @@ -106,18 +100,4 @@ impl ServiceError { ServiceError::CsvIntoInnerError => 500, } } -} - -#[cfg(test)] -mod tests { - use super::ServiceError; - - #[test] - fn test_service_error_code() { - let error = ServiceError::CryptoHashFailed; - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status - assert!(error.code() >= 100); - assert!(error.code() <= 599); - } } \ No newline at end of file From 75e3ccdcd5a008e1de5700aa7e96ffec47700b40 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 16:10:03 +0100 Subject: [PATCH 05/16] style: remove comment --- core/src/database/query/parent.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs index 01368f5..52fd6e7 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -19,7 +19,6 @@ impl Query { Entity::find_by_id(id).one(db).await } - // TODO limit to two parents?? pub async fn find_candidate_parents( db: &DbConn, candidate: candidate::Model, From 787b23404800bdf069c0632a5a89cb4798f58bbd Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 16:41:12 +0100 Subject: [PATCH 06/16] feat: test util function under cfg(test) --- core/src/utils/db.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/utils/db.rs b/core/src/utils/db.rs index cc71e0e..59a754d 100644 --- a/core/src/utils/db.rs +++ b/core/src/utils/db.rs @@ -1,6 +1,4 @@ -use entity::{admin, candidate, parent, session}; -use sea_orm::{Schema, Database, DbConn}; -use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; +use sea_orm::DbConn; use crate::Query; use crate::error::ServiceError; @@ -12,8 +10,13 @@ pub async fn get_recipients(db: &DbConn, candidate_pubkey: &str) -> Result sea_orm::DbConn { + use entity::{admin, candidate, parent, session}; + use sea_orm::{Schema, Database}; + use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; + + let base_url = "sqlite::memory:"; let db: DbConn = Database::connect(base_url).await.unwrap(); From 7bce4bf0bcc2c94141a0cf8fb4351d2c87b36bf5 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 16:58:03 +0100 Subject: [PATCH 07/16] feat: optimize candidate details --- core/src/models/candidate_details.rs | 119 +++++++++++++---------- core/src/services/application_service.rs | 5 +- core/src/services/candidate_service.rs | 3 +- core/src/services/parent_service.rs | 2 +- 4 files changed, 70 insertions(+), 59 deletions(-) diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index 3efcbaa..acef00c 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -1,6 +1,7 @@ use chrono::NaiveDate; use entity::{candidate, parent}; +use futures::future; use crate::{crypto, models::candidate::{CandidateWithParent, ApplicationDetails}, error::ServiceError}; @@ -83,7 +84,6 @@ impl From for EncryptedString { } impl TryFrom> for EncryptedString { - // TODO: take a look at this type Error = ServiceError; fn try_from(d: Option) -> Result { @@ -97,20 +97,20 @@ impl TryFrom> for EncryptedString { impl EncryptedCandidateDetails { pub async fn new( form: &CandidateDetails, - recipients: Vec, + recipients: &Vec, ) -> Result { let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( - EncryptedString::new(&form.name, &recipients), - EncryptedString::new(&form.surname, &recipients), - EncryptedString::new(&form.birthplace, &recipients), - EncryptedString::new(&birthdate_str, &recipients), - EncryptedString::new(&form.address, &recipients), - EncryptedString::new(&form.telephone, &recipients), - EncryptedString::new(&form.citizenship, &recipients), - EncryptedString::new(&form.email, &recipients), - EncryptedString::new(&form.sex, &recipients), - EncryptedString::new(&form.personal_id_number, &recipients), + EncryptedString::new(&form.name, recipients), + EncryptedString::new(&form.surname, recipients), + EncryptedString::new(&form.birthplace, recipients), + EncryptedString::new(&birthdate_str, recipients), + EncryptedString::new(&form.address, recipients), + EncryptedString::new(&form.telephone, recipients), + EncryptedString::new(&form.citizenship, recipients), + EncryptedString::new(&form.email, recipients), + EncryptedString::new(&form.sex, recipients), + EncryptedString::new(&form.personal_id_number, recipients), )?; Ok( @@ -187,13 +187,13 @@ impl TryFrom for EncryptedCandidateDetails { impl EncryptedParentDetails { pub async fn new( form: &ParentDetails, - recipients: Vec, + recipients: &Vec, ) -> Result { let d = tokio::try_join!( - EncryptedString::new(&form.name, &recipients), - EncryptedString::new(&form.surname, &recipients), - EncryptedString::new(&form.telephone, &recipients), - EncryptedString::new(&form.email, &recipients), + EncryptedString::new(&form.name, recipients), + EncryptedString::new(&form.surname, recipients), + EncryptedString::new(&form.telephone, recipients), + EncryptedString::new(&form.email, recipients), )?; Ok( @@ -244,11 +244,11 @@ impl EncryptedApplicationDetails { form: &ApplicationDetails, recipients: Vec, ) -> Result { - let candidate = EncryptedCandidateDetails::new(&form.candidate, recipients.clone()).await?; + let candidate = EncryptedCandidateDetails::new(&form.candidate, &recipients).await?; let mut enc_parents= vec![]; for parent in form.parents.iter() { enc_parents.push( - EncryptedParentDetails::new(parent, recipients.clone()).await? + EncryptedParentDetails::new(parent, &recipients).await? ); } Ok( @@ -260,15 +260,17 @@ impl EncryptedApplicationDetails { } pub async fn decrypt(self, priv_key: String) -> Result { - let candidate = self.candidate.decrypt(priv_key.clone()).await?; - let mut parents = vec![]; - for parent in self.parents.iter() { - let dec = parent.decrypt(priv_key.clone()).await?; - parents.push(dec); - } + let decrypted_candidate = self.candidate.decrypt(priv_key.clone()).await?; + + let decrypted_parents = future::try_join_all( + self.parents + .iter() + .map(|d| d.decrypt(priv_key.clone())) + ).await?; + Ok(ApplicationDetails { - candidate, - parents: parents, + candidate: decrypted_candidate, + parents: decrypted_parents, }) } } @@ -339,9 +341,12 @@ pub async fn decrypt_if_exists( pub mod tests { use std::sync::Mutex; + use chrono::{Local}; + use entity::admin; use once_cell::sync::Lazy; + use sea_orm::{DbConn, Set, ActiveModelTrait}; - use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}}; + use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}, utils::db::get_memory_sqlite_connection, Query, services::candidate_service::tests::put_user_data}; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; @@ -384,10 +389,30 @@ pub mod tests { assert_eq!(details.candidate.sex, "sex"); assert_eq!(details.candidate.study, "study"); assert_eq!(details.candidate.personal_id_number, "personal_id_number"); - assert_eq!(details.parents[0].name, "parent_name"); - assert_eq!(details.parents[0].surname, "parent_surname"); - assert_eq!(details.parents[0].telephone, "parent_telephone"); - assert_eq!(details.parents[0].email, "parent_email"); + for parent in &details.parents { + assert_eq!(parent.name, "parent_name"); + assert_eq!(parent.surname, "parent_surname"); + assert_eq!(parent.telephone, "parent_telephone"); + assert_eq!(parent.email, "parent_email"); + } + } + + async fn insert_test_admin(db: &DbConn) -> admin::Model { + admin::ActiveModel { + id: Set(1), + name: Set("Admin".to_owned()), + public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()), + // AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS + private_key: Set("5KCEGk0ueWVGnu5Xo3rmpLoilcVZ2ZWmwIcdZEJ8rrBNW7jwzZU/XTcTXtk/xyy/zjF8s+YnuVpOklQvX3EC/Sn+ZwyPY3jokM2RNwnZZlnqdehOEV1SMm/Y".to_owned()), + // test + password: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()), + created_at: Set(Local::now().naive_local()), + updated_at: Set(Local::now().naive_local()), + ..Default::default() + } + .insert(db) + .await + .unwrap() } #[tokio::test] @@ -436,35 +461,25 @@ pub mod tests { assert_all_application_details(&application_details); } - // TODO - /* #[tokio::test] + #[tokio::test] async fn test_encrypted_application_details_from_candidate_parent() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; + let db = get_memory_sqlite_connection().await; + let _admin = insert_test_admin(&db).await; - const birthdate: NaiveDate = chrono::offset::Local::now().date_naive(); - let encrypted_details = EncryptedApplicationDetails::try_from( - , - vec![PUBLIC_KEY], - ) - .await - .unwrap(); + let (candidate, parents) = put_user_data(&db).await; + + let encrypted_details = EncryptedApplicationDetails::try_from((candidate, parents)).unwrap(); let application_details = encrypted_details - .decrypt(PRIVATE_KEY.to_string()) + .decrypt(PRIVATE_KEY.to_string()) // decrypt with admin's private key .await .unwrap(); assert_all_application_details(&application_details); - } */ + } #[tokio::test] async fn test_encrypted_string_new() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); @@ -479,10 +494,6 @@ pub mod tests { #[tokio::test] async fn test_encrypted_string_decrypt() { - const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"; - const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; - let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()]) .await .unwrap(); diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 88a737a..c5a6183 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -28,10 +28,11 @@ impl ApplicationService { form: &ApplicationDetails, ) -> Result<(candidate::Model, Vec), ServiceError> { // TODO: is this service needed? + let recipients = get_recipients(db, &candidate.public_key).await?; Ok( ( - CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate).await?, - ParentService::add_parents_details(db, candidate, &form.parents).await? + CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate, &recipients).await?, + ParentService::add_parents_details(db, candidate, &form.parents, &recipients).await? ) ) } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index b15fea8..4a417d0 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -118,7 +118,6 @@ impl CandidateService { Mutation::update_candidate_password_and_keys(db, candidate.clone(), new_password_hash, pubkey, encrypted_priv_key).await?; // user might no have filled his details yet, but personal id number is filled from beginning - // TODO: make personal id number required let personal_id_number = EncryptedString::from(candidate.personal_identification_number.clone()) .decrypt(&admin_private_key) .await?; @@ -150,8 +149,8 @@ impl CandidateService { db: &DbConn, candidate: candidate::Model, details: &CandidateDetails, + recipients: &Vec, ) -> Result { - let recipients = get_recipients(db, &candidate.public_key).await?; let enc_details = EncryptedCandidateDetails::new(&details, recipients).await?; let model = Mutation::add_candidate_details(db, candidate, enc_details).await?; Ok(model) diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index f25664d..347292d 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -30,6 +30,7 @@ impl ParentService { db: &DbConn, ref_candidate: candidate::Model, parents_details: &Vec, + recipients: &Vec, ) -> Result, ServiceError> { let found_parents = Query::find_candidate_parents(db, ref_candidate.clone()).await?; if found_parents.len() > 2 { @@ -42,7 +43,6 @@ impl ParentService { Some(parent) => parent.clone(), None => ParentService::create(db, ref_candidate.application).await?, }; - let recipients = get_recipients(db, &ref_candidate.public_key).await?; let enc_details = EncryptedParentDetails::new(&parents_details[i], recipients).await?; let parent = Mutation::add_parent_details(db, found_parent.clone(), enc_details.clone()).await?; result.push(parent); From e5e91aff9a702c853447a3641990b4947205fe88 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 17:10:00 +0100 Subject: [PATCH 08/16] feat: map application details --- core/src/models/candidate_details.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index acef00c..d620d43 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -245,12 +245,10 @@ impl EncryptedApplicationDetails { recipients: Vec, ) -> Result { let candidate = EncryptedCandidateDetails::new(&form.candidate, &recipients).await?; - let mut enc_parents= vec![]; - for parent in form.parents.iter() { - enc_parents.push( - EncryptedParentDetails::new(parent, &recipients).await? - ); - } + let enc_parents = future::try_join_all( + form.parents.iter() + .map(|d| EncryptedParentDetails::new(d, &recipients)) + ).await?; Ok( EncryptedApplicationDetails { candidate, @@ -282,12 +280,10 @@ impl TryFrom<(candidate::Model, Vec)> for EncryptedApplicationDet fn try_from( (candidate, parents): (candidate::Model, Vec), ) -> Result { - let mut enc_parents = vec![]; - for parent in parents.iter() { - enc_parents.push( - EncryptedParentDetails::try_from(parent.clone())? - ); - } + let enc_parents = parents.iter() + .map(|m| EncryptedParentDetails::try_from(m.clone())) + .collect::, ServiceError>>()?; + Ok(EncryptedApplicationDetails { candidate: EncryptedCandidateDetails::try_from(candidate)?, parents: enc_parents, From 95aa99cfa1799b0c2891ca6ad78b1d572e2931c0 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 17:17:35 +0100 Subject: [PATCH 09/16] feat: session mutation test --- core/src/crypto.rs | 1 - core/src/database/mutation/session.rs | 20 +++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index de9c921..cc6f0a4 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -15,7 +15,6 @@ use crate::error::ServiceError; /// Foolproof random 8 char string /// only uppercase letters (except for 0 and O) and numbers -/// TODO tests pub fn random_8_char_string() -> String { let iterator = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) diff --git a/core/src/database/mutation/session.rs b/core/src/database/mutation/session.rs index f557bc0..c540398 100644 --- a/core/src/database/mutation/session.rs +++ b/core/src/database/mutation/session.rs @@ -40,5 +40,23 @@ impl Mutation { #[cfg(test)] mod tests { - // TODO: Testy + use sea_orm::prelude::Uuid; + + use crate::{utils::db::get_memory_sqlite_connection, Mutation, services::candidate_service::tests::put_user_data}; + + #[tokio::test] + async fn test_insert_delete_session() { + let db = get_memory_sqlite_connection().await; + + let session_id = Uuid::new_v4(); + let (user, _) = put_user_data(&db).await; + + let session = Mutation::insert_session(&db, Some(user.application), None, session_id, "127.0.0.1".to_string()).await.unwrap(); + + assert_eq!(session.id, session_id); + + let delete_result = Mutation::delete_session(&db, session_id).await.unwrap(); + + assert_eq!(delete_result.rows_affected, 1); + } } \ No newline at end of file From 24d385bbdf8aa741ae16b9e35ca8922c6b7bb9e5 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 17:20:19 +0100 Subject: [PATCH 10/16] fix: find session test --- core/src/database/query/session.rs | 40 ++++++++++++++---------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/core/src/database/query/session.rs b/core/src/database/query/session.rs index 10aed95..854a2d9 100644 --- a/core/src/database/query/session.rs +++ b/core/src/database/query/session.rs @@ -32,7 +32,8 @@ impl Query { #[cfg(test)] mod tests { - use entity::{session}; + use entity::{session, admin, candidate}; + use sea_orm::ActiveValue::NotSet; use sea_orm::{prelude::Uuid, ActiveModelTrait, Set}; use crate::utils::db::get_memory_sqlite_connection; @@ -57,8 +58,7 @@ mod tests { assert!(session.is_some()); } - // TODO: Opravit test_find_sessions_by_user_id - /* #[tokio::test] + #[tokio::test] async fn test_find_sessions_by_user_id() { let db = get_memory_sqlite_connection().await; @@ -69,14 +69,14 @@ mod tests { code: Set("test".to_string()), public_key: Set("test".to_string()), private_key: Set("test".to_string()), - personal_identification_number_hash: Set("test".to_string()), + personal_identification_number: Set("test".to_string()), created_at: Set(chrono::offset::Local::now().naive_local()), updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); session::ActiveModel { id: Set(Uuid::new_v4()), @@ -87,9 +87,9 @@ mod tests { expires_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); const ADMIN_ID: i32 = 1; @@ -103,9 +103,9 @@ mod tests { updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); + .insert(&db) + .await + .unwrap(); session::ActiveModel { id: Set(Uuid::new_v4()), @@ -116,18 +116,14 @@ mod tests { expires_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() } - .insert(&db) - .await - .unwrap(); - - let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None) + .insert(&db) .await .unwrap(); + + let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None).await.unwrap(); assert_eq!(sessions.len(), 1); - let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)) - .await - .unwrap(); + let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)).await.unwrap(); assert_eq!(sessions.len(), 1); - } */ + } } From 9fbeb61280ab56bd79547e8a3549b32bf62c2586 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 17:23:35 +0100 Subject: [PATCH 11/16] style: comments --- core/src/models/candidate.rs | 6 ++---- core/src/models/candidate_details.rs | 4 +--- core/src/services/application_service.rs | 2 +- core/src/services/candidate_service.rs | 6 +++--- core/src/services/session_service.rs | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/core/src/models/candidate.rs b/core/src/models/candidate.rs index 14c4082..a93e44a 100644 --- a/core/src/models/candidate.rs +++ b/core/src/models/candidate.rs @@ -30,11 +30,10 @@ pub struct BaseCandidateResponse { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct CandidateDetails { - // pub application_id: i32, pub name: String, pub surname: String, pub birthplace: String, - pub birthdate: NaiveDate, // TODO: User NaiveDate or String? + pub birthdate: NaiveDate, pub address: String, pub telephone: String, pub citizenship: String, @@ -46,7 +45,6 @@ pub struct CandidateDetails { #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] pub struct ParentDetails { - // pub application_id: i32, pub name: String, pub surname: String, pub telephone: String, @@ -65,7 +63,7 @@ pub struct ApplicationDetails { /// CSV export (admin endpoint) #[derive(FromQueryResult, Serialize, Default)] #[serde(rename_all = "camelCase")] -pub struct CandidateWithParent { // TODO: use this instead of (Candidate, Parent)??? +pub struct CandidateWithParent { pub application: i32, pub name: Option, pub surname: Option, diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index d620d43..912e6c9 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -148,7 +148,7 @@ impl EncryptedCandidateDetails { name: d.0, surname: d.1, birthplace: d.2, - birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO + birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), address: d.4, telephone: d.5, citizenship: d.6, @@ -273,7 +273,6 @@ impl EncryptedApplicationDetails { } } -// TODO: use different metehod for this impl TryFrom<(candidate::Model, Vec)> for EncryptedApplicationDetails { type Error = ServiceError; @@ -322,7 +321,6 @@ impl TryFrom for EncryptedApplicationDetails { } } -// TODO: use this more??? pub async fn decrypt_if_exists( private_key: &String, encrypted_string: Option, diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index c5a6183..6c225e3 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -26,7 +26,7 @@ impl ApplicationService { db: &DbConn, candidate: candidate::Model, form: &ApplicationDetails, - ) -> Result<(candidate::Model, Vec), ServiceError> { // TODO: is this service needed? + ) -> Result<(candidate::Model, Vec), ServiceError> { let recipients = get_recipients(db, &candidate.public_key).await?; Ok( diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 4a417d0..a22ea53 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -10,7 +10,7 @@ use crate::{ use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService, portfolio_service::PortfolioService}; -// TODO +// TODO validation /* pub struct FieldOfStudy { pub short_name: String, @@ -102,7 +102,7 @@ impl CandidateService { ) -> Result { let candidate = Query::find_candidate_by_id(db, id).await? .ok_or(ServiceError::CandidateNotFound)?; - let parents = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO + let parents = Query::find_candidate_parents(db, candidate.clone()).await?; let new_password_plain = crypto::random_8_char_string(); @@ -242,7 +242,7 @@ impl CandidateService { fn is_application_id_valid(application_id: i32) -> bool { let s = &application_id.to_string(); if s.len() <= 3 { - // TODO: does the field of study prefix have to be exactly 6 digits? + // TODO: does the field of study prefix have to be exactly 6 digits? VYRESIT PODLE PRIHLASEK!!! return false; } let field_of_study_prefix = &s[0..3]; diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 389c712..78bc054 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -109,7 +109,7 @@ impl SessionService { // delete old sessions SessionService::delete_old_sessions(db, user_id, admin_id, 3) .await - .ok(); // TODO move to dotenv + .ok(); Ok(session.id.to_string()) } From efa4fbe40df5e3ea0574a6c1c8c3c78b53c52685 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 17:44:26 +0100 Subject: [PATCH 12/16] fix: remove cfg(test) --- core/src/utils/db.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/utils/db.rs b/core/src/utils/db.rs index 59a754d..c9a4503 100644 --- a/core/src/utils/db.rs +++ b/core/src/utils/db.rs @@ -10,7 +10,6 @@ pub async fn get_recipients(db: &DbConn, candidate_pubkey: &str) -> Result sea_orm::DbConn { use entity::{admin, candidate, parent, session}; use sea_orm::{Schema, Database}; From 96a93dad3a66b023cdaead5f9c62c8e9affe39d1 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 18:27:59 +0100 Subject: [PATCH 13/16] feat: safer portfolio submit --- api/src/routes/candidate.rs | 1 - core/src/error.rs | 3 +++ core/src/services/portfolio_service.rs | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 75a40ff..91e0cab 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -213,7 +213,6 @@ pub async fn submit_portfolio( if submit.is_err() { let e = submit.err().unwrap(); // Delete on critical error - // TODO: Více kontrol? if e.code() == 500 { // Cleanup PortfolioService::delete_portfolio(candidate.application) diff --git a/core/src/error.rs b/core/src/error.rs index b4870d1..dc5992a 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -56,6 +56,8 @@ pub enum ServiceError { AesError(#[from] aes_gcm_siv::Error), #[error("Portfolio is incomplete")] IncompletePortfolio, + #[error("Portfolio write error")] + PortfolioWriteError, #[error("Zip error")] ZipError(#[from] async_zip::error::ZipError), #[error("Csv error")] @@ -95,6 +97,7 @@ impl ServiceError { ServiceError::TokioJoinError(_) => 500, ServiceError::AesError(_) => 500, ServiceError::ArgonConfigError(_) => 500, + ServiceError::PortfolioWriteError => 500, ServiceError::ZipError(_) => 500, ServiceError::CsvError(_) => 500, ServiceError::CsvIntoInnerError => 500, diff --git a/core/src/services/portfolio_service.rs b/core/src/services/portfolio_service.rs index 556f31c..2552154 100644 --- a/core/src/services/portfolio_service.rs +++ b/core/src/services/portfolio_service.rs @@ -314,6 +314,11 @@ impl PortfolioService { ).await?; tokio::fs::remove_file(final_path).await?; + + if !Self::is_portfolio_submitted(candidate_id).await { + return Err(ServiceError::PortfolioWriteError) + } + Ok(()) } From 214fb72b839917794f28a9e865f3f6bd050721c4 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 18:31:21 +0100 Subject: [PATCH 14/16] style: delete comment --- cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 34ae161..fde048e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -257,7 +257,7 @@ async fn main() -> Result<(), Box> { let output = sub_matches.get_one::("output").unwrap(); tokio::fs::write(output, decrypted).await?; }, - Some(("package", sub_matches)) => { // TODO: compress the output directory into one file??? + Some(("package", sub_matches)) => { let db_url = sub_matches.get_one::("database").unwrap(); let db = get_db_conn(sub_matches).await?; let key = get_admin_private_key(&db, sub_matches).await?; From 55a31649aee39169ffb28491f35d3cf03d8dcb9a Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 18:31:31 +0100 Subject: [PATCH 15/16] feat: CORS in production --- api/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index bb0696f..bb1b2d7 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -47,7 +47,16 @@ impl Fairing for CORS { #[cfg(not(debug_assertions))] async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { - // TODO + response.set_header(Header::new( + "Access-Control-Allow-Origin", + "https://portfolio.ssps.cz", // TODO: UPRAVIT NA PRODUKČNÍ URL!! + )); + response.set_header(Header::new( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS, DELETE", + )); + response.set_header(Header::new("Access-Control-Allow-Headers", "content-type")); + response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); } } From a1de499b764510074e4cde4a18b7a8c4322d2813 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 14 Dec 2022 18:33:50 +0100 Subject: [PATCH 16/16] style: delete comments --- api/src/guards/data/letter.rs | 2 -- api/src/guards/data/portfolio.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/api/src/guards/data/letter.rs b/api/src/guards/data/letter.rs index 58ee4e2..a92c5ea 100644 --- a/api/src/guards/data/letter.rs +++ b/api/src/guards/data/letter.rs @@ -25,7 +25,6 @@ impl<'r> FromData<'r> for Letter { let data_bytes = data.into_bytes().await.unwrap(); if !data_bytes.is_complete() { - // TODO: Over limit return Outcome::Failure((Status::BadRequest, None)) } @@ -34,7 +33,6 @@ impl<'r> FromData<'r> for Letter { let is_pdf = portfolio_core::utils::filetype::filetype_is_pdf(&data_bytes); if !is_pdf { - // TODO: Not PDF return Outcome::Failure((Status::BadRequest, None)) } diff --git a/api/src/guards/data/portfolio.rs b/api/src/guards/data/portfolio.rs index 9029220..4953437 100644 --- a/api/src/guards/data/portfolio.rs +++ b/api/src/guards/data/portfolio.rs @@ -25,7 +25,6 @@ impl<'r> FromData<'r> for Portfolio { let data_bytes = data.into_bytes().await.unwrap(); if !data_bytes.is_complete() { - // TODO: Over limit return Outcome::Failure((Status::BadRequest, None)) } @@ -34,7 +33,6 @@ impl<'r> FromData<'r> for Portfolio { let is_zip = portfolio_core::utils::filetype::filetype_is_zip(&data_bytes); if !is_zip { - // TODO: Not ZIP return Outcome::Failure((Status::BadRequest, None)) }