diff --git a/api/src/lib.rs b/api/src/lib.rs index d5752f4..edd5814 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -40,6 +40,7 @@ pub fn rocket() -> Rocket{ "/candidate/", routes![ routes::candidate::login, + routes::candidate::logout, routes::candidate::whoami, routes::candidate::get_details, ], @@ -67,6 +68,7 @@ pub fn rocket() -> Rocket{ "/admin/", routes![ routes::admin::login, + routes::admin::logout, routes::admin::whoami, routes::admin::hello, routes::admin::create_candidate, diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index a9348ba..3700704 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -2,7 +2,7 @@ use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use portfolio_core::{ crypto::random_8_char_string, - services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, responses::CandidateResponse, candidate_details::ApplicationDetails, + services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, responses::CandidateResponse, candidate_details::ApplicationDetails, sea_orm::prelude::Uuid, }; use requests::{AdminLoginRequest, RegisterRequest}; use rocket::http::{Cookie, Status, CookieJar}; @@ -51,6 +51,25 @@ pub async fn login( return Ok(response); } +#[post("/logout")] +pub async fn logout(conn: Connection<'_, Db>, _session: AdminAuth, cookies: &CookieJar<'_>,) -> Result<(), Custom> { + let db = conn.into_inner(); + + let cookie = cookies.get_private("id") // unwrap would be safe here because of the auth guard + .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 res = AdminService::logout(db, session_id) + .await + .map_err(|e| Custom(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), e.to_string()))?; + + cookies.remove_private(Cookie::named("id")); + cookies.remove_private(Cookie::named("key")); + + Ok(res) +} + #[get("/whoami")] pub async fn whoami(session: AdminAuth) -> Result> { diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index 7068bb6..6328b23 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -1,6 +1,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use portfolio_core::candidate_details::ApplicationDetails; +use portfolio_core::sea_orm::prelude::Uuid; use portfolio_core::services::application_service::ApplicationService; use portfolio_core::services::candidate_service::CandidateService; use portfolio_core::services::portfolio_service::{PortfolioService, SubmissionProgress}; @@ -51,6 +52,25 @@ pub async fn login( return Ok(response); } +#[post("/logout")] +pub async fn logout(conn: Connection<'_, Db>, _session: CandidateAuth, cookies: &CookieJar<'_>,) -> Result<(), Custom> { + let db = conn.into_inner(); + + let cookie = cookies.get_private("id") // unwrap would be safe here because of the auth guard + .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 res = CandidateService::logout(db, session_id) + .await + .map_err(|e| Custom(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), e.to_string()))?; + + cookies.remove_private(Cookie::named("id")); + cookies.remove_private(Cookie::named("key")); + + Ok(res) +} + #[get("/whoami")] pub async fn whoami(session: CandidateAuth) -> Result> { let candidate: entity::candidate::Model = session.into(); diff --git a/core/src/database/query/session.rs b/core/src/database/query/session.rs index 0361997..6a1eb14 100644 --- a/core/src/database/query/session.rs +++ b/core/src/database/query/session.rs @@ -18,9 +18,13 @@ impl Query { user_id: Option, admin_id: Option, ) -> Result, DbErr> { - Session::find() - .filter(session::Column::UserId.eq(user_id)) - .filter(session::Column::AdminId.eq(admin_id)) + if user_id.is_some() { + Session::find() + .filter(session::Column::UserId.eq(user_id)) + } else { + Session::find() + .filter(session::Column::AdminId.eq(admin_id)) + } .all(db) .await } diff --git a/core/src/services/admin_service.rs b/core/src/services/admin_service.rs index 9e23b65..fd7a62a 100644 --- a/core/src/services/admin_service.rs +++ b/core/src/services/admin_service.rs @@ -1,7 +1,7 @@ use entity::admin; use sea_orm::{prelude::Uuid, DbConn}; -use crate::{crypto, error::ServiceError, Query}; +use crate::{crypto, error::ServiceError, Query, Mutation}; use super::session_service::{AdminUser, SessionService}; @@ -38,6 +38,11 @@ impl AdminService { Ok((session_id, private_key)) } + pub async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> { + Mutation::delete_session(db, session_id).await?; + Ok(()) + } + pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result { match SessionService::auth_user_session(db, session_uuid).await? { AdminUser::Admin(admin) => Ok(admin), diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 264535a..a62a9e6 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -131,6 +131,11 @@ impl CandidateService { Ok(new_password_plain) } + pub async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> { + Mutation::delete_session(db, session_id).await?; + Ok(()) + } + pub(in crate::services) async fn add_candidate_details( db: &DbConn, candidate: candidate::Model,