From fb27fcff60955d7178e156641562d81d3b7321c3 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 19:33:30 +0200 Subject: [PATCH] feat: session authentication instead of jwt --- api/src/lib.rs | 22 ++++++------ core/src/error.rs | 3 ++ core/src/mutation.rs | 12 +++++++ core/src/query.rs | 8 +++++ core/src/services/candidate_service.rs | 49 ++++++++++++++++++++++++-- core/src/services/mod.rs | 3 +- core/src/services/session_service.rs | 27 -------------- 7 files changed, 81 insertions(+), 43 deletions(-) delete mode 100644 core/src/services/session_service.rs diff --git a/api/src/lib.rs b/api/src/lib.rs index 75fe90d..13f6e7f 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -4,7 +4,6 @@ extern crate rocket; use guard::candidate_jwt::TokenRequest; use portfolio_core::error::ServiceError; use portfolio_core::services::candidate_service::CandidateService; -use portfolio_core::services::session_service::SessionService; use requests::LoginRequest; use rocket::http::Status; use rocket::{Rocket, Build}; @@ -48,15 +47,15 @@ async fn create(conn: Connection<'_, Db>, post_form: Json) -> Ok(plain_text_password) } -#[get("/refresh")] +/* #[get("/refresh")] async fn refresh_token(conn: Connection<'_, Db>, token_req: Result) -> Result> { let db = conn.into_inner(); let jwt = token_req.ok().unwrap().to_token(); - let refresh_token = SessionService::new_refresh_token(db, jwt.application_id).await; + let refresh_token = SessionService::login_user(db, jwt.application_id).await; Ok(refresh_token.ok().unwrap()) -} +} */ #[get("/validate_refresh")] async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result) -> Result> { @@ -75,17 +74,18 @@ async fn login(conn: Connection<'_, Db>, login_form: Json) -> Resu let db = conn.into_inner(); println!("{} {}", login_form.application_id, login_form.password); - let jwt = CandidateService::login(db, - login_form.application_id, - login_form.password.to_owned()).await; + let session_token = CandidateService::get_session(db, + login_form.application_id, + login_form.password.to_string() + ).await; - if jwt.is_ok() { + if session_token.is_ok() { return Ok( - jwt.ok().unwrap() + session_token.ok().unwrap() ); } else { return Err( - custom_err_from_service_err(jwt.err().unwrap()) + custom_err_from_service_err(session_token.err().unwrap()) ) } } @@ -125,7 +125,7 @@ async fn start() -> Result<(), rocket::Error> { .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) //.mount("/", FileServer::from(relative!("/static"))) - .mount("/", routes![create, login, hello, whoami, refresh_token, validate]) + .mount("/", routes![create, login, hello, whoami, validate]) .register("/", catchers![]) .launch() .await diff --git a/core/src/error.rs b/core/src/error.rs index e70cba8..d8afda9 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -4,6 +4,9 @@ pub struct Status { pub const INVALID_CREDENTIALS_ERROR: ServiceError = ServiceError(Status { code: 401 }, "Invalid credentials"); +pub const EXPIRED_SESSION_ERROR: ServiceError = ServiceError(Status { code: 401 }, + "Session expired, please login again"); + pub const JWT_ERROR: ServiceError = ServiceError(Status { code: 500 }, "Error while encoding JWT"); diff --git a/core/src/mutation.rs b/core/src/mutation.rs index 9bc32bf..e1d6bd3 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -43,4 +43,16 @@ impl Mutation { .insert(db) .await } + + pub async fn delete_session( + db: &DbConn, + session_id: Uuid + ) -> Result { + session::ActiveModel { + id: Set(session_id), + ..Default::default() + } + .delete(db) + .await + } } diff --git a/core/src/query.rs b/core/src/query.rs index bcc805b..06c9807 100644 --- a/core/src/query.rs +++ b/core/src/query.rs @@ -13,4 +13,12 @@ impl Query { pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result, DbErr> { Session::find_by_id(uuid).one(db).await } + + // find session by user id + pub async fn find_session_by_user_id(db: &DbConn, user_id: i32) -> Result, DbErr> { + Session::find() + .filter(session::Column::UserId.eq(user_id)) + .one(db) + .await + } } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 7e28180..e658a15 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,12 +1,13 @@ +use chrono::Duration; use entity::candidate; use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait}; -use crate::{crypto, Query, token::{generate_candidate_token, candidate_token::CandidateToken}, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID, USER_NOT_FOUND_BY_SESSION_ID}}; +use crate::{crypto::{self, hash_sha256}, Query, token::{generate_candidate_token, candidate_token::CandidateToken}, error::{ServiceError, USER_NOT_FOUND_ERROR, INVALID_CREDENTIALS_ERROR, DB_ERROR, USER_NOT_FOUND_BY_JWT_ID, USER_NOT_FOUND_BY_SESSION_ID}, Mutation}; pub struct CandidateService; impl CandidateService { - + #[deprecated(note = "Use login instead")] pub async fn login(db: &DatabaseConnection, id: i32, password: String) -> Result { let candidate = match Query::find_candidate_by_id(db, id).await { Ok(candidate) => match candidate { @@ -26,7 +27,40 @@ impl CandidateService { let jwt = generate_candidate_token(candidate); // TODO better error handling Ok(jwt) - + } + + pub async fn get_session(db: &DatabaseConnection, user_id: i32, password: String) -> Result { + let candidate = match Query::find_candidate_by_id(db, user_id).await { + Ok(candidate) => match candidate { + Some(candidate) => candidate, + None => return Err(USER_NOT_FOUND_ERROR) + }, + Err(_) => {return Err(DB_ERROR)} + }; + + // compare passwords + match crypto::verify_password(&password, &candidate.code) { + Ok(valid) => { + if !valid { + return Err(INVALID_CREDENTIALS_ERROR) + } + }, + Err(_) => {return Err(INVALID_CREDENTIALS_ERROR)} + } + + // TODO delete old sessions? + + // user is authenticated, generate a session + let random_uuid: Uuid = Uuid::new_v4(); + + let jwt = generate_candidate_token(candidate); + + let session = match Mutation::insert_session(db, user_id, random_uuid, hash_sha256(jwt)).await { + Ok(session) => session, + Err(_) => return Err(DB_ERROR) + }; + + Ok(session.id.to_string()) } pub async fn authenticate_candidate(db: &DatabaseConnection, token: CandidateToken) -> Result { @@ -50,6 +84,15 @@ 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 { + // delete session + Mutation::delete_session(db, session.id).await.unwrap(); + return Err(USER_NOT_FOUND_BY_SESSION_ID) + } + let candidate = match session.find_related(candidate::Entity).one(db).await { Ok(candidate) => match candidate { Some(candidate) => candidate, diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index 1bafcbb..1992ce2 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -1,2 +1 @@ -pub mod candidate_service; -pub mod session_service; \ No newline at end of file +pub mod candidate_service; \ No newline at end of file diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs deleted file mode 100644 index dade2b4..0000000 --- a/core/src/services/session_service.rs +++ /dev/null @@ -1,27 +0,0 @@ -use sea_orm::{prelude::Uuid, DbConn}; - -use crate::{token::{generate_candidate_token}, Query, error::{USER_NOT_FOUND_ERROR, DB_ERROR, ServiceError}, crypto::hash_sha256, Mutation}; - -pub struct SessionService; - -impl SessionService { - pub async fn new_refresh_token(db: &DbConn, id: i32) -> Result { - let candidate = match Query::find_candidate_by_id(db, id).await { - Ok(candidate) => match candidate { - Some(candidate) => candidate, - None => return Err(USER_NOT_FOUND_ERROR) - }, - Err(_) => {return Err(DB_ERROR)} - }; - let random_uuid: Uuid = Uuid::new_v4(); - - let jwt = generate_candidate_token(candidate); - - let session = match Mutation::insert_session(db, id, random_uuid, hash_sha256(jwt)).await { - Ok(session) => session, - Err(_) => return Err(DB_ERROR) - }; - - Ok(session.id.to_string()) - } -} \ No newline at end of file