mirror of
https://github.com/danbulant/Portfolio
synced 2026-06-19 14:31:05 +00:00
refactor: errors, services
This commit is contained in:
parent
b582d2e8e1
commit
56f64a43c8
6 changed files with 163 additions and 89 deletions
|
|
@ -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())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
} */
|
||||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
98
core/src/services/candidate_service.rs
Normal file
98
core/src/services/candidate_service.rs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
pub mod session_service;
|
||||
pub mod session_service;
|
||||
pub mod candidate_service;
|
||||
|
|
@ -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!(
|
||||
|
|
|
|||
Loading…
Reference in a new issue