refactor: errors, services

This commit is contained in:
Sebastian Pravda 2022-11-01 19:01:12 +01:00
parent b582d2e8e1
commit 56f64a43c8
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
6 changed files with 163 additions and 89 deletions

View file

@ -4,15 +4,13 @@ extern crate rocket;
use std::net::SocketAddr;
use guards::request::session_auth::SessionAuth;
use portfolio_core::error::ServiceError;
use portfolio_core::services::session_service::SessionService;
use portfolio_core::services::candidate_service::CandidateService;
use requests::{LoginRequest, RegisterRequest};
use rocket::http::Status;
use rocket::{Rocket, Build};
use rocket::serde::json::Json;
use rocket::fairing::{self, AdHoc};
use rocket::response::status::Custom;
use portfolio_core::{Mutation};
use migration::{MigratorTrait};
use sea_orm_rocket::{Connection, Database};
@ -30,9 +28,9 @@ pub use entity::candidate::Entity as Candidate;
use portfolio_core::crypto::random_8_char_string;
fn custom_err_from_service_err(service_err: ServiceError) -> Custom<String> {
/* fn custom_err_from_service_err(service_err: ServiceError) -> Custom<String> {
Custom(Status::from_code(service_err.0.code).unwrap_or_default(), service_err.1.to_string())
}
} */
#[post("/", data = "<post_form>")]
async fn create(conn: Connection<'_, Db>, post_form: Json<RegisterRequest>) -> Result<String, Custom<String>> {
@ -41,9 +39,9 @@ async fn create(conn: Connection<'_, Db>, post_form: Json<RegisterRequest>) -> R
let plain_text_password = random_8_char_string();
Mutation::create_candidate(db, form.application_id, &plain_text_password, form.personal_id_number)
CandidateService::create(db, form.application_id, &plain_text_password, form.personal_id_number)
.await
.expect("Could not insert candidate");
.unwrap();
Ok(plain_text_password)
}
@ -59,11 +57,13 @@ async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>, ip_addr
let db = conn.into_inner();
println!("{} {}", login_form.application_id, login_form.password);
let session_token = SessionService::new_session(db,
login_form.application_id,
login_form.password.to_string(),
ip_addr.ip().to_string()
).await;
let session_token = CandidateService::login(
db,
login_form.application_id,
login_form.password.to_string(),
ip_addr.ip().to_string()
)
.await;
if session_token.is_ok() {
return Ok(
@ -71,7 +71,8 @@ async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>, ip_addr
);
} else {
return Err(
custom_err_from_service_err(session_token.err().unwrap())
// custom_err_from_service_err(session_token.err().unwrap())
Custom(Status::Unauthorized, "TODO".to_string())
)
}
}

View file

@ -1,27 +1,17 @@
use crate::Mutation;
use std::vec;
use ::entity::candidate;
use sea_orm::{*};
use crate::{crypto::{hash_password, self}};
impl Mutation {
pub async fn create_candidate(
db: &DbConn,
application_id: i32,
plain_text_password: &String,
personal_id_number: String,
hashed_password: String,
encrypted_personal_id_number: String,
pubkey: String,
encrypted_priv_key: String
) -> Result<candidate::Model, DbErr> {
// TODO: unwrap pro testing..
let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap();
let (pubkey, priv_key_plain_text) = crypto::create_identity();
let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await.unwrap();
let encrypted_personal_id_number = crypto::encrypt_password_with_recipients(
&personal_id_number, vec![&pubkey]
).await.unwrap();
candidate::ActiveModel {
application: Set(application_id),
personal_identification_number: Set(encrypted_personal_id_number),
@ -36,48 +26,8 @@ impl Mutation {
.await
}
}
/*
#[cfg(test)]
mod tests {
use sea_orm::{Database, DbConn};
use crate::{Mutation, crypto};
#[cfg(test)]
async fn get_memory_sqlite_connection() -> DbConn {
use entity::candidate;
use sea_orm::{DbBackend, sea_query::TableCreateStatement, ConnectionTrait};
use sea_orm::Schema;
let base_url = "sqlite::memory:";
let db: DbConn = Database::connect(base_url).await.unwrap();
let schema = Schema::new(DbBackend::Sqlite);
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
db
}
#[tokio::test]
async fn test_encrypt_decrypt_private_key_with_passphrase() {
let db = get_memory_sqlite_connection().await;
let plain_text_password = "test".to_string();
let secret_message = "trnka".to_string();
let candidate = Mutation::create_candidate(&db, 5555555, &plain_text_password, "".to_string()).await.unwrap();
let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap();
let private_key_plain_text = crypto::decrypt_password(candidate.private_key, plain_text_password).await.unwrap();
let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap();
assert_eq!(secret_message, decrypted_message);
}
}
#[tokio::fs]
} */

View file

@ -1,4 +1,4 @@
pub struct Status {
/* pub struct Status {
pub code: u16,
}
@ -21,4 +21,28 @@ pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 5
pub const USER_NOT_FOUND_BY_SESSION_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted
"User not found, please contact technical support"); // Shouldn't ever happen
pub struct ServiceError<'a>(pub Status, pub &'a str);
pub struct ServiceError<'a>(pub Status, pub &'a str); */
pub enum ServiceError {
InvalidCredentials,
ExpiredSession,
JwtError,
UserNotFound,
DbError,
UserNotFoundByJwtId,
UserNotFoundBySessionId,
}
impl std::fmt::Debug for ServiceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ServiceError::InvalidCredentials => write!(f, "Invalid credentials"),
ServiceError::ExpiredSession => write!(f, "Session expired, please login again"),
ServiceError::JwtError => write!(f, "Error while encoding JWT"),
ServiceError::UserNotFound => write!(f, "User not found"),
ServiceError::DbError => write!(f, "Database error"),
ServiceError::UserNotFoundByJwtId => write!(f, "User not found, please contact technical support"),
ServiceError::UserNotFoundBySessionId => write!(f, "User not found, please contact technical support"),
}
}
}

View file

@ -0,0 +1,98 @@
use entity::candidate;
use sea_orm::{DbConn, prelude::Uuid};
use crate::{Mutation, crypto::{hash_password, self}, error::{ServiceError}};
use super::session_service::SessionService;
pub struct CandidateService;
impl CandidateService {
pub async fn create(
db: &DbConn,
application_id: i32,
plain_text_password: &String,
personal_id_number: String
) -> Result<candidate::Model, ServiceError>{
// TODO: unwrap pro testing..
let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap();
let (pubkey, priv_key_plain_text) = crypto::create_identity();
let encrypted_priv_key = crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await.unwrap();
let encrypted_personal_id_number = crypto::encrypt_password_with_recipients(
&personal_id_number, vec![&pubkey]
).await.unwrap();
Mutation::create_candidate(
db,
application_id,
hashed_password,
encrypted_personal_id_number,
pubkey,
encrypted_priv_key
)
.await
.map_err(|_| ServiceError::DbError)
}
pub async fn login(
db: &DbConn,
user_id: i32,
password: String,
ip_addr: String
) -> Result<String, ServiceError> {
SessionService::new_session(db, user_id, password, ip_addr).await
}
pub async fn auth(
db: &DbConn,
session_uuid: Uuid,
) -> Result<candidate::Model, ServiceError> {
SessionService::auth_user_session(db, session_uuid).await
}
}
#[cfg(test)]
mod tests {
use sea_orm::{Database, DbConn};
use crate::{crypto, services::candidate_service::CandidateService};
#[cfg(test)]
async fn get_memory_sqlite_connection() -> DbConn {
use entity::candidate;
use sea_orm::{DbBackend, sea_query::TableCreateStatement, ConnectionTrait};
use sea_orm::Schema;
let base_url = "sqlite::memory:";
let db: DbConn = Database::connect(base_url).await.unwrap();
let schema = Schema::new(DbBackend::Sqlite);
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
db
}
#[tokio::test]
async fn test_encrypt_decrypt_private_key_with_passphrase() {
let db = get_memory_sqlite_connection().await;
let plain_text_password = "test".to_string();
let secret_message = "trnka".to_string();
let candidate = CandidateService::create(&db, 5555555, &plain_text_password, "".to_string()).await.unwrap();
let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap();
let private_key_plain_text = crypto::decrypt_password(candidate.private_key, plain_text_password).await.unwrap();
let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap();
assert_eq!(secret_message, decrypted_message);
}
}

View file

@ -1 +1,2 @@
pub mod session_service;
pub mod session_service;
pub mod candidate_service;

View file

@ -3,7 +3,7 @@ use std::cmp::min;
use entity::candidate;
use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait};
use crate::{crypto::{self}, Query, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID, USER_NOT_FOUND_BY_SESSION_ID, EXPIRED_SESSION_ERROR}, Mutation};
use crate::{crypto::{self}, Query, error::{ServiceError}, Mutation};
// TODO: generics
pub struct SessionService;
@ -28,19 +28,19 @@ impl SessionService {
let candidate = match Query::find_candidate_by_id(db, user_id).await {
Ok(candidate) => match candidate {
Some(candidate) => candidate,
None => return Err(USER_NOT_FOUND_ERROR)
None => return Err(ServiceError::UserNotFound)
},
Err(_) => {return Err(DB_ERROR)}
Err(_) => {return Err(ServiceError::DbError)}
};
// compare passwords
match crypto::verify_password(password,candidate.code.clone()).await {
Ok(valid) => {
if !valid {
return Err(INVALID_CREDENTIALS_ERROR)
return Err(ServiceError::InvalidCredentials)
}
},
Err(_) => {return Err(INVALID_CREDENTIALS_ERROR)}
Err(_) => {return Err(ServiceError::InvalidCredentials)}
}
@ -49,7 +49,7 @@ impl SessionService {
let session = match Mutation::insert_session(db, user_id, random_uuid, ip_addr).await {
Ok(session) => session,
Err(_) => return Err(DB_ERROR)
Err(_) => return Err(ServiceError::DbError)
};
// delete old sessions
@ -64,9 +64,9 @@ impl SessionService {
let session = match Query::find_session_by_uuid(db, uuid).await {
Ok(session) => match session {
Some(session) => session,
None => return Err(USER_NOT_FOUND_BY_SESSION_ID)
None => return Err(ServiceError::UserNotFoundBySessionId)
},
Err(_) => {return Err(DB_ERROR)}
Err(_) => {return Err(ServiceError::DbError)}
};
let now = chrono::Utc::now().naive_utc();
@ -74,15 +74,15 @@ impl SessionService {
if now > session.expires_at {
// delete session
Mutation::delete_session(db, session.id).await.unwrap();
return Err(EXPIRED_SESSION_ERROR)
return Err(ServiceError::ExpiredSession)
}
let candidate = match session.find_related(candidate::Entity).one(db).await {
Ok(candidate) => match candidate {
Some(candidate) => candidate,
None => return Err(USER_NOT_FOUND_BY_JWT_ID)
None => return Err(ServiceError::UserNotFoundBySessionId)
},
Err(_) => {return Err(DB_ERROR)}
Err(_) => {return Err(ServiceError::DbError)}
};
Ok(candidate)
@ -93,10 +93,10 @@ impl SessionService {
#[cfg(test)]
mod tests {
use entity::candidate;
use entity::{candidate};
use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid};
use crate::{crypto, Mutation, services::session_service::SessionService};
use crate::{crypto, services::{session_service::SessionService, candidate_service::CandidateService}};
#[cfg(test)]
async fn get_memory_sqlite_connection() -> DbConn {
@ -119,7 +119,7 @@ mod tests {
let db = get_memory_sqlite_connection().await;
let candidate = Mutation::create_candidate(&db, 5555555, &SECRET.to_string(), "".to_string()).await.unwrap();
let candidate = CandidateService::create(&db, 5555555, &SECRET.to_string(), "".to_string()).await.ok().unwrap();
assert_eq!(candidate.application, 5555555);
assert_ne!(candidate.code, SECRET.to_string());
@ -130,7 +130,7 @@ mod tests {
async fn test_candidate_session_correct_password() {
let db = &get_memory_sqlite_connection().await;
Mutation::create_candidate(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.unwrap();
CandidateService::create(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap();
// correct password
let session = SessionService::new_session(
@ -153,7 +153,7 @@ mod tests {
async fn test_candidate_session_incorrect_password() {
let db = &get_memory_sqlite_connection().await;
let candidate_form = Mutation::create_candidate(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.unwrap();
let candidate_form = CandidateService::create(&db, 5555555, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap();
// incorrect password
assert!(