diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 68a6062..9d110df 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -75,22 +75,14 @@ pub async fn create_candidate( let plain_text_password = random_8_char_string(); - let candidate = CandidateService::create( + CandidateService::create( db, form.application_id, &plain_text_password, form.personal_id_number, ) - .await; - - if candidate.is_err() { - // TODO cleanup - let e = candidate.err().unwrap(); - return Err(Custom( - Status::from_code(e.code()).unwrap_or_default(), - e.message(), - )); - } + .await + .map_err(|e| Custom(Status::InternalServerError, e.to_string()))?; Ok(plain_text_password) } diff --git a/core/src/candidate_details.rs b/core/src/candidate_details.rs index 027cf5f..7b20ab2 100644 --- a/core/src/candidate_details.rs +++ b/core/src/candidate_details.rs @@ -1,11 +1,12 @@ use chrono::{NaiveDate}; -use entity::candidate; +use entity::{candidate, parent}; use serde::{Serialize, Deserialize}; use crate::{error::ServiceError, crypto}; pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d"; +#[derive(Clone)] pub struct EncryptedString(String); impl EncryptedString { @@ -56,7 +57,9 @@ impl TryFrom> for EncryptedString { // TODO: take a look at th } } -pub(crate) struct EncryptedCandidateDetails { +#[derive(Clone)] +pub struct EncryptedCandidateDetails { + // Candidate pub name: EncryptedString, pub surname: EncryptedString, pub birthplace: EncryptedString, @@ -67,6 +70,12 @@ pub(crate) struct EncryptedCandidateDetails { pub email: EncryptedString, pub sex: EncryptedString, pub study: EncryptedString, + + // Parent + pub parent_name: EncryptedString, + pub parent_surname: EncryptedString, + pub parent_telephone: EncryptedString, + pub parent_email: EncryptedString, } impl EncryptedCandidateDetails { @@ -76,13 +85,18 @@ impl EncryptedCandidateDetails { EncryptedString::new(&form.name, &recipients), EncryptedString::new(&form.surname, &recipients), EncryptedString::new(&form.birthplace, &recipients), - EncryptedString::new(&birthdate_str, &recipients), // TODO + 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.study, &recipients), + + EncryptedString::new(&form.parent_name, &recipients), + EncryptedString::new(&form.parent_surname, &recipients), + EncryptedString::new(&form.parent_telephone, &recipients), + EncryptedString::new(&form.parent_email, &recipients), )?; Ok(EncryptedCandidateDetails { @@ -96,21 +110,31 @@ impl EncryptedCandidateDetails { email: d.7, sex: d.8, study: d.9, + + parent_name: d.10, + parent_surname: d.11, + parent_telephone: d.12, + parent_email: d.13, }) } pub async fn decrypt(self, priv_key: String) -> Result { let d = tokio::try_join!( - self.name.decrypt(&priv_key), - self.surname.decrypt(&priv_key), - self.birthplace.decrypt(&priv_key), - self.birthdate.decrypt(&priv_key), - self.address.decrypt(&priv_key), - self.telephone.decrypt(&priv_key), - self.citizenship.decrypt(&priv_key), - self.email.decrypt(&priv_key), - self.sex.decrypt(&priv_key), - self.study.decrypt(&priv_key), + self.name.decrypt(&priv_key), // 0 + self.surname.decrypt(&priv_key), // 1 + self.birthplace.decrypt(&priv_key), // 2 + self.birthdate.decrypt(&priv_key), // 3 + self.address.decrypt(&priv_key), // 4 + self.telephone.decrypt(&priv_key), // 5 + self.citizenship.decrypt(&priv_key), // 6 + self.email.decrypt(&priv_key), // 7 + self.sex.decrypt(&priv_key), // 8 + self.study.decrypt(&priv_key), // 9 + + self.parent_name.decrypt(&priv_key), + self.parent_surname.decrypt(&priv_key), + self.parent_telephone.decrypt(&priv_key), + self.parent_email.decrypt(&priv_key), )?; Ok(CandidateDetails { @@ -124,14 +148,19 @@ impl EncryptedCandidateDetails { email: d.7, sex: d.8, study: d.9, + + parent_name: d.10, + parent_surname: d.11, + parent_telephone: d.12, + parent_email: d.13, }) } } -impl TryFrom for EncryptedCandidateDetails { +impl TryFrom<(candidate::Model, parent::Model)> for EncryptedCandidateDetails { type Error = ServiceError; - fn try_from(candidate: candidate::Model) -> Result { + fn try_from((candidate, parent): (candidate::Model, parent::Model)) -> Result { Ok(EncryptedCandidateDetails { name: EncryptedString::try_from(candidate.name)?, surname: EncryptedString::try_from(candidate.surname)?, @@ -143,12 +172,18 @@ impl TryFrom for EncryptedCandidateDetails { email: EncryptedString::try_from(candidate.email)?, sex: EncryptedString::try_from(candidate.sex)?, study: EncryptedString::try_from(candidate.study)?, + + parent_name: EncryptedString::try_from(parent.name)?, + parent_surname: EncryptedString::try_from(parent.surname)?, + parent_telephone: EncryptedString::try_from(parent.telephone)?, + parent_email: EncryptedString::try_from(parent.email)?, }) } } #[derive(Debug, Serialize, Deserialize)] pub struct CandidateDetails { + // Candidate pub name: String, pub surname: String, pub birthplace: String, @@ -159,4 +194,10 @@ pub struct CandidateDetails { pub email: String, pub sex: String, pub study: String, + + // Parent + pub parent_name: String, + pub parent_surname: String, + pub parent_telephone: String, + pub parent_email: String, } \ No newline at end of file diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs index 80c7a7e..2855a29 100644 --- a/core/src/database/mutation/parent.rs +++ b/core/src/database/mutation/parent.rs @@ -1,4 +1,4 @@ -use crate::Mutation; +use crate::{Mutation, candidate_details::EncryptedCandidateDetails}; use ::entity::parent::{self, Model}; use sea_orm::*; @@ -17,17 +17,14 @@ impl Mutation { pub async fn add_parent_details( db: &DbConn, - user: Model, - name: String, - surname: String, - telephone: String, - email: String, + parent: Model, + enc_details: EncryptedCandidateDetails, // TODO: use seperate struct?? ) -> 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)); + let mut user: parent::ActiveModel = parent.into(); + user.name = Set(Some(enc_details.parent_name.into())); + user.surname = Set(Some(enc_details.parent_surname.into())); + user.telephone = Set(Some(enc_details.parent_telephone.into())); + user.email = Set(Some(enc_details.parent_email.into())); user.updated_at = Set(chrono::offset::Local::now().naive_local()); diff --git a/core/src/database/query/mod.rs b/core/src/database/query/mod.rs index 73362b8..90575e6 100644 --- a/core/src/database/query/mod.rs +++ b/core/src/database/query/mod.rs @@ -2,4 +2,5 @@ pub struct Query; pub mod candidate; pub mod admin; -pub mod session; \ No newline at end of file +pub mod session; +pub mod parent; \ No newline at end of file diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs new file mode 100644 index 0000000..7fc0429 --- /dev/null +++ b/core/src/database/query/parent.rs @@ -0,0 +1,17 @@ + +use entity::parent::Model; +use entity::parent::Entity; +use sea_orm::{DbConn, DbErr}; +use sea_orm::EntityTrait; + +use crate::Query; + +impl Query { + pub async fn find_parent_by_id( + db: &DbConn, + application_id: i32, + ) -> Result, DbErr> { + + Entity::find_by_id(application_id).one(db).await + } +} \ No newline at end of file diff --git a/core/src/error.rs b/core/src/error.rs index 428b0f9..0627fdc 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -5,7 +5,8 @@ pub enum ServiceError { ExpiredSession, JwtError, UserAlreadyExists, - UserNotFound, + CandidateNotFound, + ParentNotFound, DbError, UserNotFoundByJwtId, UserNotFoundBySessionId, @@ -24,7 +25,8 @@ impl ServiceError { ServiceError::ExpiredSession => (401, "Session expired, please login again".to_string()), ServiceError::JwtError => (500, "Error while encoding JWT".to_string()), ServiceError::UserAlreadyExists => (409, "User already exists".to_string()), - ServiceError::UserNotFound => (404, "User not found".to_string()), + ServiceError::CandidateNotFound => (404, "User not found".to_string()), + ServiceError::ParentNotFound => (500, "Parent not found".to_string()), 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()), diff --git a/core/src/services/admin_service.rs b/core/src/services/admin_service.rs index d0d9cdd..12dd925 100644 --- a/core/src/services/admin_service.rs +++ b/core/src/services/admin_service.rs @@ -20,7 +20,7 @@ impl AdminService { }; let Some(admin) = admin else { - return Err(ServiceError::UserNotFound); + return Err(ServiceError::CandidateNotFound); }; let private_key_encrypted = admin.private_key; diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs new file mode 100644 index 0000000..664bae6 --- /dev/null +++ b/core/src/services/application_service.rs @@ -0,0 +1,5 @@ +/* pub struct ApplicationService; + +impl ApplicationService { + pub fn create +} */ \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 3cd7651..d0ed56a 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,4 +1,4 @@ -use entity::candidate; +use entity::{candidate}; use sea_orm::{prelude::Uuid, DbConn}; use crate::{ @@ -7,7 +7,7 @@ use crate::{ Mutation, Query, candidate_details::{CandidateDetails, EncryptedCandidateDetails}, }; -use super::session_service::{AdminUser, SessionService}; +use super::{session_service::{AdminUser, SessionService}, parent_service::ParentService}; const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"]; @@ -52,9 +52,9 @@ impl CandidateService { 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(); */ + + ParentService::create_parent(db, application_id) + .await?; Mutation::create_candidate( db, @@ -64,8 +64,8 @@ impl CandidateService { pubkey, encrypted_priv_key, ) - .await - .map_err(|_| ServiceError::DbError) + .await + .map_err(|_| ServiceError::DbError) } pub async fn add_candidate_details( @@ -85,21 +85,23 @@ impl CandidateService { recipients.append(&mut admin_public_keys_refrence); let enc_details = EncryptedCandidateDetails::new(form, recipients).await?; - - Mutation::add_candidate_details(db, candidate, enc_details) + + ParentService::add_parent_details(db, candidate.application, enc_details.clone()).await?; + Mutation::add_candidate_details(db, candidate, enc_details.clone()) .await .map_err(|_| ServiceError::DbError) } pub async fn decrypt_details( db: &DbConn, - candidate_id: i32, + application_id: i32, password: String, ) -> Result { - let candidate = match Query::find_candidate_by_id(db, candidate_id).await { + let candidate = match Query::find_candidate_by_id(db, application_id).await { Ok(candidate) => candidate.unwrap(), Err(_) => return Err(ServiceError::DbError), // TODO: logging }; + let parent = Query::find_parent_by_id(db, application_id).await.unwrap().unwrap(); match crypto::verify_password((&password).to_string(), candidate.code.clone()).await { Ok(valid) => { @@ -114,7 +116,7 @@ impl CandidateService { .await .ok() .unwrap(); - let enc_details = EncryptedCandidateDetails::try_from(candidate)?; + let enc_details = EncryptedCandidateDetails::try_from((candidate, parent))?; enc_details.decrypt(dec_priv_key).await } @@ -162,7 +164,7 @@ impl CandidateService { }; let Some(candidate) = candidate else { - return Err(ServiceError::UserNotFound); + return Err(ServiceError::CandidateNotFound); }; let private_key_encrypted = candidate.private_key; @@ -221,7 +223,7 @@ mod tests { use crate::{ crypto, - services::candidate_service::{CandidateService, CandidateDetails}, + services::candidate_service::{CandidateService, CandidateDetails}, Query, Mutation, }; use super::EncryptedCandidateDetails; @@ -239,7 +241,7 @@ mod tests { #[cfg(test)] async fn get_memory_sqlite_connection() -> DbConn { - use entity::{admin, candidate}; + use entity::{admin, candidate, parent}; use sea_orm::Schema; use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; @@ -248,8 +250,8 @@ mod tests { let schema = Schema::new(DbBackend::Sqlite); let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity); - let stmt2: TableCreateStatement = schema.create_table_from_entity(admin::Entity); + let stmt3: TableCreateStatement = schema.create_table_from_entity(parent::Entity); db.execute(db.get_database_backend().build(&stmt)) .await @@ -257,6 +259,9 @@ mod tests { db.execute(db.get_database_backend().build(&stmt2)) .await .unwrap(); + db.execute(db.get_database_backend().build(&stmt3)) + .await + .unwrap(); db } @@ -268,6 +273,9 @@ mod tests { let secret_message = "trnka".to_string(); + Mutation::create_parent(&db, 1) + .await.unwrap(); + let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string()) .await .ok() @@ -303,7 +311,7 @@ mod tests { let form = CandidateDetails { name: "test".to_string(), - surname: "a".to_string(), + surname: "aaa".to_string(), birthplace: "b".to_string(), birthdate: NaiveDate::from_ymd(1999, 1, 1), address: "test".to_string(), @@ -312,10 +320,15 @@ mod tests { email: "test".to_string(), sex: "test".to_string(), study: "test".to_string(), + parent_name: "test".to_string(), + parent_surname: "test".to_string(), + parent_telephone: "test".to_string(), + parent_email: "test".to_string(), + }; + CandidateService::add_candidate_details(&db, candidate, form) .await - .ok() .unwrap() } @@ -331,16 +344,15 @@ mod tests { let password = "test".to_string(); let db = get_memory_sqlite_connection().await; let enc_candidate = put_user_data(&db).await; + let enc_parent = Query::find_parent_by_id(&db, enc_candidate.application).await.unwrap().unwrap(); let dec_priv_key = crypto::decrypt_password(enc_candidate.private_key.clone(), password) .await .unwrap(); - let dec_candidate = EncryptedCandidateDetails::try_from(enc_candidate) - .unwrap() - .decrypt(dec_priv_key) - .await - .unwrap(); + let enc_details = EncryptedCandidateDetails::try_from((enc_candidate, enc_parent)).ok().unwrap(); + let dec_details = enc_details.decrypt(dec_priv_key).await.ok().unwrap(); - assert_eq!(dec_candidate.name, "test"); + assert_eq!(dec_details.name, "test"); // TODO: test every element + assert_eq!(dec_details.parent_surname, "test"); } } diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index d6cd137..28fe987 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -1,3 +1,5 @@ pub mod session_service; pub mod candidate_service; -pub mod admin_service; \ No newline at end of file +pub mod admin_service; +pub mod parent_service; +pub mod application_service; \ No newline at end of file diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs new file mode 100644 index 0000000..c2877ca --- /dev/null +++ b/core/src/services/parent_service.rs @@ -0,0 +1,36 @@ +use entity::{parent}; +use sea_orm::DbConn; + +use crate::{error::ServiceError, Mutation, candidate_details::EncryptedCandidateDetails, Query}; + +pub struct ParentService; + +impl ParentService { + pub async fn create_parent( + db: &DbConn, + application_id: i32, + ) -> Result { + let parent = Mutation::create_parent(db, application_id) + .await + .map_err(|_| ServiceError::DbError)?; + + Ok(parent) + } + + pub async fn add_parent_details( + db: &DbConn, + application_id: i32, + enc_details: EncryptedCandidateDetails, + ) -> Result { + let parent = Query::find_parent_by_id(db, application_id) + .await + .map_err(|_| ServiceError::DbError)? + .ok_or(ServiceError::ParentNotFound)?; + + let parent = Mutation::add_parent_details(db, parent, enc_details) + .await + .map_err(|_| ServiceError::DbError)?; + + Ok(parent) + } +} \ No newline at end of file diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 2474b29..35363ac 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -57,7 +57,7 @@ impl SessionService { let candidate = match Query::find_candidate_by_id(db, user_id.unwrap()).await { Ok(candidate) => match candidate { Some(candidate) => candidate, - None => return Err(ServiceError::UserNotFound), + None => return Err(ServiceError::CandidateNotFound), }, Err(_) => return Err(ServiceError::DbError), }; @@ -78,7 +78,7 @@ impl SessionService { let admin = match Query::find_admin_by_id(db, admin_id.unwrap()).await { Ok(admin) => match admin { Some(admin) => admin, - None => return Err(ServiceError::UserNotFound), + None => return Err(ServiceError::CandidateNotFound), }, Err(_) => return Err(ServiceError::DbError), }; @@ -162,7 +162,7 @@ impl SessionService { #[cfg(test)] mod tests { - use entity::{admin, candidate, session}; + use entity::{admin, candidate, session, parent}; use sea_orm::{ prelude::Uuid, sea_query::TableCreateStatement, ConnectionTrait, Database, DbBackend, @@ -183,6 +183,7 @@ mod tests { let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity); let stmt2: TableCreateStatement = schema.create_table_from_entity(admin::Entity); let stmt3: TableCreateStatement = schema.create_table_from_entity(session::Entity); + let stmt4: TableCreateStatement = schema.create_table_from_entity(parent::Entity); db.execute(db.get_database_backend().build(&stmt)) .await .unwrap(); @@ -192,6 +193,9 @@ mod tests { db.execute(db.get_database_backend().build(&stmt3)) .await .unwrap(); + db.execute(db.get_database_backend().build(&stmt4)) + .await + .unwrap(); db } @@ -218,9 +222,8 @@ mod tests { async fn test_candidate_session_correct_password() { let db = &get_memory_sqlite_connection().await; - CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string()) + CandidateService::create(db, 103151, &"Tajny_kod".to_string(), "".to_string()) .await - .ok() .unwrap(); // correct password @@ -231,9 +234,8 @@ mod tests { "Tajny_kod".to_string(), "127.0.0.1".to_string(), ) - .await - .ok() - .unwrap(); + .await + .unwrap(); // println!("{}", session.err().unwrap().1); assert!( SessionService::auth_user_session(db, Uuid::parse_str(&session).unwrap()) @@ -249,7 +251,6 @@ mod tests { let candidate_form = CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string()) .await - .ok() .unwrap(); // incorrect password diff --git a/entity/src/candidate.rs b/entity/src/candidate.rs index 002093d..6a044fa 100644 --- a/entity/src/candidate.rs +++ b/entity/src/candidate.rs @@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "candidate")] pub struct Model { - #[sea_orm(column_type = "Integer", primary_key, auto_increment = false)] + #[sea_orm(primary_key, auto_increment = false)] pub application: i32, pub code: String, pub name: Option, @@ -17,8 +17,8 @@ pub struct Model { pub email: Option, pub sex: Option, pub study: Option, - #[sea_orm(column_type = "Text", nullable)] pub personal_identification_number: Option, + #[sea_orm(column_type = "Text")] pub personal_identification_number_hash: String, pub public_key: String, pub private_key: String, @@ -28,18 +28,10 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { - #[sea_orm(has_one = "super::parent::Entity")] - Parent, #[sea_orm(has_many = "super::session::Entity")] Session, } -impl Related for Entity { - fn to() -> RelationDef { - Relation::Parent.def() - } -} - impl Related for Entity { fn to() -> RelationDef { Relation::Session.def() diff --git a/entity/src/parent.rs b/entity/src/parent.rs index 4d107af..7e1b00c 100644 --- a/entity/src/parent.rs +++ b/entity/src/parent.rs @@ -14,19 +14,6 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::candidate::Entity", - from = "Column::Application", - to = "super::candidate::Column::Application" - )] - Candidate, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Candidate.def() - } -} +pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 85e2a9c..511d8c0 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -7,7 +7,6 @@ mod m20221024_134454_insert_sample_admin; mod m20221025_154422_create_session; mod m20221027_194728_session_create_user_fk; mod m20221028_194728_session_create_admin_fk; -mod m20221030_133428_parent_create_candidate_fk; pub struct Migrator; #[async_trait::async_trait] @@ -21,7 +20,6 @@ impl MigratorTrait for Migrator { Box::new(m20221025_154422_create_session::Migration), Box::new(m20221027_194728_session_create_user_fk::Migration), Box::new(m20221028_194728_session_create_admin_fk::Migration), - Box::new(m20221030_133428_parent_create_candidate_fk::Migration), ] } } diff --git a/migration/src/m20221030_133428_parent_create_candidate_fk.rs b/migration/src/m20221030_133428_parent_create_candidate_fk.rs deleted file mode 100644 index c390fa0..0000000 --- a/migration/src/m20221030_133428_parent_create_candidate_fk.rs +++ /dev/null @@ -1,26 +0,0 @@ -use sea_orm_migration::prelude::*; - -use crate::{m20221024_124701_create_parent::Parent, m20221024_121621_create_candidate::Candidate}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_foreign_key(ForeignKey::create() - .name("candidate_fk") - .from(Parent::Table, Parent::Application) - .to(Candidate::Table, Candidate::Application) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - .to_owned()).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_foreign_key(ForeignKey::drop() - .name("candidate_fk") - .table(Parent::Table) - .to_owned()).await - } -} \ No newline at end of file