Merge pull request #40 from EETagent/parent_details

Parent details, massive refactoring
This commit is contained in:
Sebastian Pravda 2022-11-12 11:50:38 +01:00 committed by GitHub
commit 72ae61637f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 498 additions and 348 deletions

View file

@ -51,7 +51,7 @@ impl<'r> FromRequest<'r> for AdminAuth {
match session {
Ok(model) => Outcome::Success(AdminAuth(model, private_key.to_string())),
Err(e) => Outcome::Failure(
(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), None)
(Status::from_code(e.code()).unwrap_or(Status::Unauthorized), None)
),
}

View file

@ -2,7 +2,7 @@ use std::net::SocketAddr;
use portfolio_core::{
crypto::random_8_char_string,
services::{admin_service::AdminService, candidate_service::CandidateService},
services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService},
};
use requests::{AdminLoginRequest, RegisterRequest};
use rocket::http::{Cookie, Status, CookieJar};
@ -75,22 +75,14 @@ pub async fn create_candidate(
let plain_text_password = random_8_char_string();
let candidate = CandidateService::create(
ApplicationService::create_candidate_with_parent(
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)
}

View file

@ -1,6 +1,8 @@
use std::net::SocketAddr;
use portfolio_core::services::candidate_service::{CandidateService, UserDetails};
use portfolio_core::candidate_details::ApplicationDetails;
use portfolio_core::services::application_service::ApplicationService;
use portfolio_core::services::candidate_service::{CandidateService};
use requests::LoginRequest;
use rocket::http::{Cookie, CookieJar, Status};
use rocket::response::status::Custom;
@ -59,18 +61,18 @@ pub async fn whoami(session: CandidateAuth) -> Result<String, Custom<String>> {
#[post("/details", data = "<details>")]
pub async fn fill_details(
conn: Connection<'_, Db>,
details: Json<UserDetails>,
details: Json<ApplicationDetails>,
session: CandidateAuth,
) -> Result<String, Custom<String>> {
let db = conn.into_inner();
let form = details.into_inner();
let candidate: entity::candidate::Model = session.into();
let candidate: entity::candidate::Model = session.into(); // TODO: don't return candidate from session
let candidate = CandidateService::add_user_details(db, candidate, form).await;
let candidate_parent = ApplicationService::add_all_details(db, candidate.application, form).await;
if candidate.is_err() {
if candidate_parent.is_err() {
// TODO cleanup
let e = candidate.err().unwrap();
let e = candidate_parent.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.message(),
@ -85,13 +87,13 @@ pub async fn get_details(
conn: Connection<'_, Db>,
password_form: Json<PasswordRequest>,
session: CandidateAuth,
) -> Result<Json<UserDetails>, Custom<String>> {
) -> Result<Json<ApplicationDetails>, Custom<String>> {
let db = conn.into_inner();
let candidate: entity::candidate::Model = session.into();
let password = password_form.password.clone();
// let handle = tokio::spawn(async move {
let details = CandidateService::decrypt_details(db, candidate.application, password)
let details = ApplicationService::decrypt_all_details(db, candidate.application, password)
.await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap_or_default(), e.message()));

View file

@ -0,0 +1,203 @@
use chrono::{NaiveDate};
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 {
pub async fn new(s: &str, recipients: &Vec<&str>) -> Result<Self, ServiceError> {
match crypto::encrypt_password_with_recipients(&s, &recipients).await{
Ok(encrypted) => Ok(Self(encrypted)),
Err(_) => Err(ServiceError::CryptoEncryptFailed),
}
}
pub async fn decrypt(&self, private_key: &String) -> Result<String, ServiceError> {
match crypto::decrypt_password_with_private_key(&self.0, private_key).await {
Ok(decrypted) => Ok(decrypted),
Err(_) => Err(ServiceError::CryptoDecryptFailed),
}
}
pub fn to_string(self) -> String {
self.0
}
}
impl Into<String> for EncryptedString {
fn into(self) -> String {
self.0
}
}
impl TryFrom<Option<String>> for EncryptedString {
type Error = ServiceError;
fn try_from(s: Option<String>) -> Result<Self, Self::Error> {
match s {
Some(s) => Ok(Self(s)),
None => Err(ServiceError::CandidateDetailsNotSet),
}
}
}
impl TryFrom<Option<NaiveDate>> for EncryptedString { // TODO: take a look at this
type Error = ServiceError;
fn try_from(d: Option<NaiveDate>) -> Result<Self, Self::Error> {
match d {
Some(d) => Ok(Self(d.to_string())),
None => Err(ServiceError::CandidateDetailsNotSet),
}
}
}
#[derive(Clone)]
pub struct EncryptedApplicationDetails {
// Candidate
pub name: EncryptedString,
pub surname: EncryptedString,
pub birthplace: EncryptedString,
pub birthdate: EncryptedString,
pub address: EncryptedString,
pub telephone: EncryptedString,
pub citizenship: EncryptedString,
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 EncryptedApplicationDetails {
pub async fn new(form: ApplicationDetails, recipients: Vec<&str>) -> Result<EncryptedApplicationDetails, ServiceError> {
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.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(EncryptedApplicationDetails {
name: d.0,
surname: d.1,
birthplace: d.2,
birthdate: d.3,
address: d.4,
telephone: d.5,
citizenship: d.6,
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<ApplicationDetails, ServiceError> {
let d = tokio::try_join!(
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(ApplicationDetails {
name: d.0,
surname: d.1,
birthplace: d.2,
birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO
address: d.4,
telephone: d.5,
citizenship: d.6,
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<(candidate::Model, parent::Model)> for EncryptedApplicationDetails {
type Error = ServiceError;
fn try_from((candidate, parent): (candidate::Model, parent::Model)) -> Result<Self, Self::Error> {
Ok(EncryptedApplicationDetails {
name: EncryptedString::try_from(candidate.name)?,
surname: EncryptedString::try_from(candidate.surname)?,
birthplace: EncryptedString::try_from(candidate.birthplace)?,
birthdate: EncryptedString::try_from(candidate.birthdate)?,
address: EncryptedString::try_from(candidate.address)?,
telephone: EncryptedString::try_from(candidate.telephone)?,
citizenship: EncryptedString::try_from(candidate.citizenship)?,
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 ApplicationDetails {
// Candidate
pub name: String,
pub surname: String,
pub birthplace: String,
pub birthdate: NaiveDate, // TODO: User NaiveDate or String?
pub address: String,
pub telephone: String,
pub citizenship: String,
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,
}

View file

@ -1,4 +1,4 @@
use crate::{Mutation, services::candidate_service::EncryptedUserDetails};
use crate::{Mutation, candidate_details::{EncryptedApplicationDetails}};
use ::entity::candidate::{self};
use sea_orm::{*};
@ -29,18 +29,19 @@ impl Mutation {
pub async fn add_candidate_details(
db: &DbConn,
user: candidate::Model,
enc_details: EncryptedUserDetails,
enc_details: EncryptedApplicationDetails,
) -> Result<candidate::Model, sea_orm::DbErr> {
let mut user: candidate::ActiveModel = user.into();
user.name = Set(Some(enc_details.name));
user.surname = Set(Some(enc_details.surname));
user.birthplace = Set(Some(enc_details.birthplace));
user.address = Set(Some(enc_details.address));
user.telephone = Set(Some(enc_details.telephone));
user.citizenship = Set(Some(enc_details.citizenship));
user.email = Set(Some(enc_details.email));
user.sex = Set(Some(enc_details.sex));
user.study = Set(Some(enc_details.study));
user.name = Set(Some(enc_details.name.into()));
user.surname = Set(Some(enc_details.surname.into()));
user.birthplace = Set(Some(enc_details.birthplace.into()));
user.birthdate = Set(Some(enc_details.birthdate.into()));
user.address = Set(Some(enc_details.address.into()));
user.telephone = Set(Some(enc_details.telephone.into()));
user.citizenship = Set(Some(enc_details.citizenship.into()));
user.email = Set(Some(enc_details.email.into()));
user.sex = Set(Some(enc_details.sex.into()));
user.study = Set(Some(enc_details.study.into()));
user.updated_at = Set(chrono::offset::Local::now().naive_local());

View file

@ -1,4 +1,4 @@
use crate::Mutation;
use crate::{Mutation, candidate_details::EncryptedApplicationDetails};
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: EncryptedApplicationDetails, // TODO: use seperate struct??
) -> Result<Model, sea_orm::DbErr> {
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());

View file

@ -2,4 +2,5 @@ pub struct Query;
pub mod candidate;
pub mod admin;
pub mod session;
pub mod session;
pub mod parent;

View file

@ -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<Option<Model>, DbErr> {
Entity::find_by_id(application_id).one(db).await
}
}

View file

@ -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()),

View file

@ -3,6 +3,7 @@ pub mod crypto;
pub mod filetype;
pub mod services;
pub mod error;
pub mod candidate_details;
pub use database::mutation::*;
pub use database::query::*;

View file

@ -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;
@ -55,7 +55,7 @@ impl AdminService {
match SessionService::auth_user_session(db, session_uuid).await {
Ok(user) => match user {
AdminUser::Admin(admin) => Ok(admin),
AdminUser::User(_) => Err(ServiceError::DbError),
AdminUser::Candidate(_) => Err(ServiceError::DbError),
},
Err(e) => Err(e),
}

View file

@ -0,0 +1,95 @@
use entity::{candidate, parent};
use sea_orm::DbConn;
use crate::{error::ServiceError, candidate_details::{ApplicationDetails, EncryptedApplicationDetails}, Query, crypto};
use super::{parent_service::ParentService, candidate_service::CandidateService};
pub struct ApplicationService;
impl ApplicationService {
pub async fn create_candidate_with_parent( // uchazeč s maminkou 👩‍🍼
db: &DbConn,
application_id: i32,
plain_text_password: &String,
personal_id_number: String,
) -> Result<(candidate::Model, parent::Model), ServiceError> {
Ok(
/* tokio::try_join!( // TODO: try_join! is not working
CandidateService::create(db, application_id, plain_text_password, personal_id_number),
ParentService::create(db, application_id)
)? */
(
CandidateService::create(db, application_id, plain_text_password, personal_id_number).await?,
ParentService::create(db, application_id).await?
)
)
}
pub async fn add_all_details(
db: &DbConn,
application: i32,
form: ApplicationDetails,
) -> Result<(candidate::Model, parent::Model), ServiceError> {
let candidate = Query::find_candidate_by_id(db, application)
.await
.map_err(|_| ServiceError::DbError)?
.ok_or(ServiceError::CandidateNotFound)?;
let parent = Query::find_parent_by_id(db, application)
.await
.map_err(|_| ServiceError::DbError)?
.ok_or(ServiceError::ParentNotFound)?;
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![&*candidate.public_key];
recipients.append(&mut admin_public_keys_refrence);
let enc_details = EncryptedApplicationDetails::new(form, recipients).await?;
Ok(
tokio::try_join!(
CandidateService::add_candidate_details(db, candidate, enc_details.clone()),
ParentService::add_parent_details(db, parent, enc_details.clone())
)?
)
}
pub async fn decrypt_all_details(
db: &DbConn,
application_id: i32,
password: String,
) -> Result<ApplicationDetails, ServiceError> {
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) => {
if !valid {
return Err(ServiceError::InvalidCredentials);
}
}
Err(_) => return Err(ServiceError::InvalidCredentials),
}
let dec_priv_key = crypto::decrypt_password(candidate.private_key.clone(), password)
.await
.ok()
.unwrap();
let enc_details = EncryptedApplicationDetails::try_from((candidate, parent))?;
enc_details.decrypt(dec_priv_key).await
}
}

View file

@ -1,188 +1,16 @@
use entity::candidate;
use entity::{candidate};
use sea_orm::{prelude::Uuid, DbConn};
use serde::{Deserialize, Serialize};
use crate::{
crypto::{self, hash_password},
error::ServiceError,
Mutation, Query,
Mutation, Query, candidate_details::{EncryptedApplicationDetails},
};
use super::session_service::{AdminUser, SessionService};
use super::{session_service::{AdminUser, SessionService}};
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
pub(crate) struct EncryptedUserDetails {
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 EncryptedUserDetails {
pub async fn encrypt_form(form: UserDetails, recipients: Vec<&str>) -> EncryptedUserDetails {
let (
Ok(name),
Ok(surname),
Ok(birthplace),
// Ok(enc_birthdate),
Ok(address),
Ok(telephone),
Ok(citizenship),
Ok(email),
Ok(sex),
Ok(study),
) = tokio::join!(
crypto::encrypt_password_with_recipients(&form.name, &recipients),
crypto::encrypt_password_with_recipients(&form.surname, &recipients),
crypto::encrypt_password_with_recipients(&form.birthplace, &recipients),
// crypto::encrypt_password_with_recipients(&self.birthdate, &recipients), // TODO
crypto::encrypt_password_with_recipients(&form.address, &recipients),
crypto::encrypt_password_with_recipients(&form.telephone, &recipients),
crypto::encrypt_password_with_recipients(&form.citizenship, &recipients),
crypto::encrypt_password_with_recipients(&form.email, &recipients),
crypto::encrypt_password_with_recipients(&form.sex, &recipients),
crypto::encrypt_password_with_recipients(&form.study, &recipients),
) else {
panic!("Failed to encrypt user details"); // TODO
};
EncryptedUserDetails {
name,
surname,
birthplace,
// birthdate: NaiveDate::from_ymd(2000, 1, 1),
address,
telephone,
citizenship,
email,
sex,
study,
}
}
fn extract_enc_candidate_details(
candidate: candidate::Model,
) -> Result<UserDetails, ServiceError> {
let ( // TODO: simplify??
Some(name),
Some(surname),
Some(birthplace),
// Some(birthdate),
Some(address),
Some(telephone),
Some(citizenship),
Some(email),
Some(sex),
Some(study),
) = (
candidate.name,
candidate.surname,
candidate.birthplace,
// candidate.birthdate,
candidate.address,
candidate.telephone,
candidate.citizenship,
candidate.email,
candidate.sex,
candidate.study,
) else {
return Err(ServiceError::CandidateDetailsNotSet);
};
Ok(UserDetails {
name,
surname,
birthplace,
// birthdate,
address,
telephone,
citizenship,
email,
sex,
study,
})
}
pub fn from_model(candidate: candidate::Model) -> Result<EncryptedUserDetails, ServiceError> {
let Ok(details) = Self::extract_enc_candidate_details(candidate) else {
return Err(ServiceError::CandidateDetailsNotSet);
};
Ok(EncryptedUserDetails {
name: details.name,
surname: details.surname,
birthplace: details.birthplace,
// birthdate,
address: details.address,
telephone: details.telephone,
citizenship: details.citizenship,
email: details.email,
sex: details.sex,
study: details.study,
})
}
pub async fn decrypt(self, priv_key: String) -> Result<UserDetails, ServiceError> {
let (
Ok(name),
Ok(surname),
Ok(birthplace),
// Ok(enc_birthdate),
Ok(address),
Ok(telephone),
Ok(citizenship),
Ok(email),
Ok(sex),
Ok(study),
) = tokio::join!(
crypto::decrypt_password_with_private_key(&self.name, &priv_key),
crypto::decrypt_password_with_private_key(&self.surname, &priv_key),
crypto::decrypt_password_with_private_key(&self.birthplace, &priv_key),
crypto::decrypt_password_with_private_key(&self.address, &priv_key),
crypto::decrypt_password_with_private_key(&self.telephone, &priv_key),
crypto::decrypt_password_with_private_key(&self.citizenship, &priv_key),
crypto::decrypt_password_with_private_key(&self.email, &priv_key),
crypto::decrypt_password_with_private_key(&self.sex, &priv_key),
crypto::decrypt_password_with_private_key(&self.study, &priv_key),
) else {
panic!("Failed to encrypt user details"); // TODO
};
Ok(UserDetails {
name,
surname,
birthplace,
// birthdate: NaiveDate::from_ymd(2000, 1, 1),
address,
telephone,
citizenship,
email,
sex,
study,
})
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserDetails {
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 CandidateService;
impl CandidateService {
@ -191,7 +19,7 @@ impl CandidateService {
/// Hashed password
/// Encrypted private key
/// Public key
pub async fn create(
pub(in crate::services) async fn create(
db: &DbConn,
application_id: i32,
plain_text_password: &String,
@ -224,9 +52,6 @@ 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(); */
Mutation::create_candidate(
db,
@ -236,66 +61,25 @@ impl CandidateService {
pubkey,
encrypted_priv_key,
)
.await
.map_err(|_| ServiceError::DbError)
}
pub async fn add_user_details(
db: &DbConn,
user: candidate::Model,
form: UserDetails,
) -> Result<entity::candidate::Model, ServiceError> {
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.public_key];
recipients.append(&mut admin_public_keys_refrence);
let enc_details = EncryptedUserDetails::encrypt_form(form, recipients).await;
Mutation::add_candidate_details(db, user, enc_details)
.await
.map_err(|_| ServiceError::DbError)
}
pub async fn decrypt_details(
pub(in crate::services) async fn add_candidate_details(
db: &DbConn,
candidate_id: i32,
password: String,
) -> Result<UserDetails, ServiceError> {
let candidate = match Query::find_candidate_by_id(db, candidate_id).await {
Ok(candidate) => candidate.unwrap(),
Err(_) => return Err(ServiceError::DbError), // TODO: logging
};
match crypto::verify_password((&password).to_string(), candidate.code.clone()).await {
Ok(valid) => {
if !valid {
return Err(ServiceError::InvalidCredentials);
}
}
Err(_) => return Err(ServiceError::InvalidCredentials),
}
let dec_priv_key = crypto::decrypt_password(candidate.private_key.clone(), password)
candidate: candidate::Model,
enc_details: EncryptedApplicationDetails,
) -> Result<entity::candidate::Model, ServiceError> {
Mutation::add_candidate_details(db, candidate, enc_details.clone())
.await
.ok()
.unwrap();
let enc_details = EncryptedUserDetails::from_model(candidate)?;
enc_details.decrypt(dec_priv_key).await
.map_err(|_| ServiceError::DbError)
}
pub async fn is_set_up(candidate: &candidate::Model) -> bool {
pub fn is_set_up(candidate: &candidate::Model) -> bool {
candidate.name.is_some() &&
candidate.surname.is_some() &&
candidate.birthplace.is_some() &&
// birthdate: NaiveDate::from_ymd(2000, 1, 1),
candidate.birthdate.is_some() &&
candidate.address.is_some() &&
candidate.telephone.is_some() &&
candidate.citizenship.is_some() &&
@ -323,20 +107,9 @@ impl CandidateService {
}
async fn decrypt_private_key(
db: &DbConn,
candidate_id: i32,
candidate: candidate::Model,
password: String,
) -> Result<String, ServiceError> {
let candidate = Query::find_candidate_by_id(db, candidate_id).await;
let Ok(candidate) = candidate else {
return Err(ServiceError::DbError);
};
let Some(candidate) = candidate else {
return Err(ServiceError::UserNotFound);
};
let private_key_encrypted = candidate.private_key;
let private_key = crypto::decrypt_password(private_key_encrypted, password).await;
@ -350,15 +123,20 @@ impl CandidateService {
pub async fn login(
db: &DbConn,
user_id: i32,
candidate_id: i32,
password: String,
ip_addr: String,
) -> Result<(String, String), ServiceError> {
let candidate = Query::find_candidate_by_id(db, candidate_id)
.await
.map_err(|_| ServiceError::DbError)?
.ok_or(ServiceError::CandidateNotFound)?;
let session_id =
SessionService::new_session(db, Some(user_id), None, password.clone(), ip_addr).await;
SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr).await;
match session_id {
Ok(session_id) => {
let private_key = Self::decrypt_private_key(db, user_id, password).await?;
let private_key = Self::decrypt_private_key(candidate, password).await?;
Ok((session_id, private_key))
}
Err(e) => Err(e),
@ -368,7 +146,7 @@ impl CandidateService {
pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<candidate::Model, ServiceError> {
match SessionService::auth_user_session(db, session_uuid).await {
Ok(user) => match user {
AdminUser::User(candidate) => Ok(candidate),
AdminUser::Candidate(candidate) => Ok(candidate),
AdminUser::Admin(_) => Err(ServiceError::DbError),
},
Err(e) => Err(e),
@ -388,15 +166,19 @@ impl CandidateService {
#[cfg(test)]
mod tests {
use entity::candidate::Model;
use sea_orm::{Database, DbConn};
use crate::{
crypto,
services::candidate_service::{CandidateService, UserDetails},
services::candidate_service::{CandidateService}, Mutation,
};
use super::EncryptedUserDetails;
use super::EncryptedApplicationDetails;
use chrono::NaiveDate;
use entity::{parent, candidate};
use crate::services::application_service::ApplicationService;
use crate::candidate_details::ApplicationDetails;
#[tokio::test]
async fn test_application_id_validation() {
@ -411,7 +193,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};
@ -420,8 +202,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
@ -429,6 +211,9 @@ mod tests {
db.execute(db.get_database_backend().build(&stmt2))
.await
.unwrap();
db.execute(db.get_database_backend().build(&stmt3))
.await
.unwrap();
db
}
@ -440,11 +225,15 @@ mod tests {
let secret_message = "trnka".to_string();
let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string())
.await
.ok()
.unwrap();
Mutation::create_parent(&db, 103151)
.await.unwrap();
let encrypted_message =
crypto::encrypt_password_with_recipients(&secret_message, &vec![&candidate.public_key])
.await
@ -464,53 +253,57 @@ mod tests {
}
#[cfg(test)]
async fn put_user_data(db: &DbConn) -> Model {
async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) {
let plain_text_password = "test".to_string();
let candidate = CandidateService::create(&db, 103151, &plain_text_password, "".to_string())
let (candidate, parent) = ApplicationService::create_candidate_with_parent(&db, 103151, &plain_text_password, "".to_string())
.await
.ok()
.unwrap();
let form = UserDetails {
let form = ApplicationDetails {
name: "test".to_string(),
surname: "a".to_string(),
surname: "aaa".to_string(),
birthplace: "b".to_string(),
// birthdate: NaiveDate::from_ymd(1999, 1, 1),
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(),
parent_name: "test".to_string(),
parent_surname: "test".to_string(),
parent_telephone: "test".to_string(),
parent_email: "test".to_string(),
};
CandidateService::add_user_details(&db, candidate, form)
ApplicationService::add_all_details(&db, candidate.application, form)
.await
.ok()
.unwrap()
}
#[tokio::test]
async fn test_put_user_data() {
let db = get_memory_sqlite_connection().await;
let candidate = put_user_data(&db).await;
let (candidate, parent) = put_user_data(&db).await;
assert!(candidate.name.is_some());
assert!(parent.name.is_some());
}
#[tokio::test]
async fn test_encrypt_decrypt_user_data() {
let password = "test".to_string();
let db = get_memory_sqlite_connection().await;
let enc_candidate = put_user_data(&db).await;
let (enc_candidate, enc_parent) = put_user_data(&db).await;
let dec_priv_key = crypto::decrypt_password(enc_candidate.private_key.clone(), password)
.await
.unwrap();
let dec_candidate = EncryptedUserDetails::from_model(enc_candidate)
.unwrap()
.decrypt(dec_priv_key)
.await
.unwrap();
let enc_details = EncryptedApplicationDetails::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");
}
}

View file

@ -1,3 +1,5 @@
pub mod session_service;
pub mod candidate_service;
pub mod admin_service;
pub mod admin_service;
pub mod parent_service;
pub mod application_service;

View file

@ -0,0 +1,31 @@
use entity::{parent};
use sea_orm::DbConn;
use crate::{error::ServiceError, Mutation, candidate_details::EncryptedApplicationDetails};
pub struct ParentService;
impl ParentService {
pub async fn create(
db: &DbConn,
application_id: i32,
) -> Result<parent::Model, ServiceError> {
let parent = Mutation::create_parent(db, application_id)
.await
.map_err(|_| ServiceError::DbError)?;
Ok(parent)
}
pub async fn add_parent_details(
db: &DbConn,
parent: parent::Model,
enc_details: EncryptedApplicationDetails,
) -> Result<parent::Model, ServiceError> {
let parent = Mutation::add_parent_details(db, parent, enc_details)
.await
.map_err(|_| ServiceError::DbError)?;
Ok(parent)
}
}

View file

@ -11,7 +11,7 @@ use crate::{
pub enum AdminUser {
Admin(entity::admin::Model),
User(entity::candidate::Model),
Candidate(entity::candidate::Model),
}
pub(in crate::services) struct SessionService;
@ -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),
};
@ -147,7 +147,7 @@ impl SessionService {
if candidate.is_ok() {
if let Some(candidate) = candidate.unwrap() {
return Ok(AdminUser::User(candidate));
return Ok(AdminUser::Candidate(candidate));
}
}
@ -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,
@ -171,7 +171,7 @@ mod tests {
use crate::{
crypto,
services::{candidate_service::CandidateService, session_service::SessionService},
services::{session_service::SessionService, application_service::ApplicationService},
};
#[cfg(test)]
@ -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
}
@ -201,10 +205,10 @@ mod tests {
let db = get_memory_sqlite_connection().await;
let candidate = CandidateService::create(&db, 103151, &SECRET.to_string(), "".to_string())
let candidate = ApplicationService::create_candidate_with_parent(&db, 103151, &SECRET.to_string(), "".to_string())
.await
.ok()
.unwrap();
.unwrap().0;
assert_eq!(candidate.application, 103151);
assert_ne!(candidate.code, SECRET.to_string());
@ -218,10 +222,9 @@ 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())
ApplicationService::create_candidate_with_parent(db, 103151, &"Tajny_kod".to_string(), "".to_string())
.await
.ok()
.unwrap();
.unwrap().0;
// correct password
let session = SessionService::new_session(
@ -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())
@ -247,10 +249,9 @@ mod tests {
let db = &get_memory_sqlite_connection().await;
let candidate_form =
CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string())
ApplicationService::create_candidate_with_parent(&db, 103151, &"Tajny_kod".to_string(), "".to_string())
.await
.ok()
.unwrap();
.unwrap().0;
// incorrect password
assert!(SessionService::new_session(

View file

@ -1,9 +1,11 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "admin")]
pub struct Model {
#[sea_orm(column_type = "Integer", primary_key)]
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub public_key: String,

View file

@ -1,24 +1,26 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
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<String>,
pub surname: Option<String>,
pub birth_surname: Option<String>,
pub birthplace: Option<String>,
pub birthdate: Option<Date>,
pub birthdate: Option<String>,
pub address: Option<String>,
pub telephone: Option<String>,
pub citizenship: Option<String>,
pub email: Option<String>,
pub sex: Option<String>,
pub study: Option<String>,
#[sea_orm(column_type = "Text", nullable)]
pub personal_identification_number: Option<String>,
#[sea_orm(column_type = "Text")]
pub personal_identification_number_hash: String,
pub public_key: String,
pub private_key: String,
@ -28,7 +30,7 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_one = "super::parent::Entity")]
#[sea_orm(has_many = "super::parent::Entity")]
Parent,
#[sea_orm(has_many = "super::session::Entity")]
Session,

View file

@ -1,3 +1,5 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
pub mod prelude;
pub mod admin;

View file

@ -1,3 +1,5 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
@ -18,7 +20,9 @@ pub enum Relation {
#[sea_orm(
belongs_to = "super::candidate::Entity",
from = "Column::Application",
to = "super::candidate::Column::Application"
to = "super::candidate::Column::Application",
on_update = "Cascade",
on_delete = "Cascade"
)]
Candidate,
}

View file

@ -1,3 +1,5 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
pub use super::admin::Entity as Admin;
pub use super::candidate::Entity as Candidate;
pub use super::parent::Entity as Parent;

View file

@ -1,3 +1,5 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
@ -5,10 +7,8 @@ use sea_orm::entity::prelude::*;
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
#[sea_orm(column_type = "Integer", nullable)]
pub admin_id: Option<i32>,
#[sea_orm(column_type = "Integer", nullable)]
pub user_id: Option<i32>,
pub admin_id: Option<i32>,
pub ip_address: String,
pub created_at: DateTime,
pub expires_at: DateTime,
@ -34,16 +34,16 @@ pub enum Relation {
Candidate,
}
impl Related<super::candidate::Entity> for Entity {
fn to() -> RelationDef {
Relation::Candidate.def()
}
}
impl Related<super::admin::Entity> for Entity {
fn to() -> RelationDef {
Relation::Admin.def()
}
}
impl Related<super::candidate::Entity> for Entity {
fn to() -> RelationDef {
Relation::Candidate.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -7,7 +7,7 @@ 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;
mod m20221112_112212_create_parent_candidate_fk;
pub struct Migrator;
#[async_trait::async_trait]
@ -21,7 +21,7 @@ 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),
Box::new(m20221112_112212_create_parent_candidate_fk::Migration),
]
}
}

View file

@ -23,7 +23,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Candidate::Surname).string())
.col(ColumnDef::new(Candidate::BirthSurname).string())
.col(ColumnDef::new(Candidate::Birthplace).string())
.col(ColumnDef::new(Candidate::Birthdate).date())
.col(ColumnDef::new(Candidate::Birthdate).string())
.col(ColumnDef::new(Candidate::Address).string())
.col(ColumnDef::new(Candidate::Telephone).string())
.col(ColumnDef::new(Candidate::Citizenship).string())