mirror of
https://github.com/danbulant/Portfolio
synced 2026-05-21 05:18:44 +00:00
feat: auth trait
This commit is contained in:
parent
2f12211aa7
commit
4cf408ab2f
10 changed files with 95 additions and 87 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
14
core/src/models/auth.rs
Normal 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>;
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod candidate_details;
|
||||
pub mod candidate;
|
||||
pub mod candidate;
|
||||
pub mod auth;
|
||||
|
|
@ -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?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue