From 713d978f2c6846a9cf30b70f4a8e439e9240f7d0 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 4 Nov 2022 18:44:50 +0100 Subject: [PATCH 01/10] feat: put encrypted user personal data --- core/src/crypto.rs | 10 ++--- core/src/database/mutation/candidate.rs | 26 ++++++++++- core/src/services/candidate_service.rs | 60 ++++++++++++++++++++++++- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index cf95b2f..2b823db 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -197,7 +197,7 @@ pub fn create_identity() -> (String, String) { async fn age_encrypt_with_recipients( input_buffer: &[u8], output_buffer: &mut W, - recipients: Vec<&str>, + recipients: &Vec<&str>, ) -> Result<(), age::EncryptError> { let public_keys = recipients .into_iter() @@ -248,7 +248,7 @@ async fn age_decrypt_with_private_key( pub async fn encrypt_password_with_recipients( password_plain_text: &str, - recipients: Vec<&str>, + recipients: &Vec<&str>, ) -> Result { let mut encrypt_buffer = Vec::new(); @@ -287,7 +287,7 @@ pub async fn encrypt_file_with_recipients>( tokio::io::AsyncReadExt::read_to_end(&mut plain_file, &mut plain_file_contents).await?; - age_encrypt_with_recipients(plain_file_contents.as_slice(), &mut cipher_file, recipients).await + age_encrypt_with_recipients(plain_file_contents.as_slice(), &mut cipher_file, &recipients).await } pub async fn decrypt_file_with_private_key>( @@ -446,7 +446,7 @@ mod tests { const PASSWORD: &str = "test"; const PUBLIC_KEY: &str = "age1t220v5c8ye0pjx99kw8nr57y7a5qlw4ke0wchjuxnr2gcvfzt3hq7fufz0"; - let encrypted = super::encrypt_password_with_recipients(PASSWORD, vec![PUBLIC_KEY]) + let encrypted = super::encrypt_password_with_recipients(PASSWORD, &vec![PUBLIC_KEY]) .await .unwrap(); @@ -460,7 +460,7 @@ mod tests { const PUBLIC_KEY_2: &str = "age1ygswsk38cq9r64um5klqxyvzemfdvx6qe5zed99pdexakwwhpatsgatgpw"; let encrypted = - super::encrypt_password_with_recipients(PASSWORD, vec![PUBLIC_KEY_1, PUBLIC_KEY_2]) + super::encrypt_password_with_recipients(PASSWORD, &vec![PUBLIC_KEY_1, PUBLIC_KEY_2]) .await .unwrap(); diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index f6b80f0..14b94ce 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -1,6 +1,6 @@ -use crate::Mutation; +use crate::{Mutation, services::candidate_service::{AddUserDetailsForm, EncryptedAddUserData}}; -use ::entity::candidate; +use ::entity::candidate::{self, Model}; use sea_orm::{*}; impl Mutation { @@ -25,4 +25,26 @@ impl Mutation { .insert(db) .await } + + pub async fn add_user_details( + db: &DbConn, + user: Model, + details: EncryptedAddUserData, + ) -> Result { + let mut user: candidate::ActiveModel = user.into(); + user.name = Set(Some(details.name)); + user.surname = Set(Some(details.surname)); + user.birthplace = Set(Some(details.birthplace)); + user.birthdate = Set(Some(details.birthdate)); + user.address = Set(Some(details.address)); + user.telephone = Set(Some(details.telephone)); + user.citizenship = Set(Some(details.citizenship)); + user.email = Set(Some(details.email)); + user.sex = Set(Some(details.sex)); + user.study = Set(Some(details.study)); + + user.updated_at = Set(chrono::offset::Local::now().naive_local()); + + user.update(db).await + } } \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 145c953..b720426 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDate; use entity::candidate; use sea_orm::{DbConn, prelude::Uuid}; @@ -7,6 +8,51 @@ use super::session_service::SessionService; const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; +pub struct EncryptedAddUserData { + pub name: String, + pub surname: String, + pub birthplace: String, + pub birthdate: NaiveDate, + pub address: String, + pub telephone: String, + pub citizenship: String, + pub email: String, + pub sex: String, + pub study: String, +} + +pub struct AddUserDetailsForm { + pub application_id: i32, + + pub name: String, + pub surname: String, + pub birthplace: String, + pub birthdate: NaiveDate, + pub address: String, + pub telephone: String, + pub citizenship: String, + pub email: String, + pub sex: String, + pub study: String, +} + +impl AddUserDetailsForm { + pub async fn to_encrypted(self, recipients: Vec<&str>) -> EncryptedAddUserData { + EncryptedAddUserData { + name: crypto::encrypt_password_with_recipients(&self.name, &recipients).await.unwrap(), + surname: crypto::encrypt_password_with_recipients(&self.surname, &recipients).await.unwrap(), + birthplace: crypto::encrypt_password_with_recipients(&self.birthplace, &recipients).await.unwrap(), + birthdate: self.birthdate, // TODO: encrypt + address: crypto::encrypt_password_with_recipients(&self.address, &recipients).await.unwrap(), + telephone: crypto::encrypt_password_with_recipients(&self.telephone, &recipients).await.unwrap(), + citizenship: crypto::encrypt_password_with_recipients(&self.citizenship, &recipients).await.unwrap(), + email: crypto::encrypt_password_with_recipients(&self.email, &recipients).await.unwrap(), + sex: crypto::encrypt_password_with_recipients(&self.sex, &recipients).await.unwrap(), + study: crypto::encrypt_password_with_recipients(&self.study, &recipients).await.unwrap(), + } + } +} + pub struct CandidateService; impl CandidateService { @@ -37,7 +83,7 @@ impl CandidateService { 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] + &personal_id_number, &vec![&pubkey] ).await.unwrap(); Mutation::create_candidate( @@ -52,6 +98,16 @@ impl CandidateService { .map_err(|_| ServiceError::DbError) } + pub async fn add_user_details( + db: &DbConn, + details: AddUserDetailsForm, + ) -> Result { + let user = Query::find_candidate_by_id(db, details.application_id).await.unwrap().unwrap(); + let recipients = vec![&*user.public_key]; + let encrypted = details.to_encrypted(recipients).await; + Mutation::add_user_details(db, user, encrypted).await + } + pub async fn login( db: &DbConn, user_id: i32, @@ -123,7 +179,7 @@ mod tests { let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()).await.ok().unwrap(); - let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).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(); From 2bd112beb8412048d138040aa85a54aabef531c7 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 4 Nov 2022 19:05:22 +0100 Subject: [PATCH 02/10] feat: test put_user_data --- core/src/services/candidate_service.rs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index b720426..0bfa1d5 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -137,10 +137,13 @@ impl CandidateService { #[cfg(test)] mod tests { + use chrono::NaiveDate; use sea_orm::{Database, DbConn}; use crate::{crypto, services::candidate_service::CandidateService}; + use super::AddUserDetailsForm; + #[tokio::test] async fn test_application_id_validation() { assert!(CandidateService::is_application_id_valid(101_101)); @@ -188,4 +191,29 @@ mod tests { assert_eq!(secret_message, decrypted_message); } + + #[tokio::test] + async fn test_put_user_data() { + let db = get_memory_sqlite_connection().await; + let plain_text_password = "test".to_string(); + let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()).await.ok().unwrap(); + + let form = AddUserDetailsForm { + application_id: candidate.application, + name: "test".to_string(), + surname: "a".to_string(), + birthplace: "b".to_string(), + birthdate: NaiveDate::from_ymd(1999, 1, 1), + address: "test".to_string(), + telephone: "test".to_string(), + citizenship: "test".to_string(), + email: "test".to_string(), + sex: "test".to_string(), + study: "test".to_string(), + }; + + let candidate = CandidateService::add_user_details(&db, form).await.ok().unwrap(); + + assert!(candidate.name.is_some()); + } } \ No newline at end of file From 7bdec2482ec72461c3d35c39349bccf67ca99785 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:00:04 +0100 Subject: [PATCH 03/10] fix: update hash, remove structs --- core/src/crypto.rs | 2 +- core/src/database/mutation/candidate.rs | 39 ++++++++++++------- core/src/database/query/candidate.rs | 2 +- entity/src/candidate.rs | 4 +- .../src/m20221024_121621_create_candidate.rs | 4 +- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 2b823db..8614a87 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -344,7 +344,7 @@ mod tests { #[tokio::test] async fn test_verify_password() { - const HASH: &str = "$argon2id$v=19$m=4096,t=3,p=1$c2VjcmV0bHl0ZXN0aW5nZXZlcnl0aGluZw$xEzH8wD/ZjzgZTDTl3YtzMFCfcVa5M5m9y6NfSyB1n4"; + const HASH: &str = "$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ"; const PASSWORD: &str = "test"; let result = super::verify_password(PASSWORD.to_string(), HASH.to_string()) diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 14b94ce..4c86b08 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -1,4 +1,4 @@ -use crate::{Mutation, services::candidate_service::{AddUserDetailsForm, EncryptedAddUserData}}; +use crate::{Mutation}; use ::entity::candidate::{self, Model}; use sea_orm::{*}; @@ -8,13 +8,13 @@ impl Mutation { db: &DbConn, application_id: i32, hashed_password: String, - encrypted_personal_id_number: String, + hashed_personal_id_number: String, pubkey: String, encrypted_priv_key: String ) -> Result { candidate::ActiveModel { application: Set(application_id), - personal_identification_number: Set(encrypted_personal_id_number), + personal_identification_number_hash: Set(hashed_personal_id_number), code: Set(hashed_password), public_key: Set(pubkey), private_key: Set(encrypted_priv_key), @@ -26,22 +26,31 @@ impl Mutation { .await } - pub async fn add_user_details( + pub async fn add_candidate_details( db: &DbConn, user: Model, - details: EncryptedAddUserData, + name: String, + surname: String, + birthplace: String, + birthdate: String, + address: String, + telephone: String, + citizenship: String, + email: String, + sex: String, + study: String, ) -> Result { let mut user: candidate::ActiveModel = user.into(); - user.name = Set(Some(details.name)); - user.surname = Set(Some(details.surname)); - user.birthplace = Set(Some(details.birthplace)); - user.birthdate = Set(Some(details.birthdate)); - user.address = Set(Some(details.address)); - user.telephone = Set(Some(details.telephone)); - user.citizenship = Set(Some(details.citizenship)); - user.email = Set(Some(details.email)); - user.sex = Set(Some(details.sex)); - user.study = Set(Some(details.study)); + user.name = Set(Some(name)); + user.surname = Set(Some(surname)); + user.birthplace = Set(Some(birthplace)); + user.birthdate = Set(None); + user.address = Set(Some(address)); + user.telephone = Set(Some(telephone)); + user.citizenship = Set(Some(citizenship)); + user.email = Set(Some(email)); + user.sex = Set(Some(sex)); + user.study = Set(Some(study)); user.updated_at = Set(chrono::offset::Local::now().naive_local()); diff --git a/core/src/database/query/candidate.rs b/core/src/database/query/candidate.rs index a8b5552..72ef424 100644 --- a/core/src/database/query/candidate.rs +++ b/core/src/database/query/candidate.rs @@ -41,7 +41,7 @@ mod tests { code: Set("test".to_string()), public_key: Set("test".to_string()), private_key: Set("test".to_string()), - personal_identification_number: Set("test".to_string()), + personal_identification_number_hash: Set("test".to_string()), created_at: Set(chrono::offset::Local::now().naive_local()), updated_at: Set(chrono::offset::Local::now().naive_local()), ..Default::default() diff --git a/entity/src/candidate.rs b/entity/src/candidate.rs index 2806e76..81b7f72 100644 --- a/entity/src/candidate.rs +++ b/entity/src/candidate.rs @@ -17,9 +17,9 @@ pub struct Model { pub email: Option, pub sex: Option, pub study: Option, - pub personal_identification_number: String, #[sea_orm(column_type = "Text", nullable)] - pub personal_identification_number_hash: Option, + pub personal_identification_number: Option, + pub personal_identification_number_hash: String, pub public_key: String, pub private_key: String, #[sea_orm(default_value = false)] diff --git a/migration/src/m20221024_121621_create_candidate.rs b/migration/src/m20221024_121621_create_candidate.rs index 8743428..1d0e998 100644 --- a/migration/src/m20221024_121621_create_candidate.rs +++ b/migration/src/m20221024_121621_create_candidate.rs @@ -30,8 +30,8 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Candidate::Email).string()) .col(ColumnDef::new(Candidate::Sex).string()) .col(ColumnDef::new(Candidate::Study).string()) - .col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string().not_null()) - .col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text()) + .col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string()) + .col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text().not_null()) .col(ColumnDef::new(Candidate::PublicKey).string().not_null()) .col(ColumnDef::new(Candidate::PrivateKey).string().not_null()) .col(ColumnDef::new(Candidate::IsAdmin).boolean().not_null().default(false)) From 05fe04ca967726df45703c447a2b1b678081d15a Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:01:06 +0100 Subject: [PATCH 04/10] dev: add working admin account for development only! --- .../src/m20221024_134454_insert_sample_admin.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/migration/src/m20221024_134454_insert_sample_admin.rs b/migration/src/m20221024_134454_insert_sample_admin.rs index 56f551c..4bee191 100644 --- a/migration/src/m20221024_134454_insert_sample_admin.rs +++ b/migration/src/m20221024_134454_insert_sample_admin.rs @@ -15,11 +15,13 @@ impl Default for Migration { Self { candidate: candidate::ActiveModel { application: Set(1), - name: Set(Some("Administrátor Pepa".to_owned())), - public_key: Set("lorem ipsum".to_owned()), - private_key: Set("lorem ipsum".to_owned()), - code: Set("$argon2id$v=19$m=4096,t=3,p=1$V2M1eENXcnJvenhqTVF1Yw$xwriCZexpzF7Qtj9lwq0Sw".to_owned()), - personal_identification_number: Set("ADMIN".to_owned()), + name: Set(Some("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 + code: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()), + personal_identification_number_hash: Set("ADMIN".to_owned()), created_at: Set(Local::now().naive_local()), updated_at: Set(Local::now().naive_local()), is_admin: Set(true), From 8b3305dae747243455c7f8ae75902962a86a5dbd Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:01:22 +0100 Subject: [PATCH 05/10] feat: admin query --- core/src/database/query/admin.rs | 18 ++++++++++++++++++ core/src/database/query/mod.rs | 1 + 2 files changed, 19 insertions(+) create mode 100644 core/src/database/query/admin.rs diff --git a/core/src/database/query/admin.rs b/core/src/database/query/admin.rs new file mode 100644 index 0000000..fffdfd0 --- /dev/null +++ b/core/src/database/query/admin.rs @@ -0,0 +1,18 @@ +use crate::Query; + +use ::entity::{candidate, candidate::Entity as Admin}; +use sea_orm::*; + +impl Query { + pub async fn find_admin_by_id(db: &DbConn, id: i32) -> Result, DbErr> { + Admin::find_by_id(id).one(db).await + } + + pub async fn get_all_admin_public_keys(db: &DbConn) -> Result, DbErr> { + let admins = Admin::find().all(db).await?; + + let public_keys = admins.iter().map(|admin| admin.public_key.clone()).collect(); + + Ok(public_keys) + } +} diff --git a/core/src/database/query/mod.rs b/core/src/database/query/mod.rs index df4cb4a..73362b8 100644 --- a/core/src/database/query/mod.rs +++ b/core/src/database/query/mod.rs @@ -1,4 +1,5 @@ pub struct Query; pub mod candidate; +pub mod admin; pub mod session; \ No newline at end of file From 2833d9850bb92b9332c79a683058958fcf8153c8 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:01:36 +0100 Subject: [PATCH 06/10] feat: parent mutation --- core/src/database/mutation/mod.rs | 3 ++- core/src/database/mutation/parent.rs | 36 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 core/src/database/mutation/parent.rs diff --git a/core/src/database/mutation/mod.rs b/core/src/database/mutation/mod.rs index 633a325..80bf6e9 100644 --- a/core/src/database/mutation/mod.rs +++ b/core/src/database/mutation/mod.rs @@ -1,4 +1,5 @@ pub struct Mutation; pub mod session; -pub mod candidate; \ No newline at end of file +pub mod candidate; +pub mod parent; \ No newline at end of file diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs new file mode 100644 index 0000000..80c7a7e --- /dev/null +++ b/core/src/database/mutation/parent.rs @@ -0,0 +1,36 @@ +use crate::Mutation; + +use ::entity::parent::{self, Model}; +use sea_orm::*; + +impl Mutation { + pub async fn create_parent(db: &DbConn, application_id: i32) -> Result { + parent::ActiveModel { + application: Set(application_id), + created_at: Set(chrono::offset::Local::now().naive_local()), + updated_at: Set(chrono::offset::Local::now().naive_local()), + ..Default::default() + } + .insert(db) + .await + } + + pub async fn add_parent_details( + db: &DbConn, + user: Model, + name: String, + surname: String, + telephone: String, + email: String, + ) -> Result { + let mut user: parent::ActiveModel = user.into(); + user.name = Set(Some(name)); + user.surname = Set(Some(surname)); + user.telephone = Set(Some(telephone)); + user.email = Set(Some(email)); + + user.updated_at = Set(chrono::offset::Local::now().naive_local()); + + user.update(db).await + } +} From 0a33695210c0e5fb29fd7a0d722b29b25b3e653b Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:03:14 +0100 Subject: [PATCH 07/10] feat: remove structs in favor of function arguments, tokio::join for async encryption --- core/src/services/candidate_service.rs | 238 ++++++++++++++----------- 1 file changed, 137 insertions(+), 101 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 0bfa1d5..5aa2045 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,58 +1,17 @@ use chrono::NaiveDate; use entity::candidate; -use sea_orm::{DbConn, prelude::Uuid}; +use sea_orm::{prelude::Uuid, DbConn}; -use crate::{Mutation, crypto::{hash_password, self}, error::{ServiceError}, Query}; +use crate::{ + crypto::{self, hash_password}, + error::ServiceError, + Mutation, Query, +}; use super::session_service::SessionService; const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; -pub struct EncryptedAddUserData { - pub name: String, - pub surname: String, - pub birthplace: String, - pub birthdate: NaiveDate, - pub address: String, - pub telephone: String, - pub citizenship: String, - pub email: String, - pub sex: String, - pub study: String, -} - -pub struct AddUserDetailsForm { - pub application_id: i32, - - pub name: String, - pub surname: String, - pub birthplace: String, - pub birthdate: NaiveDate, - pub address: String, - pub telephone: String, - pub citizenship: String, - pub email: String, - pub sex: String, - pub study: String, -} - -impl AddUserDetailsForm { - pub async fn to_encrypted(self, recipients: Vec<&str>) -> EncryptedAddUserData { - EncryptedAddUserData { - name: crypto::encrypt_password_with_recipients(&self.name, &recipients).await.unwrap(), - surname: crypto::encrypt_password_with_recipients(&self.surname, &recipients).await.unwrap(), - birthplace: crypto::encrypt_password_with_recipients(&self.birthplace, &recipients).await.unwrap(), - birthdate: self.birthdate, // TODO: encrypt - address: crypto::encrypt_password_with_recipients(&self.address, &recipients).await.unwrap(), - telephone: crypto::encrypt_password_with_recipients(&self.telephone, &recipients).await.unwrap(), - citizenship: crypto::encrypt_password_with_recipients(&self.citizenship, &recipients).await.unwrap(), - email: crypto::encrypt_password_with_recipients(&self.email, &recipients).await.unwrap(), - sex: crypto::encrypt_password_with_recipients(&self.sex, &recipients).await.unwrap(), - study: crypto::encrypt_password_with_recipients(&self.study, &recipients).await.unwrap(), - } - } -} - pub struct CandidateService; impl CandidateService { @@ -65,68 +24,132 @@ impl CandidateService { db: &DbConn, application_id: i32, plain_text_password: &String, - personal_id_number: String - ) -> Result{ + personal_id_number: String, + ) -> Result { // Check if application id starts with 101, 102 or 103 if !CandidateService::is_application_id_valid(application_id) { - return Err(ServiceError::InvalidApplicationId) + return Err(ServiceError::InvalidApplicationId); } // Check if user with that application id already exists - if Query::find_candidate_by_id(db, application_id).await.unwrap().is_some() { - return Err(ServiceError::UserAlreadyExists) + if Query::find_candidate_by_id(db, application_id) + .await + .unwrap() + .is_some() + { + return Err(ServiceError::UserAlreadyExists); } // TODO: unwrap pro testing.. - let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap(); + 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_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( + let hashed_personal_id_number = hash_password(personal_id_number).await.unwrap(); + /* let encrypted_personal_id_number = crypto::encrypt_password_with_recipients( &personal_id_number, &vec![&pubkey] - ).await.unwrap(); + ).await.unwrap(); */ Mutation::create_candidate( db, application_id, hashed_password, - encrypted_personal_id_number, + hashed_personal_id_number, pubkey, - encrypted_priv_key + encrypted_priv_key, ) - .await - .map_err(|_| ServiceError::DbError) + .await + .map_err(|_| ServiceError::DbError) } pub async fn add_user_details( db: &DbConn, - details: AddUserDetailsForm, + application_id: i32, + name: String, + surname: String, + birthplace: String, + birthdate: String, + address: String, + telephone: String, + citizenship: String, + email: String, + sex: String, + study: String, ) -> Result { - let user = Query::find_candidate_by_id(db, details.application_id).await.unwrap().unwrap(); - let recipients = vec![&*user.public_key]; - let encrypted = details.to_encrypted(recipients).await; - Mutation::add_user_details(db, user, encrypted).await + let user = Query::find_candidate_by_id(db, application_id) + .await? + .unwrap(); + + let admin_public_keys = Query::get_all_admin_public_keys(db).await?; + let mut admin_public_keys_refrence: Vec<&str> = admin_public_keys.iter().map(|s| &**s).collect(); + + let mut recipients = vec![&*user.public_key]; + + recipients.append(&mut admin_public_keys_refrence); + + let ( + enc_name, + enc_surname, + enc_birthplace, + enc_birthdate, + enc_address, + enc_telephone, + enc_citizenship, + enc_email, + enc_sex, + enc_study, + ) = tokio::join!( + crypto::encrypt_password_with_recipients(&name, &recipients), + crypto::encrypt_password_with_recipients(&surname, &recipients), + crypto::encrypt_password_with_recipients(&birthplace, &recipients), + crypto::encrypt_password_with_recipients(&birthdate, &recipients), + crypto::encrypt_password_with_recipients(&address, &recipients), + crypto::encrypt_password_with_recipients(&telephone, &recipients), + crypto::encrypt_password_with_recipients(&citizenship, &recipients), + crypto::encrypt_password_with_recipients(&email, &recipients), + crypto::encrypt_password_with_recipients(&sex, &recipients), + crypto::encrypt_password_with_recipients(&study, &recipients), + ); + + Mutation::add_candidate_details( + db, + user, + enc_name.unwrap(), + enc_surname.unwrap(), + enc_birthplace.unwrap(), + enc_birthdate.unwrap(), + enc_address.unwrap(), + enc_telephone.unwrap(), + enc_citizenship.unwrap(), + enc_email.unwrap(), + enc_sex.unwrap(), + enc_study.unwrap(), + ) + .await } pub async fn login( db: &DbConn, user_id: i32, password: String, - ip_addr: 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 { + pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result { SessionService::auth_user_session(db, session_uuid).await } 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? + if s.len() <= 3 { + // TODO: does the field of study prefix have to be exactly 6 digits? return false; } let field_of_study_prefix = &s[0..3]; @@ -134,7 +157,6 @@ impl CandidateService { } } - #[cfg(test)] mod tests { use chrono::NaiveDate; @@ -142,8 +164,6 @@ mod tests { use crate::{crypto, services::candidate_service::CandidateService}; - use super::AddUserDetailsForm; - #[tokio::test] async fn test_application_id_validation() { assert!(CandidateService::is_application_id_valid(101_101)); @@ -158,16 +178,17 @@ mod tests { #[cfg(test)] async fn get_memory_sqlite_connection() -> DbConn { use entity::candidate; - use sea_orm::{DbBackend, sea_query::TableCreateStatement, ConnectionTrait}; use sea_orm::Schema; - + use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; 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.execute(db.get_database_backend().build(&stmt)) + .await + .unwrap(); db } @@ -179,41 +200,56 @@ mod tests { let secret_message = "trnka".to_string(); - - let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()).await.ok().unwrap(); + let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) + .await + .ok() + .unwrap(); - let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key]).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 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(); + let decrypted_message = + crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text) + .await + .unwrap(); assert_eq!(secret_message, decrypted_message); - } #[tokio::test] async fn test_put_user_data() { let db = get_memory_sqlite_connection().await; let plain_text_password = "test".to_string(); - let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()).await.ok().unwrap(); + let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) + .await + .ok() + .unwrap(); - let form = AddUserDetailsForm { - application_id: candidate.application, - name: "test".to_string(), - surname: "a".to_string(), - birthplace: "b".to_string(), - birthdate: NaiveDate::from_ymd(1999, 1, 1), - address: "test".to_string(), - telephone: "test".to_string(), - citizenship: "test".to_string(), - email: "test".to_string(), - sex: "test".to_string(), - study: "test".to_string(), - }; - - let candidate = CandidateService::add_user_details(&db, form).await.ok().unwrap(); + let candidate = CandidateService::add_user_details( + &db, + candidate.application, + "test".to_string(), + "a".to_string(), + "b".to_string(), + NaiveDate::from_ymd(1999, 1, 1).to_string(), + "test".to_string(), + "test".to_string(), + "test".to_string(), + "test".to_string(), + "test".to_string(), + "test".to_string(), + ) + .await + .ok() + .unwrap(); assert!(candidate.name.is_some()); } -} \ No newline at end of file +} From 5ef966341f88e96e6c85abcc503d2f89f3165aa0 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:25:29 +0100 Subject: [PATCH 08/10] feat: improve error handling, add crypto errors --- core/src/error.rs | 4 +++ core/src/services/candidate_service.rs | 47 ++++++++++++++++---------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/core/src/error.rs b/core/src/error.rs index d145b37..5fb8cfc 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -9,6 +9,8 @@ pub enum ServiceError { DbError, UserNotFoundByJwtId, UserNotFoundBySessionId, + CryptoHashFailed, + CryptoEncryptFailed, } impl ServiceError { @@ -24,6 +26,8 @@ impl ServiceError { ServiceError::DbError => (500, "Database error".to_string()), ServiceError::UserNotFoundByJwtId => (500, "User not found, please contact technical support".to_string()), ServiceError::UserNotFoundBySessionId => (500, "User not found, please contact technical support".to_string()), + ServiceError::CryptoHashFailed => (500, "Crypto hash failed, please contact technical support".to_string()), + ServiceError::CryptoEncryptFailed => (500, "Crypto encryption failed, please contact technical support".to_string()), } } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 5aa2045..1791655 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -40,17 +40,19 @@ impl CandidateService { return Err(ServiceError::UserAlreadyExists); } - // 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 Ok(hashed_password) = hash_password(plain_text_password.to_string()).await else { + return Err(ServiceError::CryptoHashFailed); + }; - let hashed_personal_id_number = hash_password(personal_id_number).await.unwrap(); + let (pubkey, priv_key_plain_text) = crypto::create_identity(); + + let Ok(encrypted_priv_key) = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await else { + return Err(ServiceError::CryptoEncryptFailed); + }; + + let Ok(hashed_personal_id_number) = hash_password(personal_id_number).await else { + return Err(ServiceError::CryptoHashFailed); + }; /* let encrypted_personal_id_number = crypto::encrypt_password_with_recipients( &personal_id_number, &vec![&pubkey] ).await.unwrap(); */ @@ -80,15 +82,23 @@ impl CandidateService { email: String, sex: String, study: String, - ) -> Result { - let user = Query::find_candidate_by_id(db, application_id) - .await? - .unwrap(); + ) -> Result { + let Ok(user) = Query::find_candidate_by_id(db, application_id).await else { + return Err(ServiceError::DbError); + }; - let admin_public_keys = Query::get_all_admin_public_keys(db).await?; - let mut admin_public_keys_refrence: Vec<&str> = admin_public_keys.iter().map(|s| &**s).collect(); + let Some(user_unwrapped) = user else { + return Err(ServiceError::UserNotFound); + }; - let mut recipients = vec![&*user.public_key]; + let Ok(admin_public_keys) = Query::get_all_admin_public_keys(db).await else { + return Err(ServiceError::DbError); + }; + + let mut admin_public_keys_refrence: Vec<&str> = + admin_public_keys.iter().map(|s| &**s).collect(); + + let mut recipients = vec![&*user_unwrapped.public_key]; recipients.append(&mut admin_public_keys_refrence); @@ -118,7 +128,7 @@ impl CandidateService { Mutation::add_candidate_details( db, - user, + user_unwrapped, enc_name.unwrap(), enc_surname.unwrap(), enc_birthplace.unwrap(), @@ -131,6 +141,7 @@ impl CandidateService { enc_study.unwrap(), ) .await + .map_err(|_| ServiceError::DbError) } pub async fn login( From 1073fd4d72e414b50e4adaabc95e35a979ef5640 Mon Sep 17 00:00:00 2001 From: EETagent Date: Sat, 5 Nov 2022 17:43:13 +0100 Subject: [PATCH 09/10] fix: fix admin guard to really check for admin role --- api/src/guards/request/auth/admin.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/src/guards/request/auth/admin.rs b/api/src/guards/request/auth/admin.rs index b22a145..7933258 100644 --- a/api/src/guards/request/auth/admin.rs +++ b/api/src/guards/request/auth/admin.rs @@ -30,7 +30,13 @@ impl<'r> FromRequest<'r> for AdminAuth { let session = AdminService::auth(conn, uuid).await; match session { - Ok(model) => Outcome::Success(AdminAuth(model)), + Ok(model) => { + if model.is_admin { + Outcome::Success(AdminAuth(model)) + } else { + Outcome::Failure((Status::Forbidden, None)) + } + }, Err(e) => Outcome::Failure( (Status::from_code(e.code()).unwrap_or(Status::InternalServerError), None) ), From ae2198d2131fed1d8df9ff436abf24484de9668e Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 6 Nov 2022 00:27:33 +0100 Subject: [PATCH 10/10] Revert "fix: fix admin guard to really check for admin role" This reverts commit 1073fd4d72e414b50e4adaabc95e35a979ef5640. --- api/src/guards/request/auth/admin.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/api/src/guards/request/auth/admin.rs b/api/src/guards/request/auth/admin.rs index 7933258..b22a145 100644 --- a/api/src/guards/request/auth/admin.rs +++ b/api/src/guards/request/auth/admin.rs @@ -30,13 +30,7 @@ impl<'r> FromRequest<'r> for AdminAuth { let session = AdminService::auth(conn, uuid).await; match session { - Ok(model) => { - if model.is_admin { - Outcome::Success(AdminAuth(model)) - } else { - Outcome::Failure((Status::Forbidden, None)) - } - }, + Ok(model) => Outcome::Success(AdminAuth(model)), Err(e) => Outcome::Failure( (Status::from_code(e.code()).unwrap_or(Status::InternalServerError), None) ),