feat: limit n of sessions in db

save client's ip address
This commit is contained in:
Sebastian Pravda 2022-10-29 18:33:37 +02:00
parent dd7386882b
commit 137039df44
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
4 changed files with 38 additions and 16 deletions

View file

@ -1,6 +1,8 @@
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
use std::net::SocketAddr;
use portfolio_core::error::ServiceError; use portfolio_core::error::ServiceError;
use portfolio_core::services::candidate_service::CandidateService; use portfolio_core::services::candidate_service::CandidateService;
use requests::LoginRequest; use requests::LoginRequest;
@ -59,13 +61,14 @@ async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result<UUIDCookie, Stat
} }
#[post("/login", data = "<login_form>")] #[post("/login", data = "<login_form>")]
async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>) -> Result<String, Custom<String>> { async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>, ip_addr: SocketAddr) -> Result<String, Custom<String>> {
let db = conn.into_inner(); let db = conn.into_inner();
println!("{} {}", login_form.application_id, login_form.password); println!("{} {}", login_form.application_id, login_form.password);
let session_token = CandidateService::new_session(db, let session_token = CandidateService::new_session(db,
login_form.application_id, login_form.application_id,
login_form.password.to_string() login_form.password.to_string(),
ip_addr.ip().to_string()
).await; ).await;
if session_token.is_ok() { if session_token.is_ok() {

View file

@ -31,11 +31,12 @@ impl Mutation {
db: &DbConn, db: &DbConn,
user_id: i32, user_id: i32,
random_uuid: Uuid, random_uuid: Uuid,
ip_addr: String,
) -> Result<session::Model, DbErr> { ) -> Result<session::Model, DbErr> {
session::ActiveModel { session::ActiveModel {
id: Set(random_uuid), id: Set(random_uuid),
user_id: Set(user_id), 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()), created_at: Set(Utc::now().naive_local()),
expires_at: Set(Utc::now().naive_local().checked_add_signed(Duration::days(1)).unwrap()), expires_at: Set(Utc::now().naive_local().checked_add_signed(Duration::days(1)).unwrap()),
} }

View file

@ -15,10 +15,10 @@ impl Query {
} }
// find session by user id // find session by user id
pub async fn find_session_by_user_id(db: &DbConn, user_id: i32) -> Result<Option<session::Model>, DbErr> { pub async fn find_sessions_by_user_id(db: &DbConn, user_id: i32) -> Result<Vec<session::Model>, DbErr> {
Session::find() Session::find()
.filter(session::Column::UserId.eq(user_id)) .filter(session::Column::UserId.eq(user_id))
.one(db) .all(db)
.await .await
} }
} }

View file

@ -1,13 +1,27 @@
use chrono::Duration; use std::cmp::min;
use entity::candidate; use entity::candidate;
use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait}; 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; pub struct CandidateService;
impl CandidateService { impl CandidateService {
pub async fn new_session(db: &DatabaseConnection, user_id: i32, password: String) -> Result<String, ServiceError> { 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<String, ServiceError> {
let candidate = match Query::find_candidate_by_id(db, user_id).await { let candidate = match Query::find_candidate_by_id(db, user_id).await {
Ok(candidate) => match candidate { Ok(candidate) => match candidate {
Some(candidate) => candidate, Some(candidate) => candidate,
@ -28,14 +42,18 @@ impl CandidateService {
// TODO delete old sessions? // TODO delete old sessions?
// user is authenticated, generate a session // user is authenticated, generate a session
let random_uuid: Uuid = Uuid::new_v4(); 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, Ok(session) => session,
Err(_) => return Err(DB_ERROR) 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()) Ok(session.id.to_string())
} }
@ -48,13 +66,12 @@ impl CandidateService {
Err(_) => {return Err(DB_ERROR)} Err(_) => {return Err(DB_ERROR)}
}; };
let limit = session.created_at.checked_add_signed(Duration::days(1)).unwrap();
let now = chrono::Utc::now().naive_utc(); let now = chrono::Utc::now().naive_utc();
// check if session is expired // check if session is expired
if now > limit { if now > session.expires_at {
// delete session // delete session
Mutation::delete_session(db, session.id).await.unwrap(); 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 { let candidate = match session.find_related(candidate::Entity).one(db).await {
@ -125,7 +142,8 @@ mod tests {
let session = CandidateService::new_session( let session = CandidateService::new_session(
db, db,
5555555, 5555555,
"Tajny_kod".to_string() "Tajny_kod".to_string(),
"127.0.0.1".to_string(),
) )
.await.ok().unwrap(); .await.ok().unwrap();
// println!("{}", session.err().unwrap().1); // println!("{}", session.err().unwrap().1);
@ -149,7 +167,7 @@ mod tests {
// incorrect password // incorrect password
assert!( 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()
); );
} }
} }