mirror of
https://github.com/danbulant/Portfolio
synced 2026-06-18 14:01:04 +00:00
Merge pull request #99 from EETagent/improve_sessions
Sessions improvements
This commit is contained in:
commit
0a610fb0ee
29 changed files with 474 additions and 290 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -226,9 +226,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.58"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c"
|
||||
checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -2064,6 +2064,7 @@ dependencies = [
|
|||
"argon2",
|
||||
"async-compat",
|
||||
"async-tempfile",
|
||||
"async-trait",
|
||||
"async_zip",
|
||||
"base64",
|
||||
"chrono",
|
||||
|
|
@ -2088,6 +2089,7 @@ dependencies = [
|
|||
name = "portfolio-entity"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"sea-orm",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -58,8 +58,9 @@ pub async fn logout(conn: Connection<'_, Db>, _session: AdminAuth, cookies: &Coo
|
|||
.ok_or(Custom(Status::Unauthorized, "No session cookie".to_string()))?;
|
||||
let session_id = Uuid::try_parse(cookie.value()) // unwrap would be safe here because of the auth guard
|
||||
.map_err(|e| Custom(Status::BadRequest, e.to_string()))?;
|
||||
let session = Query::find_admin_session_by_uuid(db, session_id).await.unwrap().unwrap();
|
||||
|
||||
let _res = AdminService::logout(db, session_id)
|
||||
let _res = AdminService::logout(db, session)
|
||||
.await
|
||||
.map_err(to_custom_error)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
|
||||
use portfolio_core::Query;
|
||||
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;
|
||||
|
|
@ -58,8 +60,8 @@ pub async fn logout(
|
|||
))?;
|
||||
let session_id = Uuid::try_parse(cookie.value()) // unwrap would be safe here because of the auth guard
|
||||
.map_err(|e| Custom(Status::BadRequest, e.to_string()))?;
|
||||
|
||||
CandidateService::logout(db, session_id)
|
||||
let session = Query::find_session_by_uuid(db, session_id).await.unwrap().unwrap(); // TODO
|
||||
CandidateService::logout(db, session)
|
||||
.await
|
||||
.map_err(to_custom_error)?;
|
||||
|
||||
|
|
@ -227,7 +229,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
|
||||
|
|
|
|||
49
core/src/database/mutation/admin_session.rs
Normal file
49
core/src/database/mutation/admin_session.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use chrono::{Utc, Duration};
|
||||
use entity::{admin_session};
|
||||
use sea_orm::{DbConn, prelude::Uuid, DbErr, Set, ActiveModelTrait};
|
||||
|
||||
use crate::Mutation;
|
||||
|
||||
impl Mutation {
|
||||
pub async fn insert_admin_session(
|
||||
db: &DbConn,
|
||||
admin_id: i32,
|
||||
random_uuid: Uuid,
|
||||
ip_addr: String,
|
||||
) -> Result<admin_session::Model, DbErr> {
|
||||
admin_session::ActiveModel {
|
||||
id: Set(random_uuid),
|
||||
admin_id: Set(Some(admin_id)),
|
||||
ip_address: Set(ip_addr),
|
||||
created_at: Set(Utc::now().naive_local()),
|
||||
expires_at: Set(Utc::now()
|
||||
.naive_local()
|
||||
.checked_add_signed(Duration::days(1))
|
||||
.unwrap()),
|
||||
updated_at: Set(Utc::now().naive_local())
|
||||
}
|
||||
.insert(db)
|
||||
.await
|
||||
}
|
||||
|
||||
/* pub async fn update_session_expiration(db: &DbConn,
|
||||
session: session::Model,
|
||||
expires_at: NaiveDateTime,
|
||||
) -> Result<session::Model, DbErr> {
|
||||
let mut session = session.into_active_model();
|
||||
|
||||
session.expires_at = Set(expires_at);
|
||||
session.updated_at = Set(Utc::now().naive_local());
|
||||
|
||||
session.update(db).await
|
||||
}
|
||||
|
||||
pub async fn delete_admin_session(db: &DbConn, session: ad) -> Result<DeleteResult, DbErr> {
|
||||
session::ActiveModel {
|
||||
id: Set(session_id),
|
||||
..Default::default()
|
||||
}
|
||||
.delete(db)
|
||||
.await
|
||||
} */
|
||||
}
|
||||
|
|
@ -2,4 +2,5 @@ pub(crate) struct Mutation;
|
|||
|
||||
pub mod session;
|
||||
pub mod candidate;
|
||||
pub mod parent;
|
||||
pub mod parent;
|
||||
pub mod admin_session;
|
||||
|
|
@ -1,46 +1,56 @@
|
|||
use chrono::{Utc, Duration};
|
||||
use ::entity::session;
|
||||
use chrono::{Utc, Duration, NaiveDateTime};
|
||||
use ::entity::{session, candidate};
|
||||
use sea_orm::{*, prelude::Uuid};
|
||||
|
||||
use crate::Mutation;
|
||||
|
||||
|
||||
impl Mutation {
|
||||
pub async fn insert_session(
|
||||
pub async fn insert_candidate_session(
|
||||
db: &DbConn,
|
||||
user_id: Option<i32>,
|
||||
admin_id: Option<i32>,
|
||||
random_uuid: Uuid,
|
||||
candidate_id: i32,
|
||||
ip_addr: String,
|
||||
) -> Result<session::Model, DbErr> {
|
||||
session::ActiveModel {
|
||||
id: Set(random_uuid),
|
||||
user_id: Set(user_id),
|
||||
admin_id: Set(admin_id),
|
||||
candidate_id: Set(Some(candidate_id)),
|
||||
ip_address: Set(ip_addr),
|
||||
created_at: Set(Utc::now().naive_local()),
|
||||
expires_at: Set(Utc::now()
|
||||
.naive_local()
|
||||
.checked_add_signed(Duration::days(1))
|
||||
.checked_add_signed(Duration::days(14))
|
||||
.unwrap()),
|
||||
updated_at: Set(Utc::now().naive_local())
|
||||
}
|
||||
.insert(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_session(db: &DbConn, session_id: Uuid) -> Result<DeleteResult, DbErr> {
|
||||
session::ActiveModel {
|
||||
id: Set(session_id),
|
||||
..Default::default()
|
||||
}
|
||||
.delete(db)
|
||||
.await
|
||||
pub async fn update_session_expiration(db: &DbConn,
|
||||
session: session::Model,
|
||||
expires_at: NaiveDateTime,
|
||||
) -> Result<session::Model, DbErr> {
|
||||
let mut session = session.into_active_model();
|
||||
|
||||
session.expires_at = Set(expires_at);
|
||||
session.updated_at = Set(Utc::now().naive_local());
|
||||
|
||||
session.update(db).await
|
||||
}
|
||||
|
||||
pub async fn delete_session<T>(db: &DbConn, session: T) -> Result<DeleteResult, DbErr>
|
||||
where T: ActiveModelTrait + std::marker::Send + ActiveModelBehavior
|
||||
{
|
||||
session
|
||||
.delete(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use sea_orm::prelude::Uuid;
|
||||
/* use sea_orm::prelude::Uuid;
|
||||
|
||||
use crate::{utils::db::get_memory_sqlite_connection, Mutation, services::candidate_service::tests::put_user_data};
|
||||
|
||||
|
|
@ -49,14 +59,14 @@ mod tests {
|
|||
let db = get_memory_sqlite_connection().await;
|
||||
|
||||
let session_id = Uuid::new_v4();
|
||||
let (user, _) = put_user_data(&db).await;
|
||||
let (candidate, _) = put_user_data(&db).await;
|
||||
|
||||
let session = Mutation::insert_session(&db, Some(user.application), None, session_id, "127.0.0.1".to_string()).await.unwrap();
|
||||
let session = Mutation::insert_candidate_session(&db, session_id, candidate.application, "127.0.0.1".to_string()).await.unwrap();
|
||||
|
||||
assert_eq!(session.id, session_id);
|
||||
|
||||
let delete_result = Mutation::delete_session(&db, session_id).await.unwrap();
|
||||
|
||||
assert_eq!(delete_result.rows_affected, 1);
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::Query;
|
||||
|
||||
use ::entity::prelude::AdminSession;
|
||||
use ::entity::{candidate, admin, admin_session};
|
||||
use ::entity::{session, session::Entity as Session};
|
||||
use sea_orm::prelude::Uuid;
|
||||
use sea_orm::*;
|
||||
|
|
@ -12,19 +14,23 @@ impl Query {
|
|||
Session::find_by_id(uuid).one(db).await
|
||||
}
|
||||
|
||||
// find session by user id
|
||||
pub async fn find_sessions_by_user_id(
|
||||
pub async fn find_admin_session_by_uuid(
|
||||
db: &DbConn,
|
||||
user_id: Option<i32>,
|
||||
admin_id: Option<i32>,
|
||||
) -> Result<Vec<session::Model>, DbErr> {
|
||||
if user_id.is_some() {
|
||||
Session::find()
|
||||
.filter(session::Column::UserId.eq(user_id))
|
||||
} else {
|
||||
Session::find()
|
||||
.filter(session::Column::AdminId.eq(admin_id))
|
||||
}
|
||||
uuid: Uuid,
|
||||
) -> Result<Option<admin_session::Model>, DbErr> {
|
||||
AdminSession::find_by_id(uuid).one(db).await
|
||||
}
|
||||
|
||||
pub async fn find_related_candidate_sessions(db: &DbConn, candidate: candidate::Model) -> Result<Vec<session::Model>, DbErr> {
|
||||
candidate.find_related(Session)
|
||||
.order_by_asc(session::Column::UpdatedAt)
|
||||
.all(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_related_admin_sessions(db: &DbConn, admin: admin::Model) -> Result<Vec<admin_session::Model>, DbErr> {
|
||||
admin.find_related(admin_session::Entity)
|
||||
.order_by_asc(admin_session::Column::UpdatedAt)
|
||||
.all(db)
|
||||
.await
|
||||
}
|
||||
|
|
@ -32,8 +38,7 @@ impl Query {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use entity::{session, admin, candidate};
|
||||
use sea_orm::ActiveValue::NotSet;
|
||||
use entity::{session, admin, candidate, admin_session};
|
||||
use sea_orm::{prelude::Uuid, ActiveModelTrait, Set};
|
||||
|
||||
use crate::utils::db::get_memory_sqlite_connection;
|
||||
|
|
@ -48,6 +53,7 @@ mod tests {
|
|||
ip_address: Set("10.10.10.10".to_string()),
|
||||
created_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
expires_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
updated_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&db)
|
||||
|
|
@ -64,7 +70,7 @@ mod tests {
|
|||
|
||||
const APPLICATION_ID: i32 = 103158;
|
||||
|
||||
candidate::ActiveModel {
|
||||
let candidate = candidate::ActiveModel {
|
||||
application: Set(APPLICATION_ID),
|
||||
code: Set("test".to_string()),
|
||||
public_key: Set("test".to_string()),
|
||||
|
|
@ -80,11 +86,11 @@ mod tests {
|
|||
|
||||
session::ActiveModel {
|
||||
id: Set(Uuid::new_v4()),
|
||||
user_id: Set(Some(APPLICATION_ID)),
|
||||
admin_id: NotSet,
|
||||
candidate_id: Set(Some(APPLICATION_ID)),
|
||||
ip_address: Set("10.10.10.10".to_string()),
|
||||
created_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
expires_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
updated_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&db)
|
||||
|
|
@ -93,7 +99,7 @@ mod tests {
|
|||
|
||||
const ADMIN_ID: i32 = 1;
|
||||
|
||||
admin::ActiveModel {
|
||||
let admin = admin::ActiveModel {
|
||||
id: Set(ADMIN_ID),
|
||||
name: Set("admin".to_string()),
|
||||
public_key: Set("test".to_string()),
|
||||
|
|
@ -107,23 +113,23 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
session::ActiveModel {
|
||||
admin_session::ActiveModel {
|
||||
id: Set(Uuid::new_v4()),
|
||||
user_id: NotSet,
|
||||
admin_id: Set(Some(ADMIN_ID)),
|
||||
ip_address: Set("10.10.10.10".to_string()),
|
||||
created_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
expires_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
updated_at: Set(chrono::offset::Local::now().naive_local()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None).await.unwrap();
|
||||
let sessions = Query::find_related_candidate_sessions(&db, candidate).await.unwrap();
|
||||
assert_eq!(sessions.len(), 1);
|
||||
|
||||
let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)).await.unwrap();
|
||||
let sessions = Query::find_related_admin_sessions(&db, admin).await.unwrap();
|
||||
assert_eq!(sessions.len(), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
core/src/models/auth.rs
Normal file
17
core/src/models/auth.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use async_trait::async_trait;
|
||||
use entity::session;
|
||||
use sea_orm::{prelude::Uuid, DbConn};
|
||||
|
||||
use crate::error::ServiceError;
|
||||
|
||||
|
||||
#[async_trait]
|
||||
pub trait AuthenticableTrait {
|
||||
type User;
|
||||
type Session;
|
||||
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: Self::Session) -> Result<(), ServiceError>;
|
||||
async fn new_session(db: &DbConn, user: Self::User, ip_addr: String, password: String) -> Result<String, ServiceError>;
|
||||
async fn delete_old_sessions(db: &DbConn, user: Self::User, keep_n_recent: usize) -> Result<(), ServiceError>;
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod candidate_details;
|
||||
pub mod candidate;
|
||||
pub mod candidate;
|
||||
pub mod auth;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use entity::admin;
|
||||
use sea_orm::{prelude::Uuid, DbConn};
|
||||
use async_trait::async_trait;
|
||||
use entity::{admin, admin_session};
|
||||
use sea_orm::{prelude::Uuid, DbConn, IntoActiveModel};
|
||||
|
||||
use crate::{crypto, error::ServiceError, Query, Mutation};
|
||||
use crate::{crypto, error::ServiceError, Query, Mutation, models::auth::AuthenticableTrait};
|
||||
|
||||
use super::session_service::{AdminUser, SessionService};
|
||||
use super::session_service::SessionService;
|
||||
|
||||
pub struct AdminService;
|
||||
|
||||
|
|
@ -19,36 +20,87 @@ impl AdminService {
|
|||
|
||||
Ok(private_key)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(
|
||||
#[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 session_id = SessionService::new_session(db,
|
||||
None,
|
||||
Some(admin_id),
|
||||
let admin = Query::find_admin_by_id(db, admin_id).await?.ok_or(ServiceError::InvalidCredentials)?;
|
||||
|
||||
let session_id = Self::new_session(db,
|
||||
admin.clone(),
|
||||
password.clone(),
|
||||
ip_addr
|
||||
)
|
||||
.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?;
|
||||
async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<admin::Model, ServiceError> {
|
||||
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.unwrap())
|
||||
.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(())
|
||||
}
|
||||
|
||||
pub 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 new_session(
|
||||
db: &DbConn,
|
||||
admin: admin::Model,
|
||||
password: String,
|
||||
ip_addr: String,
|
||||
) -> Result<String, ServiceError> {
|
||||
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)]
|
||||
|
|
@ -65,7 +117,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 +132,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,14 +1,16 @@
|
|||
use entity::candidate;
|
||||
use sea_orm::{prelude::Uuid, DbConn};
|
||||
use async_trait::async_trait;
|
||||
use chrono::Duration;
|
||||
use entity::{candidate, session};
|
||||
use sea_orm::{prelude::Uuid, DbConn, IntoActiveModel};
|
||||
|
||||
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};
|
||||
use super::{session_service::SessionService, application_service::ApplicationService, portfolio_service::PortfolioService};
|
||||
|
||||
// TODO validation
|
||||
|
||||
|
|
@ -114,7 +116,7 @@ impl CandidateService {
|
|||
).await?;
|
||||
|
||||
|
||||
SessionService::revoke_all_sessions(db, Some(id), None).await?;
|
||||
Self::delete_old_sessions(db, candidate.clone(), 0).await?;
|
||||
Mutation::update_candidate_password_and_keys(db, candidate.clone(), new_password_hash, pubkey, encrypted_priv_key).await?;
|
||||
|
||||
// user might no have filled his details yet, but personal id number is filled from beginning
|
||||
|
|
@ -140,11 +142,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 +215,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 {
|
||||
|
|
@ -255,12 +224,103 @@ impl CandidateService {
|
|||
let field_of_study_prefix = &s[0..3];
|
||||
FIELD_OF_STUDY_PREFIXES.contains(&field_of_study_prefix)
|
||||
}
|
||||
|
||||
pub async fn extend_session_duration_to_14_days(db: &DbConn, session: session::Model) -> Result<(), ServiceError> {
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
if now >= session.updated_at.checked_add_signed(Duration::days(1)).ok_or(ServiceError::Unauthorized)? {
|
||||
let new_expires_at = now.checked_add_signed(Duration::days(14)).ok_or(ServiceError::Unauthorized)?;
|
||||
Mutation::update_session_expiration(db, session, new_expires_at).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthenticableTrait for CandidateService {
|
||||
type User = candidate::Model;
|
||||
type Session = session::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 = Self::new_session(db, candidate.clone(), 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> {
|
||||
let session = Query::find_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);
|
||||
}
|
||||
// Candidate authenticated
|
||||
|
||||
Self::extend_session_duration_to_14_days(db, session.clone()).await?;
|
||||
|
||||
let candidate = Query::find_candidate_by_id(db, session.candidate_id.unwrap())
|
||||
.await?
|
||||
.ok_or(ServiceError::CandidateNotFound)?;
|
||||
|
||||
Ok(candidate)
|
||||
}
|
||||
|
||||
async fn logout(db: &DbConn, session: session::Model) -> Result<(), ServiceError> {
|
||||
Mutation::delete_session(db, session.into_active_model()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn new_session(
|
||||
db: &DbConn,
|
||||
candidate: candidate::Model,
|
||||
password: String,
|
||||
ip_addr: String,
|
||||
) -> Result<String, ServiceError> {
|
||||
if !crypto::verify_password(password.clone(), candidate.code.clone()).await? {
|
||||
return Err(ServiceError::InvalidCredentials);
|
||||
}
|
||||
// user is authenticated, generate a new session
|
||||
let random_uuid: Uuid = Uuid::new_v4();
|
||||
|
||||
let session = Mutation::insert_candidate_session(db, random_uuid, candidate.application, ip_addr).await?;
|
||||
|
||||
Self::delete_old_sessions(db, candidate, 3).await?;
|
||||
|
||||
Ok(session.id.to_string())
|
||||
}
|
||||
async fn delete_old_sessions(
|
||||
db: &DbConn,
|
||||
candidate: candidate::Model,
|
||||
keep_n_recent: usize,
|
||||
) -> Result<(), ServiceError> {
|
||||
let sessions = Query::find_related_candidate_sessions(db, candidate)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|s| s.clone().into_active_model())
|
||||
.collect();
|
||||
|
||||
SessionService::delete_sessions(db, sessions, keep_n_recent).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};
|
||||
|
|
|
|||
|
|
@ -1,165 +1,36 @@
|
|||
use std::cmp::min;
|
||||
|
||||
use entity::{admin, candidate};
|
||||
use sea_orm::{prelude::Uuid, DatabaseConnection, ModelTrait, DbConn};
|
||||
use entity::{session_trait::UserSession};
|
||||
use sea_orm::{DbConn, ActiveModelTrait, ActiveModelBehavior};
|
||||
|
||||
use crate::{
|
||||
crypto::{self},
|
||||
error::ServiceError,
|
||||
Mutation, Query,
|
||||
Mutation,
|
||||
};
|
||||
|
||||
pub enum AdminUser {
|
||||
Admin(entity::admin::Model),
|
||||
Candidate(entity::candidate::Model),
|
||||
}
|
||||
|
||||
pub(in crate::services) struct SessionService;
|
||||
|
||||
impl SessionService {
|
||||
/// Delete n old sessions for user
|
||||
async fn delete_old_sessions(
|
||||
db: &DatabaseConnection,
|
||||
user_id: Option<i32>,
|
||||
admin_id: Option<i32>,
|
||||
keep_n_recent: usize,
|
||||
) -> Result<(), ServiceError> {
|
||||
let mut sessions = Query::find_sessions_by_user_id(db, user_id, admin_id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
sessions.sort_by_key(|s| s.created_at);
|
||||
/// Check if session is valid
|
||||
pub async fn is_valid<T>(session: &T) -> Result<bool, ServiceError> where T: UserSession {
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
if now >= session.expires_at().await {
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete list of sessions
|
||||
pub async fn delete_sessions<T>(db: &DbConn, sessions: Vec<T>, keep_n_recent: usize) -> Result<(), ServiceError> where T: ActiveModelTrait + std::marker::Send + ActiveModelBehavior {
|
||||
for session in sessions
|
||||
.iter()
|
||||
.take(sessions.len() - min(sessions.len(), keep_n_recent))
|
||||
{
|
||||
Mutation::delete_session(db, session.id).await.unwrap();
|
||||
Mutation::delete_session(db, session.clone()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Authenticate user by application id and password and generate a new session
|
||||
pub async fn new_session(
|
||||
db: &DatabaseConnection,
|
||||
user_id: Option<i32>,
|
||||
admin_id: Option<i32>,
|
||||
password: String,
|
||||
ip_addr: String,
|
||||
) -> Result<String, ServiceError> {
|
||||
if user_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)),
|
||||
};
|
||||
|
||||
// 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 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)),
|
||||
};
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
// user is authenticated, generate a new session
|
||||
let random_uuid: Uuid = Uuid::new_v4();
|
||||
|
||||
let session =
|
||||
match Mutation::insert_session(db, user_id, admin_id, random_uuid, ip_addr).await {
|
||||
Ok(session) => session,
|
||||
Err(e) => {
|
||||
eprintln!("Error creating session: {}", e);
|
||||
return Err(ServiceError::DbError(e));
|
||||
}
|
||||
};
|
||||
|
||||
// delete old sessions
|
||||
SessionService::delete_old_sessions(db, user_id, admin_id, 3)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(session.id.to_string())
|
||||
}
|
||||
|
||||
pub async fn revoke_all_sessions(db: &DbConn, user_id: Option<i32>, admin_id: Option<i32>) -> Result<(), ServiceError> {
|
||||
Self::delete_old_sessions(db, user_id, admin_id, 0).await
|
||||
}
|
||||
|
||||
/// Authenticate user by session id
|
||||
/// Return user model if session is valid
|
||||
pub async fn auth_user_session(
|
||||
db: &DatabaseConnection,
|
||||
uuid: Uuid,
|
||||
) -> Result<AdminUser, ServiceError> {
|
||||
let session = match Query::find_session_by_uuid(db, uuid).await {
|
||||
Ok(session) => match session {
|
||||
Some(session) => session,
|
||||
None => return Err(ServiceError::UserNotFoundBySessionId),
|
||||
},
|
||||
Err(e) => return Err(ServiceError::DbError(e)),
|
||||
};
|
||||
|
||||
let now = chrono::Utc::now().naive_utc();
|
||||
// check if session is expired
|
||||
if now > session.expires_at {
|
||||
// delete session
|
||||
Mutation::delete_session(db, session.id).await.unwrap();
|
||||
return Err(ServiceError::ExpiredSession);
|
||||
}
|
||||
|
||||
let candidate = session.find_related(candidate::Entity).one(db).await;
|
||||
let admin = session.find_related(admin::Entity).one(db).await;
|
||||
|
||||
if candidate.is_err() || admin.is_err() {
|
||||
eprintln!("Kurva");
|
||||
return Err(ServiceError::UserNotFoundBySessionId);
|
||||
}
|
||||
|
||||
if candidate.is_ok() {
|
||||
if let Some(candidate) = candidate.unwrap() {
|
||||
return Ok(AdminUser::Candidate(candidate));
|
||||
}
|
||||
}
|
||||
|
||||
if admin.is_ok() {
|
||||
if let Some(admin) = admin.unwrap() {
|
||||
return Ok(AdminUser::Admin(admin));
|
||||
}
|
||||
}
|
||||
return Err(ServiceError::UserNotFoundBySessionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,8 +42,8 @@ mod tests {
|
|||
|
||||
use crate::{
|
||||
crypto,
|
||||
services::{application_service::ApplicationService, session_service::SessionService},
|
||||
utils::db::get_memory_sqlite_connection,
|
||||
services::{application_service::ApplicationService, candidate_service::CandidateService},
|
||||
utils::db::get_memory_sqlite_connection, models::auth::AuthenticableTrait,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -204,7 +75,7 @@ mod tests {
|
|||
async fn test_candidate_session_correct_password() {
|
||||
let db = &get_memory_sqlite_connection().await;
|
||||
|
||||
ApplicationService::create_candidate_with_parent(
|
||||
let candidate = ApplicationService::create_candidate_with_parent(
|
||||
db,
|
||||
103151,
|
||||
&"Tajny_kod".to_string(),
|
||||
|
|
@ -215,10 +86,9 @@ mod tests {
|
|||
.0;
|
||||
|
||||
// correct password
|
||||
let session = SessionService::new_session(
|
||||
let session = CandidateService::new_session(
|
||||
db,
|
||||
Some(103151),
|
||||
None,
|
||||
candidate,
|
||||
"Tajny_kod".to_string(),
|
||||
"127.0.0.1".to_string(),
|
||||
)
|
||||
|
|
@ -226,7 +96,7 @@ mod tests {
|
|||
.unwrap();
|
||||
// println!("{}", session.err().unwrap().1);
|
||||
assert!(
|
||||
SessionService::auth_user_session(db, Uuid::parse_str(&session).unwrap())
|
||||
CandidateService::auth(db, Uuid::parse_str(&session).unwrap())
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
|
|
@ -247,10 +117,9 @@ mod tests {
|
|||
.0;
|
||||
|
||||
// incorrect password
|
||||
assert!(SessionService::new_session(
|
||||
assert!(CandidateService::new_session(
|
||||
db,
|
||||
Some(candidate_form.application),
|
||||
None,
|
||||
candidate_form,
|
||||
"Spatny_kod".to_string(),
|
||||
"127.0.0.1".to_string()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use entity::admin_session;
|
||||
use sea_orm::DbConn;
|
||||
|
||||
use crate::Query;
|
||||
|
|
@ -24,10 +25,12 @@ pub async fn get_memory_sqlite_connection() -> sea_orm::DbConn {
|
|||
let stmt2: TableCreateStatement = schema.create_table_from_entity(admin::Entity);
|
||||
let stmt3: TableCreateStatement = schema.create_table_from_entity(session::Entity);
|
||||
let stmt4: TableCreateStatement = schema.create_table_from_entity(parent::Entity);
|
||||
let stmt5: TableCreateStatement = schema.create_table_from_entity(admin_session::Entity);
|
||||
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
|
||||
db.execute(db.get_database_backend().build(&stmt2)).await.unwrap();
|
||||
db.execute(db.get_database_backend().build(&stmt3)).await.unwrap();
|
||||
db.execute(db.get_database_backend().build(&stmt4)).await.unwrap();
|
||||
db.execute(db.get_database_backend().build(&stmt5)).await.unwrap();
|
||||
db
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ path = "src/lib.rs"
|
|||
|
||||
[dependencies]
|
||||
chrono = "^0.4"
|
||||
async-trait = "0.1.60"
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "^0.10"
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ pub struct Model {
|
|||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::session::Entity")]
|
||||
Session,
|
||||
#[sea_orm(has_many = "super::admin_session::Entity")]
|
||||
AdminSession,
|
||||
}
|
||||
|
||||
impl Related<super::session::Entity> for Entity {
|
||||
impl Related<super::admin_session::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Session.def()
|
||||
Relation::AdminSession.def()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
47
entity/src/admin_session.rs
Normal file
47
entity/src/admin_session.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
use crate::session_trait::UserSession;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "admin_session")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub admin_id: Option<i32>,
|
||||
pub ip_address: String,
|
||||
pub created_at: DateTime,
|
||||
pub expires_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::admin::Entity",
|
||||
from = "Column::AdminId",
|
||||
to = "super::admin::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Admin,
|
||||
}
|
||||
|
||||
impl Related<super::admin::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Admin.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserSession for Model {
|
||||
async fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
async fn expires_at(&self) -> chrono::NaiveDateTime {
|
||||
self.expires_at
|
||||
}
|
||||
}
|
||||
|
|
@ -4,3 +4,5 @@ pub mod admin;
|
|||
pub mod candidate;
|
||||
pub mod parent;
|
||||
pub mod session;
|
||||
pub mod admin_session;
|
||||
pub mod session_trait;
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
pub mod prelude;
|
||||
|
||||
pub mod admin;
|
||||
pub mod admin_session;
|
||||
pub mod candidate;
|
||||
pub mod parent;
|
||||
pub mod session;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
|
||||
|
||||
pub use super::admin::Entity as Admin;
|
||||
pub use super::admin_session::Entity as AdminSession;
|
||||
pub use super::candidate::Entity as Candidate;
|
||||
pub use super::parent::Entity as Parent;
|
||||
pub use super::session::Entity as Session;
|
||||
|
|
|
|||
|
|
@ -2,31 +2,25 @@
|
|||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
use crate::session_trait::UserSession;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "session")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub user_id: Option<i32>,
|
||||
pub admin_id: Option<i32>,
|
||||
pub candidate_id: Option<i32>,
|
||||
pub ip_address: String,
|
||||
pub created_at: DateTime,
|
||||
pub expires_at: DateTime,
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::admin::Entity",
|
||||
from = "Column::AdminId",
|
||||
to = "super::admin::Column::Id",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Admin,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::candidate::Entity",
|
||||
from = "Column::UserId",
|
||||
from = "Column::CandidateId",
|
||||
to = "super::candidate::Column::Application",
|
||||
on_update = "Cascade",
|
||||
on_delete = "Cascade"
|
||||
|
|
@ -34,12 +28,6 @@ pub enum Relation {
|
|||
Candidate,
|
||||
}
|
||||
|
||||
impl Related<super::admin::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Admin.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::candidate::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Candidate.def()
|
||||
|
|
@ -47,3 +35,13 @@ impl Related<super::candidate::Entity> for Entity {
|
|||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserSession for Model {
|
||||
async fn id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
async fn expires_at(&self) -> chrono::NaiveDateTime {
|
||||
self.expires_at
|
||||
}
|
||||
}
|
||||
8
entity/src/session_trait.rs
Normal file
8
entity/src/session_trait.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use async_trait::async_trait;
|
||||
use sea_orm::prelude::Uuid;
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserSession {
|
||||
async fn expires_at(&self) -> chrono::NaiveDateTime;
|
||||
async fn id(&self) -> Uuid;
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ mod m20221025_154422_create_session;
|
|||
mod m20221027_194728_session_create_user_fk;
|
||||
mod m20221028_194728_session_create_admin_fk;
|
||||
mod m20221112_112212_create_parent_candidate_fk;
|
||||
mod m20221221_162232_create_admin_session;
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
|
@ -20,8 +21,10 @@ impl MigratorTrait for Migrator {
|
|||
Box::new(m20221024_134454_insert_sample_admin::Migration::default()),
|
||||
Box::new(m20221025_154422_create_session::Migration),
|
||||
Box::new(m20221027_194728_session_create_user_fk::Migration),
|
||||
Box::new(m20221028_194728_session_create_admin_fk::Migration),
|
||||
Box::new(m20221112_112212_create_parent_candidate_fk::Migration),
|
||||
Box::new(m20221221_162232_create_admin_session::Migration),
|
||||
Box::new(m20221028_194728_session_create_admin_fk::Migration),
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ impl MigrationTrait for Migration {
|
|||
.unique_key()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Session::UserId).integer())
|
||||
.col(ColumnDef::new(Session::AdminId).integer())
|
||||
.col(ColumnDef::new(Session::CandidateId).integer())
|
||||
.col(ColumnDef::new(Session::IpAddress).string().not_null())
|
||||
.col(ColumnDef::new(Session::CreatedAt).date_time().not_null())
|
||||
.col(ColumnDef::new(Session::ExpiresAt).date_time().not_null())
|
||||
.col(ColumnDef::new(Session::UpdatedAt).date_time().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
|
|
@ -39,9 +39,9 @@ impl MigrationTrait for Migration {
|
|||
pub enum Session {
|
||||
Table,
|
||||
Id,
|
||||
UserId,
|
||||
AdminId,
|
||||
CandidateId,
|
||||
IpAddress,
|
||||
CreatedAt,
|
||||
ExpiresAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ impl MigrationTrait for Migration {
|
|||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_foreign_key(ForeignKey::create()
|
||||
.name("user_fk")
|
||||
.from(Session::Table, Session::UserId)
|
||||
.from(Session::Table, Session::CandidateId)
|
||||
.to(Candidate::Table, Candidate::Application)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::{m20221025_154422_create_session::Session, m20221024_111310_create_admin::Admin};
|
||||
use crate::{m20221221_162232_create_admin_session::AdminSession, m20221024_111310_create_admin::Admin};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
|
@ -10,7 +10,7 @@ impl MigrationTrait for Migration {
|
|||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_foreign_key(ForeignKey::create()
|
||||
.name("admin_fk")
|
||||
.from(Session::Table, Session::AdminId)
|
||||
.from(AdminSession::Table, AdminSession::AdminId)
|
||||
.to(Admin::Table, Admin::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
|
|
@ -20,7 +20,7 @@ impl MigrationTrait for Migration {
|
|||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_foreign_key(ForeignKey::drop()
|
||||
.name("admin_fk")
|
||||
.table(Session::Table)
|
||||
.table(AdminSession::Table)
|
||||
.to_owned()).await
|
||||
}
|
||||
}
|
||||
48
migration/src/m20221221_162232_create_admin_session.rs
Normal file
48
migration/src/m20221221_162232_create_admin_session.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(AdminSession::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(AdminSession::Id)
|
||||
.uuid()
|
||||
.unique_key()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AdminSession::AdminId).integer())
|
||||
.col(ColumnDef::new(AdminSession::IpAddress).string().not_null())
|
||||
.col(ColumnDef::new(AdminSession::CreatedAt).date_time().not_null())
|
||||
.col(ColumnDef::new(AdminSession::ExpiresAt).date_time().not_null())
|
||||
.col(ColumnDef::new(AdminSession::UpdatedAt).date_time().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(AdminSession::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Learn more at https://docs.rs/sea-query#iden
|
||||
#[derive(Iden)]
|
||||
pub enum AdminSession {
|
||||
Table,
|
||||
Id,
|
||||
AdminId,
|
||||
IpAddress,
|
||||
CreatedAt,
|
||||
ExpiresAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
Loading…
Reference in a new issue