use async_trait::async_trait; use entity::{admin, admin_session}; use sea_orm::{prelude::Uuid, DbConn, IntoActiveModel}; use crate::{crypto, error::ServiceError, Query, Mutation, models::auth::AuthenticableTrait}; use super::session_service::SessionService; pub struct AdminService; impl AdminService { async fn decrypt_private_key( db: &DbConn, admin_id: i32, password: String, ) -> Result { let admin = Query::find_admin_by_id(db, admin_id).await?.ok_or(ServiceError::InvalidCredentials)?; let private_key_encrypted = admin.private_key; let private_key = crypto::decrypt_password(private_key_encrypted, password).await?; Ok(private_key) } } #[async_trait] impl AuthenticableTrait for AdminService { type User = admin::Model; type Session = admin_session::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 = Self::new_session(db, &admin, password.clone(), ip_addr ) .await?; let private_key = Self::decrypt_private_key(db, admin.id, password).await?; Ok((session_id, private_key)) } async fn auth(db: &DbConn, session_uuid: Uuid) -> Result { let session = Query::find_admin_session_by_uuid(db, session_uuid) .await? .ok_or(ServiceError::Unauthorized)?; if !SessionService::is_valid(&session).await? { Mutation::delete_session(db, session.into_active_model()).await?; return Err(ServiceError::ExpiredSession); } let admin = Query::find_admin_by_id(db, session.admin_id) .await? .ok_or(ServiceError::CandidateNotFound)?; Ok(admin) } async fn logout(db: &DbConn, session: admin_session::Model) -> Result<(), ServiceError> { Mutation::delete_session(db, session.into_active_model()).await?; Ok(()) } async fn new_session( db: &DbConn, admin: &admin::Model, password: String, ip_addr: String, ) -> Result { 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_admin_session(db, admin.id, random_uuid, ip_addr).await?; Self::delete_old_sessions(db, &admin, 1).await?; Ok(session.id.to_string()) } async fn delete_old_sessions( db: &DbConn, admin: &admin::Model, keep_n_recent: usize, ) -> Result<(), ServiceError> { let sessions = Query::find_related_admin_sessions(db, admin) .await? .iter() .map(|s| s.clone().into_active_model()) .collect(); SessionService::delete_sessions(db, sessions, keep_n_recent).await?; Ok(()) } } #[cfg(test)] pub mod admin_tests { use chrono::{Local, Utc}; use entity::admin; use sea_orm::{Set, ActiveModelTrait}; use crate::{utils::db::get_memory_sqlite_connection, error::ServiceError}; use super::*; pub async fn create_admin(db: &DbConn) -> admin::Model { let password = "admin".to_string(); let (pubkey, priv_key) = crypto::create_identity(); let enc_priv_key = crypto::encrypt_password(priv_key, password).await.unwrap(); let admin = admin::ActiveModel { name: Set("admin".to_string()), public_key: Set(pubkey), private_key: Set(enc_priv_key), password: Set("admin".to_string()), created_at: Set(Utc::now().naive_utc()), updated_at: Set(Utc::now().naive_utc()), ..Default::default() } .insert(db) .await .unwrap(); admin } #[tokio::test] async fn test_admin_login() -> Result<(), ServiceError> { let db = get_memory_sqlite_connection().await; let admin = admin::ActiveModel { id: Set(1), name: Set("Admin".to_owned()), public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()), // AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS private_key: Set("5KCEGk0ueWVGnu5Xo3rmpLoilcVZ2ZWmwIcdZEJ8rrBNW7jwzZU/XTcTXtk/xyy/zjF8s+YnuVpOklQvX3EC/Sn+ZwyPY3jokM2RNwnZZlnqdehOEV1SMm/Y".to_owned()), // test password: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()), created_at: Set(Local::now().naive_local()), updated_at: Set(Local::now().naive_local()), ..Default::default() } .insert(&db) .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?; assert_eq!(logged_admin.id, 1); assert_eq!(logged_admin.name, "Admin"); Ok(()) } }