From 137039df44e480507a17eba6709bc7a59c5ca5f5 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 18:33:37 +0200 Subject: [PATCH] feat: limit n of sessions in db save client's ip address --- api/src/lib.rs | 9 ++++-- core/src/mutation.rs | 3 +- core/src/query.rs | 4 +-- core/src/services/candidate_service.rs | 38 +++++++++++++++++++------- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 98821d4..1a066a6 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate rocket; +use std::net::SocketAddr; + use portfolio_core::error::ServiceError; use portfolio_core::services::candidate_service::CandidateService; use requests::LoginRequest; @@ -59,13 +61,14 @@ async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result, login_form: Json) -> Result> { +async fn login(conn: Connection<'_, Db>, login_form: Json, ip_addr: SocketAddr) -> Result> { let db = conn.into_inner(); println!("{} {}", login_form.application_id, login_form.password); let session_token = CandidateService::new_session(db, - login_form.application_id, - login_form.password.to_string() + login_form.application_id, + login_form.password.to_string(), + ip_addr.ip().to_string() ).await; if session_token.is_ok() { diff --git a/core/src/mutation.rs b/core/src/mutation.rs index 8e8ccc8..a9b9450 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -31,11 +31,12 @@ impl Mutation { db: &DbConn, user_id: i32, random_uuid: Uuid, + ip_addr: String, ) -> Result { session::ActiveModel { id: Set(random_uuid), user_id: Set(user_id), - ip_address: Set("127.0.0.1".to_string()), + 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()), } diff --git a/core/src/query.rs b/core/src/query.rs index 4a356e9..35f0883 100644 --- a/core/src/query.rs +++ b/core/src/query.rs @@ -15,10 +15,10 @@ impl Query { } // find session by user id - pub async fn find_session_by_user_id(db: &DbConn, user_id: i32) -> Result, DbErr> { + pub async fn find_sessions_by_user_id(db: &DbConn, user_id: i32) -> Result, DbErr> { Session::find() .filter(session::Column::UserId.eq(user_id)) - .one(db) + .all(db) .await } } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 09606dc..73486e7 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,13 +1,27 @@ -use chrono::Duration; +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}, Mutation}; +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}; pub struct CandidateService; impl CandidateService { - pub async fn new_session(db: &DatabaseConnection, user_id: i32, password: String) -> Result { + async fn delete_old_sessions(db: &DatabaseConnection, user_id: i32, keep_n_recent: usize) -> Result<(), ServiceError> { + let mut sessions = Query::find_sessions_by_user_id(db, user_id).await.unwrap(); + + sessions.sort_by_key(|s| s.created_at); + + + for session in sessions.iter().take(sessions.len() - min(sessions.len(), keep_n_recent)) { + Mutation::delete_session(db, session.id).await.unwrap(); + } + + Ok(()) + } + + pub async fn new_session(db: &DatabaseConnection, user_id: i32, password: String, ip_addr: String) -> Result { let candidate = match Query::find_candidate_by_id(db, user_id).await { Ok(candidate) => match candidate { Some(candidate) => candidate, @@ -27,15 +41,19 @@ impl CandidateService { } // TODO delete old sessions? - + + // user is authenticated, generate a session let random_uuid: Uuid = Uuid::new_v4(); - let session = match Mutation::insert_session(db, user_id, random_uuid).await { + let session = match Mutation::insert_session(db, user_id, random_uuid, ip_addr).await { Ok(session) => session, Err(_) => return Err(DB_ERROR) }; + // delete old sessions + CandidateService::delete_old_sessions(db, candidate.application, 3).await.ok(); // TODO move to dotenv + Ok(session.id.to_string()) } @@ -48,13 +66,12 @@ impl CandidateService { Err(_) => {return Err(DB_ERROR)} }; - let limit = session.created_at.checked_add_signed(Duration::days(1)).unwrap(); let now = chrono::Utc::now().naive_utc(); // check if session is expired - if now > limit { + if now > session.expires_at { // delete session Mutation::delete_session(db, session.id).await.unwrap(); - return Err(USER_NOT_FOUND_BY_SESSION_ID) + return Err(EXPIRED_SESSION_ERROR) } let candidate = match session.find_related(candidate::Entity).one(db).await { @@ -125,7 +142,8 @@ mod tests { let session = CandidateService::new_session( db, 5555555, - "Tajny_kod".to_string() + "Tajny_kod".to_string(), + "127.0.0.1".to_string(), ) .await.ok().unwrap(); // println!("{}", session.err().unwrap().1); @@ -149,7 +167,7 @@ mod tests { // incorrect password assert!( - CandidateService::new_session(db, candidate_form.application, "Spatny_kod".to_string()).await.is_err() + CandidateService::new_session(db, candidate_form.application, "Spatny_kod".to_string(), "127.0.0.1".to_string()).await.is_err() ); } } \ No newline at end of file