feat: auth trait

This commit is contained in:
Sebastian Pravda 2022-12-21 16:08:47 +01:00
parent 2f12211aa7
commit 4cf408ab2f
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
10 changed files with 95 additions and 87 deletions

View file

@ -1,5 +1,6 @@
use entity::admin::Model as Admin;
use log::info;
use portfolio_core::models::auth::AuthenticableTrait;
use portfolio_core::sea_orm::prelude::Uuid;
use portfolio_core::services::admin_service::AdminService;
use rocket::http::Status;

View file

@ -1,4 +1,5 @@
use entity::candidate::Model as Candidate;
use portfolio_core::models::auth::AuthenticableTrait;
use portfolio_core::sea_orm::prelude::Uuid;
use portfolio_core::services::candidate_service::CandidateService;
use rocket::http::Status;

View file

@ -2,7 +2,7 @@ use std::net::{SocketAddr, IpAddr, Ipv4Addr};
use portfolio_core::{
crypto::random_12_char_string,
services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::candidate::{BaseCandidateResponse, CreateCandidateResponse, ApplicationDetails}, sea_orm::prelude::Uuid, Query, error::ServiceError, utils::csv,
services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::{candidate::{BaseCandidateResponse, CreateCandidateResponse, ApplicationDetails}, auth::AuthenticableTrait}, sea_orm::prelude::Uuid, Query, error::ServiceError, utils::csv,
};
use requests::{AdminLoginRequest, RegisterRequest};
use rocket::http::{Cookie, Status, CookieJar};

View file

@ -1,5 +1,6 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use portfolio_core::models::auth::AuthenticableTrait;
use portfolio_core::models::candidate::ApplicationDetails;
use portfolio_core::sea_orm::prelude::Uuid;
use portfolio_core::services::application_service::ApplicationService;
@ -227,7 +228,6 @@ pub async fn submit_portfolio(
#[post("/delete")]
pub async fn delete_portfolio(
conn: Connection<'_, Db>,
session: CandidateAuth,
) -> Result<(), Custom<String>> {
let candidate: entity::candidate::Model = session.into();

View file

@ -15,8 +15,9 @@ serde = { version = "^1.0", features = ["derive"] }
# csv
csv = "^1.1"
# error
async-trait = "0.1.60"
# error
thiserror = "^1.0"
# env

14
core/src/models/auth.rs Normal file
View file

@ -0,0 +1,14 @@
use async_trait::async_trait;
use sea_orm::{prelude::Uuid, DbConn};
use crate::error::ServiceError;
#[async_trait]
pub trait AuthenticableTrait {
type User;
// fn password_valid(user: T);
async fn login(db: &DbConn, user: i32, password: String, ip_addr: String) -> Result<(String, String), ServiceError>;
async fn auth(db: &DbConn, session_id: Uuid) -> Result<Self::User, ServiceError>;
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError>;
}

View file

@ -1,2 +1,3 @@
pub mod candidate_details;
pub mod candidate;
pub mod candidate;
pub mod auth;

View file

@ -1,7 +1,8 @@
use async_trait::async_trait;
use entity::admin;
use sea_orm::{prelude::Uuid, DbConn};
use crate::{crypto, error::ServiceError, Query, Mutation};
use crate::{crypto, error::ServiceError, Query, Mutation, models::auth::AuthenticableTrait};
use super::session_service::{AdminUser, SessionService};
@ -19,13 +20,20 @@ impl AdminService {
Ok(private_key)
}
}
pub async fn login(
#[async_trait]
impl AuthenticableTrait for AdminService {
type User = admin::Model;
async fn login(
db: &DbConn,
admin_id: i32,
password: String,
ip_addr: String,
) -> Result<(String, String), ServiceError> {
let admin = Query::find_admin_by_id(db, admin_id).await?.ok_or(ServiceError::InvalidCredentials)?;
let session_id = SessionService::new_session(db,
None,
Some(admin_id),
@ -34,21 +42,21 @@ impl AdminService {
)
.await?;
let private_key = Self::decrypt_private_key(db, admin_id, password).await?;
let private_key = Self::decrypt_private_key(db, admin.id, password).await?;
Ok((session_id, private_key))
}
pub async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
Ok(())
}
pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<admin::Model, ServiceError> {
async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<admin::Model, ServiceError> {
match SessionService::auth_user_session(db, session_uuid).await? {
AdminUser::Admin(admin) => Ok(admin),
AdminUser::Candidate(_) => Err(ServiceError::Unauthorized),
}
}
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
Ok(())
}
}
#[cfg(test)]
@ -65,7 +73,7 @@ mod admin_tests {
#[tokio::test]
async fn test_admin_login() -> Result<(), ServiceError> {
let db = get_memory_sqlite_connection().await;
let _ = admin::ActiveModel {
let admin = admin::ActiveModel {
id: Set(1),
name: Set("Admin".to_owned()),
public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()),
@ -80,7 +88,7 @@ mod admin_tests {
.insert(&db)
.await?;
let (session_id, _private_key) = AdminService::login(&db, 1, "test".to_owned(), "127.0.0.1".to_owned()).await?;
let (session_id, _private_key) = AdminService::login(&db, admin.id, "test".to_owned(), "127.0.0.1".to_owned()).await?;
let logged_admin = AdminService::auth(&db, session_id.parse().unwrap()).await?;

View file

@ -1,3 +1,4 @@
use async_trait::async_trait;
use entity::candidate;
use sea_orm::{prelude::Uuid, DbConn};
@ -5,7 +6,7 @@ use crate::{
models::{candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, candidate::CandidateDetails},
crypto::{self, hash_password},
error::ServiceError,
Mutation, Query, models::candidate::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients,
Mutation, Query, models::{candidate::{BaseCandidateResponse, CreateCandidateResponse}, auth::AuthenticableTrait}, utils::db::get_recipients,
};
use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService, portfolio_service::PortfolioService};
@ -140,11 +141,6 @@ impl CandidateService {
)
}
pub async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
Ok(())
}
pub async fn delete_candidate(db: &DbConn, candidate: candidate::Model) -> Result<(), ServiceError> {
PortfolioService::delete_candidate_root(candidate.application).await?;
@ -218,34 +214,6 @@ impl CandidateService {
Ok(private_key)
}
pub async fn login(
db: &DbConn,
candidate_id: i32,
password: String,
ip_addr: String,
) -> Result<(String, String), ServiceError> {
let candidate = Query::find_candidate_by_id(db, candidate_id)
.await?
.ok_or(ServiceError::CandidateNotFound)?;
let session_id =
SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr)
.await?;
let private_key = Self::decrypt_private_key(candidate, password).await?;
Ok((session_id, private_key))
}
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::Candidate(candidate) => Ok(candidate),
AdminUser::Admin(_) => Err(ServiceError::Unauthorized),
},
Err(e) => Err(e),
}
}
fn is_application_id_valid(application_id: i32) -> bool {
let s = &application_id.to_string();
if s.len() <= 3 {
@ -257,10 +225,47 @@ impl CandidateService {
}
}
#[async_trait]
impl AuthenticableTrait for CandidateService {
type User = candidate::Model;
async fn login(
db: &DbConn,
application_id: i32,
password: String,
ip_addr: String,
) -> Result<(String, String), ServiceError> {
let candidate = Query::find_candidate_by_id(db, application_id)
.await?
.ok_or(ServiceError::CandidateNotFound)?;
let session_id = SessionService::new_session(db, Some(application_id), None, password.clone(), ip_addr)
.await?;
let private_key = Self::decrypt_private_key(candidate, password).await?;
Ok((session_id, private_key))
}
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::Candidate(candidate) => Ok(candidate),
AdminUser::Admin(_) => Err(ServiceError::Unauthorized),
},
Err(e) => Err(e),
}
}
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use sea_orm::{DbConn};
use crate::models::auth::AuthenticableTrait;
use crate::models::candidate_details::tests::assert_all_application_details;
use crate::utils::db::get_memory_sqlite_connection;
use crate::{crypto, services::candidate_service::CandidateService, Mutation};

View file

@ -44,64 +44,41 @@ impl SessionService {
/// Authenticate user by application id and password and generate a new session
pub async fn new_session(
db: &DbConn,
user_id: Option<i32>,
candidate_id: Option<i32>,
admin_id: Option<i32>,
password: String,
ip_addr: String,
) -> Result<String, ServiceError> {
if user_id.is_none() && admin_id.is_none() {
if candidate_id.is_none() && admin_id.is_none() {
return Err(ServiceError::UserNotFoundBySessionId);
}
if admin_id.is_none() {
// unwrap is safe here
let candidate = match Query::find_candidate_by_id(db, user_id.unwrap()).await {
Ok(candidate) => match candidate {
Some(candidate) => candidate,
None => return Err(ServiceError::CandidateNotFound),
},
Err(e) => return Err(ServiceError::DbError(e)),
};
if let Some(candidate_id) = candidate_id {
let candidate = Query::find_candidate_by_id(db, candidate_id).await?
.ok_or(ServiceError::CandidateNotFound)?;
// compare passwords
match crypto::verify_password(password.clone(), candidate.code.clone()).await {
Ok(valid) => {
if !valid {
return Err(ServiceError::InvalidCredentials);
}
}
Err(_) => return Err(ServiceError::InvalidCredentials),
if !crypto::verify_password(password.clone(), candidate.code.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
}
if user_id.is_none() {
// unwrap is safe here
let admin = match Query::find_admin_by_id(db, admin_id.unwrap()).await {
Ok(admin) => match admin {
Some(admin) => admin,
None => return Err(ServiceError::CandidateNotFound),
},
Err(e) => return Err(ServiceError::DbError(e)),
};
if let Some(admin_id) = admin_id {
let admin = Query::find_admin_by_id(db, admin_id).await?
.ok_or(ServiceError::InvalidCredentials)?;
// compare passwords
match crypto::verify_password(password.clone(), admin.password.clone()).await {
Ok(valid) => {
if !valid {
return Err(ServiceError::InvalidCredentials);
}
}
Err(_) => return Err(ServiceError::InvalidCredentials),
if !crypto::verify_password(password.clone(), admin.password.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
}
// user is authenticated, generate a new session
let random_uuid: Uuid = Uuid::new_v4();
let session = Mutation::insert_session(db, user_id, admin_id, random_uuid, ip_addr).await?;
let session = Mutation::insert_session(db, candidate_id, admin_id, random_uuid, ip_addr).await?;
// delete old sessions
SessionService::delete_old_sessions(db, user_id, admin_id, 3)
SessionService::delete_old_sessions(db, candidate_id, admin_id, 3)
.await
.ok();