From f440c5ad72f9d7cef97c9f6cc38658733062ecfe Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Thu, 27 Oct 2022 21:05:28 +0200 Subject: [PATCH 01/13] feat: user_id fk in session table --- entity/src/candidate.rs | 11 +++++++- entity/src/session.rs | 17 +++++++++++- migration/src/lib.rs | 2 ++ .../src/m20221024_121621_create_candidate.rs | 2 +- .../src/m20221025_154422_create_session.rs | 20 +++++++++++++- ...m20221027_194728_session_create_user_fk.rs | 26 +++++++++++++++++++ 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 migration/src/m20221027_194728_session_create_user_fk.rs diff --git a/entity/src/candidate.rs b/entity/src/candidate.rs index d9199ce..4fb441e 100644 --- a/entity/src/candidate.rs +++ b/entity/src/candidate.rs @@ -37,6 +37,15 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_many = "super::session::Entity")] + Session, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Session.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/entity/src/session.rs b/entity/src/session.rs index 2df872b..4f0be16 100644 --- a/entity/src/session.rs +++ b/entity/src/session.rs @@ -14,6 +14,21 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm( + belongs_to = "super::candidate::Entity", + from = "Column::UserId", + to = "super::candidate::Column::Application", + on_update = "Cascade", + on_delete = "Cascade" + )] + Candidate, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Candidate.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/migration/src/lib.rs b/migration/src/lib.rs index adbef73..a8f21b1 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -5,6 +5,7 @@ mod m20221024_121621_create_candidate; mod m20221024_124701_create_parent; mod m20221024_134454_fill_admin; mod m20221025_154422_create_session; +mod m20221027_194728_session_create_user_fk; pub struct Migrator; @@ -17,6 +18,7 @@ impl MigratorTrait for Migrator { Box::new(m20221024_124701_create_parent::Migration), Box::new(m20221024_134454_fill_admin::Migration::default()), Box::new(m20221025_154422_create_session::Migration), + Box::new(m20221027_194728_session_create_user_fk::Migration), ] } } diff --git a/migration/src/m20221024_121621_create_candidate.rs b/migration/src/m20221024_121621_create_candidate.rs index 974a93f..d11282d 100644 --- a/migration/src/m20221024_121621_create_candidate.rs +++ b/migration/src/m20221024_121621_create_candidate.rs @@ -50,7 +50,7 @@ impl MigrationTrait for Migration { /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum Candidate { +pub enum Candidate { Table, Application, Code, diff --git a/migration/src/m20221025_154422_create_session.rs b/migration/src/m20221025_154422_create_session.rs index d7931f0..9327626 100644 --- a/migration/src/m20221025_154422_create_session.rs +++ b/migration/src/m20221025_154422_create_session.rs @@ -1,11 +1,29 @@ use sea_orm_migration::prelude::*; +use crate::m20221024_121621_create_candidate::Candidate; + #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let table = Table::create() + .table(Session::Table) + .if_not_exists() + .col( + ColumnDef::new(Session::Id) + .uuid() + .not_null() + .unique_key() + .primary_key(), + ) + .col(ColumnDef::new(Session::HashedToken).string().not_null()) + .col(ColumnDef::new(Session::UserId).integer().not_null()) + .col(ColumnDef::new(Session::CreatedAt).date_time().not_null()) + .col(ColumnDef::new(Session::UpdatedAt).date_time().not_null()) + .to_owned(); + manager .create_table( Table::create() @@ -36,7 +54,7 @@ impl MigrationTrait for Migration { /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum Session { +pub enum Session { Table, Id, HashedToken, diff --git a/migration/src/m20221027_194728_session_create_user_fk.rs b/migration/src/m20221027_194728_session_create_user_fk.rs new file mode 100644 index 0000000..248fe2d --- /dev/null +++ b/migration/src/m20221027_194728_session_create_user_fk.rs @@ -0,0 +1,26 @@ +use sea_orm_migration::prelude::*; + +use crate::{m20221025_154422_create_session::Session, m20221024_121621_create_candidate::Candidate}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +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) + .to(Candidate::Table, Candidate::Application) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + .to_owned()).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_foreign_key(ForeignKey::drop() + .name("user_fk") + .table(Session::Table) + .to_owned()).await + } +} \ No newline at end of file From a58cf73acc2b03e75f25411168f7dba2513d83ca Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 10:35:46 +0200 Subject: [PATCH 02/13] chore: add sha2 dependency --- core/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/Cargo.toml b/core/Cargo.toml index ad9a9b0..156abd9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -11,6 +11,7 @@ argon2 = "0.4.1" chrono = "0.4.22" jsonwebtoken = "8.1.1" dotenv = "0.15.0" +sha2 = "0.10.6" [dependencies.sea-orm] version = "^0.10.0" From 9378dcbead6d46d1c7477bb1fdb5aa89404eeea7 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 10:36:14 +0200 Subject: [PATCH 03/13] feat: get refresh token --- api/src/lib.rs | 22 ++++++++++++++++++++-- core/src/crypto.rs | 16 ++++++++++++++++ core/src/mutation.rs | 23 +++++++++++++++++++++-- core/src/services/mod.rs | 3 ++- core/src/services/session_service.rs | 27 +++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 core/src/services/session_service.rs diff --git a/api/src/lib.rs b/api/src/lib.rs index b4d16c9..1dde6de 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -4,6 +4,7 @@ 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}; @@ -45,6 +46,17 @@ async fn create(conn: Connection<'_, Db>, post_form: Json) -> Ok(plain_text_password) } +#[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; + + Ok(refresh_token.ok().unwrap()) + +} + #[post("/login", data = "")] async fn login(conn: Connection<'_, Db>, login_form: Json) -> Result> { let db = conn.into_inner(); @@ -72,7 +84,13 @@ async fn whoami(conn: Connection<'_, Db>, token_req: Result Ok(format!("{} {}", user.name.unwrap(), user.surname.unwrap())), + Ok(user) => Ok( + format!("{} {} {}", + user.application, + user.name.unwrap_or("".to_owned()), + user.surname.unwrap_or("".to_owned()) + ) + ), Err(e) => Err(custom_err_from_service_err(e)), } } @@ -94,7 +112,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]) + .mount("/", routes![create, login, hello, whoami, refresh_token]) .register("/", catchers![]) .launch() .await diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 7047e55..73778e7 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -2,6 +2,7 @@ use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; use rand::Rng; +use sha2::{Sha256, Digest}; /// Foolproof random 8 char string @@ -28,6 +29,14 @@ pub fn random_8_char_string() -> String { s } +pub fn hash_sha256(s: String) -> String { + let mut hasher = Sha256::new(); + hasher.update(s); + let result = hasher.finalize(); + format!("{result:x}") + +} + pub fn hash_password(password_plaint_text: &str) -> Result { let password = password_plaint_text.as_bytes(); let salt = "c2VjcmV0bHl0ZXN0aW5nZXZlcnl0aGluZw"; @@ -51,3 +60,10 @@ pub fn verify_password( .verify_password(password_plaint_text.as_bytes(), &parsed_hash) .is_ok()); } + +#[test] +fn verify_password_test() { + let password = "test"; + let hash = hash_password(password).unwrap(); + assert!(verify_password(password, &hash).unwrap()); +} \ No newline at end of file diff --git a/core/src/mutation.rs b/core/src/mutation.rs index ac04626..9bc32bf 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -1,5 +1,6 @@ -use ::entity::{candidate}; -use sea_orm::*; +use chrono::Utc; +use ::entity::{candidate, session}; +use sea_orm::{*, prelude::Uuid}; use crate::crypto::hash_password; pub struct Mutation; @@ -24,4 +25,22 @@ impl Mutation { .insert(db) .await } + + + pub async fn insert_session( + db: &DbConn, + user_id: i32, + random_uuid: Uuid, + hashed_jwt: String + ) -> Result { + session::ActiveModel { + id: Set(random_uuid), + hashed_token: Set(hashed_jwt), + user_id: Set(user_id), + created_at: Set(Utc::now().naive_local()), + updated_at: Set(Utc::now().naive_local()), + } + .insert(db) + .await + } } diff --git a/core/src/services/mod.rs b/core/src/services/mod.rs index 1992ce2..1bafcbb 100644 --- a/core/src/services/mod.rs +++ b/core/src/services/mod.rs @@ -1 +1,2 @@ -pub mod candidate_service; \ No newline at end of file +pub mod candidate_service; +pub mod session_service; \ No newline at end of file diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs new file mode 100644 index 0000000..dade2b4 --- /dev/null +++ b/core/src/services/session_service.rs @@ -0,0 +1,27 @@ +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 From 69b5a4fabf276e73b21a5c31d401fea01ba77c82 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 12:11:23 +0200 Subject: [PATCH 04/13] feat: find session by uuid --- core/src/query.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/query.rs b/core/src/query.rs index 72aa09f..bcc805b 100644 --- a/core/src/query.rs +++ b/core/src/query.rs @@ -1,5 +1,7 @@ use ::entity::{candidate, candidate::Entity as Candidate}; +use ::entity::{session, session::Entity as Session}; use sea_orm::*; +use sea_orm::prelude::Uuid; pub struct Query; @@ -7,4 +9,8 @@ impl Query { pub async fn find_candidate_by_id(db: &DbConn, id: i32) -> Result, DbErr> { Candidate::find_by_id(id).one(db).await } + + pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result, DbErr> { + Session::find_by_id(uuid).one(db).await + } } From 82f9098ed5c52a5cf40bc8aadf5f81a6ce841af8 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 18:48:08 +0200 Subject: [PATCH 05/13] feat: candidate refresh token auth --- api/src/guard/candidate_refresh_token.rs | 27 ++++++++++++++++++++++++ api/src/guard/mod.rs | 3 ++- api/src/lib.rs | 15 ++++++++++++- core/src/error.rs | 5 ++++- core/src/services/candidate_service.rs | 24 +++++++++++++++++++-- 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 api/src/guard/candidate_refresh_token.rs diff --git a/api/src/guard/candidate_refresh_token.rs b/api/src/guard/candidate_refresh_token.rs new file mode 100644 index 0000000..dee824d --- /dev/null +++ b/api/src/guard/candidate_refresh_token.rs @@ -0,0 +1,27 @@ +use portfolio_core::sea_orm::prelude::Uuid; +use rocket::http::Status; +use rocket::outcome::Outcome; +use rocket::request::{FromRequest, Request}; + + +pub struct UUIDCookie(Uuid); + +impl UUIDCookie { + pub fn value(self) -> Uuid { + self.0 + } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for UUIDCookie { + type Error = Status; + async fn from_request(req: &'r Request<'_>) -> Outcome { + let session_id = req.cookies().get("id").unwrap().name_value().1; + println!("session_id: {}", session_id); + + match Uuid::parse_str(&session_id) { + Ok(uuid) => Outcome::Success(UUIDCookie(uuid)), + Err(_) => return Outcome::Failure((Status::BadRequest, Status::BadRequest)), + } + } +} diff --git a/api/src/guard/mod.rs b/api/src/guard/mod.rs index f2318e8..6503da6 100644 --- a/api/src/guard/mod.rs +++ b/api/src/guard/mod.rs @@ -1 +1,2 @@ -pub mod candidate_jwt; \ No newline at end of file +pub mod candidate_jwt; +pub mod candidate_refresh_token; \ No newline at end of file diff --git a/api/src/lib.rs b/api/src/lib.rs index 1dde6de..75fe90d 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -28,6 +28,8 @@ pub use entity::candidate::Entity as Candidate; use portfolio_core::crypto::random_8_char_string; +use crate::guard::candidate_refresh_token::UUIDCookie; + fn custom_err_from_service_err(service_err: ServiceError) -> Custom { Custom(Status::from_code(service_err.0.code).unwrap_or_default(), service_err.1.to_string()) } @@ -54,7 +56,18 @@ async fn refresh_token(conn: Connection<'_, Db>, token_req: Result, uuid_cookie: Result) -> Result> { + let db = conn.into_inner(); + let user = CandidateService::auth_user_session(db, uuid_cookie.ok().unwrap().value()).await; + + + match user { + Ok(user) => Ok(user.application.to_string()), + Err(err) => Err(custom_err_from_service_err(err)) + } } #[post("/login", data = "")] @@ -112,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]) + .mount("/", routes![create, login, hello, whoami, refresh_token, validate]) .register("/", catchers![]) .launch() .await diff --git a/core/src/error.rs b/core/src/error.rs index b2093a7..e70cba8 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -13,6 +13,9 @@ pub const USER_NOT_FOUND_ERROR: ServiceError = ServiceError(Status { code: 404 } pub const DB_ERROR: ServiceError = ServiceError(Status { code: 500 }, "Database error"); -pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow +pub const USER_NOT_FOUND_BY_JWT_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted + "User not found, please contact technical support"); // Shouldn't ever happen + +pub const USER_NOT_FOUND_BY_SESSION_ID: ServiceError = ServiceError(Status { code: 500 }, // User got somehow deleted "User not found, please contact technical support"); // Shouldn't ever happen pub struct ServiceError<'a>(pub Status, pub &'a str); \ No newline at end of file diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 3ba2e8b..7e28180 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -1,7 +1,7 @@ use entity::candidate; -use sea_orm::DatabaseConnection; +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}}; +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}}; pub struct CandidateService; @@ -40,4 +40,24 @@ impl CandidateService { Ok(candidate) } + + pub async fn auth_user_session(db: &DatabaseConnection, uuid: Uuid) -> Result { + let session = match Query::find_session_by_uuid(db, uuid).await { + Ok(session) => match session { + Some(session) => session, + None => return Err(USER_NOT_FOUND_BY_SESSION_ID) + }, + Err(_) => {return Err(DB_ERROR)} + }; + + let candidate = match session.find_related(candidate::Entity).one(db).await { + Ok(candidate) => match candidate { + Some(candidate) => candidate, + None => return Err(USER_NOT_FOUND_BY_JWT_ID) + }, + Err(_) => {return Err(DB_ERROR)} + }; + + Ok(candidate) + } } From fb27fcff60955d7178e156641562d81d3b7321c3 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 19:33:30 +0200 Subject: [PATCH 06/13] 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 From fa74688ca35227ccce37d118f791fb5de1ae3793 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Fri, 28 Oct 2022 23:26:25 +0200 Subject: [PATCH 07/13] feat: session tests --- core/src/services/candidate_service.rs | 50 ++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index e505106..75e7ef6 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -7,7 +7,7 @@ use crate::{crypto::{self, hash_sha256}, Query, token::{generate_candidate_token pub struct CandidateService; impl CandidateService { - #[deprecated(note = "Use login instead")] + #[deprecated(note = "Use session 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 { @@ -110,19 +110,23 @@ impl CandidateService { #[cfg(test)] mod tests { use entity::candidate; - use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait}; + use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid}; use serde_json::json; use crate::{crypto, Mutation, services::candidate_service::CandidateService, token}; #[cfg(test)] async fn get_memory_sqlite_connection() -> DbConn { + use entity::session; + let base_url = "sqlite::memory:"; let db: DbConn = Database::connect(base_url).await.unwrap(); let schema = Schema::new(DbBackend::Sqlite); let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity); + let stmt2: TableCreateStatement = schema.create_table_from_entity(session::Entity); db.execute(db.get_database_backend().build(&stmt)).await.unwrap(); + db.execute(db.get_database_backend().build(&stmt2)).await.unwrap(); db } @@ -158,4 +162,46 @@ mod tests { assert_eq!(claims.application_id, candidate.application); } + + #[tokio::test] + async fn test_candidate_session_correct_password() { + let db = &get_memory_sqlite_connection().await; + + let form = serde_json::from_value(json!({ + "application": 5555555, + })).unwrap(); + + Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap(); + + // correct password + let session = CandidateService::get_session( + db, + 5555555, + "Tajny_kod".to_string() + ) + .await.ok().unwrap(); + // println!("{}", session.err().unwrap().1); + + assert!( + CandidateService::auth_user_session(db, Uuid::parse_str(&session).unwrap()) + .await + .is_ok() + ); + } + + #[tokio::test] + async fn test_candidate_session_incorrect_password() { + let db = &get_memory_sqlite_connection().await; + + let form = serde_json::from_value(json!({ + "application": 5555555, + })).unwrap(); + + let candidate_form = Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap(); + + // incorrect password + assert!( + CandidateService::get_session(db, candidate_form.application, "Spatny_kod".to_string()).await.is_err() + ); + } } \ No newline at end of file From 59e028b0ed37505fee028b1d6e505b2b52878b3d Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 11:32:14 +0200 Subject: [PATCH 08/13] refactor: rename get_session to new_session --- api/src/lib.rs | 2 +- core/src/services/candidate_service.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 13f6e7f..3f8745e 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -74,7 +74,7 @@ async fn login(conn: Connection<'_, Db>, login_form: Json) -> Resu let db = conn.into_inner(); println!("{} {}", login_form.application_id, login_form.password); - let session_token = CandidateService::get_session(db, + let session_token = CandidateService::new_session(db, login_form.application_id, login_form.password.to_string() ).await; diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 75e7ef6..0f64e96 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -29,7 +29,7 @@ impl CandidateService { Ok(jwt) } - pub async fn get_session(db: &DatabaseConnection, user_id: i32, password: String) -> Result { + pub async fn new_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, @@ -174,7 +174,7 @@ mod tests { Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap(); // correct password - let session = CandidateService::get_session( + let session = CandidateService::new_session( db, 5555555, "Tajny_kod".to_string() @@ -201,7 +201,7 @@ mod tests { // incorrect password assert!( - CandidateService::get_session(db, candidate_form.application, "Spatny_kod".to_string()).await.is_err() + CandidateService::new_session(db, candidate_form.application, "Spatny_kod".to_string()).await.is_err() ); } } \ No newline at end of file From 5e36b7b3c800889987c076746d648990d07c7490 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 12:13:17 +0200 Subject: [PATCH 09/13] refactor: session entity structure --- core/src/mutation.rs | 7 +++---- core/src/services/candidate_service.rs | 4 +--- entity/src/session.rs | 5 +++-- migration/src/m20221025_154422_create_session.rs | 11 +++++------ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/core/src/mutation.rs b/core/src/mutation.rs index e1d6bd3..9b8c071 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -1,4 +1,4 @@ -use chrono::Utc; +use chrono::{Utc, Duration}; use ::entity::{candidate, session}; use sea_orm::{*, prelude::Uuid}; use crate::crypto::hash_password; @@ -31,14 +31,13 @@ impl Mutation { db: &DbConn, user_id: i32, random_uuid: Uuid, - hashed_jwt: String ) -> Result { session::ActiveModel { id: Set(random_uuid), - hashed_token: Set(hashed_jwt), user_id: Set(user_id), + ip_address: Set("127.0.0.1".to_string()), created_at: Set(Utc::now().naive_local()), - updated_at: Set(Utc::now().naive_local()), + expires_at: Set(Utc::now().naive_local().checked_add_signed(Duration::days(1)).unwrap()), } .insert(db) .await diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 0f64e96..dc0771f 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -53,9 +53,7 @@ impl CandidateService { // 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 { + let session = match Mutation::insert_session(db, user_id, random_uuid).await { Ok(session) => session, Err(_) => return Err(DB_ERROR) }; diff --git a/entity/src/session.rs b/entity/src/session.rs index 4f0be16..f7322c4 100644 --- a/entity/src/session.rs +++ b/entity/src/session.rs @@ -7,10 +7,11 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, - pub hashed_token: String, pub user_id: i32, + #[sea_orm(column_type = "Custom(\"inet\".to_owned())")] + pub ip_address: String, pub created_at: DateTime, - pub updated_at: DateTime, + pub expires_at: DateTime, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/migration/src/m20221025_154422_create_session.rs b/migration/src/m20221025_154422_create_session.rs index 9327626..a6003dd 100644 --- a/migration/src/m20221025_154422_create_session.rs +++ b/migration/src/m20221025_154422_create_session.rs @@ -18,10 +18,9 @@ impl MigrationTrait for Migration { .unique_key() .primary_key(), ) - .col(ColumnDef::new(Session::HashedToken).string().not_null()) .col(ColumnDef::new(Session::UserId).integer().not_null()) .col(ColumnDef::new(Session::CreatedAt).date_time().not_null()) - .col(ColumnDef::new(Session::UpdatedAt).date_time().not_null()) + .col(ColumnDef::new(Session::ExpiresAt).date_time().not_null()) .to_owned(); manager @@ -36,10 +35,10 @@ impl MigrationTrait for Migration { .unique_key() .primary_key(), ) - .col(ColumnDef::new(Session::HashedToken).string().not_null()) .col(ColumnDef::new(Session::UserId).integer().not_null()) + .col(ColumnDef::new(Session::IpAddress).inet().not_null()) .col(ColumnDef::new(Session::CreatedAt).date_time().not_null()) - .col(ColumnDef::new(Session::UpdatedAt).date_time().not_null()) + .col(ColumnDef::new(Session::ExpiresAt).date_time().not_null()) .to_owned(), ) .await @@ -57,8 +56,8 @@ impl MigrationTrait for Migration { pub enum Session { Table, Id, - HashedToken, UserId, + IpAddress, CreatedAt, - UpdatedAt + ExpiresAt, } From 98110dcf9638987828288d58ffbec7b98770fdcc Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 12:24:45 +0200 Subject: [PATCH 10/13] refactor: change ip address inet type to string --- core/src/services/candidate_service.rs | 2 +- entity/src/session.rs | 1 - .../src/m20221025_154422_create_session.rs | 19 +------------------ 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index dc0771f..3732c09 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -2,7 +2,7 @@ use chrono::Duration; use entity::candidate; use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait}; -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}; +use crate::{crypto::{self}, 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; diff --git a/entity/src/session.rs b/entity/src/session.rs index f7322c4..3fdde8c 100644 --- a/entity/src/session.rs +++ b/entity/src/session.rs @@ -8,7 +8,6 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, pub user_id: i32, - #[sea_orm(column_type = "Custom(\"inet\".to_owned())")] pub ip_address: String, pub created_at: DateTime, pub expires_at: DateTime, diff --git a/migration/src/m20221025_154422_create_session.rs b/migration/src/m20221025_154422_create_session.rs index a6003dd..9c084de 100644 --- a/migration/src/m20221025_154422_create_session.rs +++ b/migration/src/m20221025_154422_create_session.rs @@ -1,28 +1,11 @@ use sea_orm_migration::prelude::*; -use crate::m20221024_121621_create_candidate::Candidate; - #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(Session::Table) - .if_not_exists() - .col( - ColumnDef::new(Session::Id) - .uuid() - .not_null() - .unique_key() - .primary_key(), - ) - .col(ColumnDef::new(Session::UserId).integer().not_null()) - .col(ColumnDef::new(Session::CreatedAt).date_time().not_null()) - .col(ColumnDef::new(Session::ExpiresAt).date_time().not_null()) - .to_owned(); - manager .create_table( Table::create() @@ -36,7 +19,7 @@ impl MigrationTrait for Migration { .primary_key(), ) .col(ColumnDef::new(Session::UserId).integer().not_null()) - .col(ColumnDef::new(Session::IpAddress).inet().not_null()) + .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()) .to_owned(), From 196632a094e74e3aa4d1362d270271d54bf61918 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 12:27:15 +0200 Subject: [PATCH 11/13] refactor: delete candidate jwt --- api/src/guard/candidate_jwt.rs | 33 --------------------------------- api/src/lib.rs | 33 ++------------------------------- 2 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 api/src/guard/candidate_jwt.rs diff --git a/api/src/guard/candidate_jwt.rs b/api/src/guard/candidate_jwt.rs deleted file mode 100644 index 25d9486..0000000 --- a/api/src/guard/candidate_jwt.rs +++ /dev/null @@ -1,33 +0,0 @@ -use rocket::http::Status; -use rocket::outcome::Outcome; -use rocket::request::{FromRequest, Request}; - -use portfolio_core::token::candidate_token::CandidateToken; -use portfolio_core::token::decode_candidate_token; - -pub struct TokenRequest(CandidateToken); - -impl TokenRequest { - pub fn to_token(self) -> CandidateToken { - self.0 - } -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for TokenRequest { - type Error = Status; - async fn from_request(req: &'r Request<'_>) -> Outcome { - if let Some(auth) = req.headers().get_one("Authorization") { - let auth_string = auth.to_string(); - if auth_string.starts_with("Bearer") { - let token = auth_string[6..auth_string.len()].trim(); - let token_data = decode_candidate_token(token.to_string()); - - if token_data.is_ok() { - return Outcome::Success(TokenRequest(token_data.ok().unwrap().claims)); - } - } - } - return Outcome::Failure((Status::Unauthorized, Status::Unauthorized)); - } -} diff --git a/api/src/lib.rs b/api/src/lib.rs index 3f8745e..98821d4 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,7 +1,6 @@ #[macro_use] extern crate rocket; -use guard::candidate_jwt::TokenRequest; use portfolio_core::error::ServiceError; use portfolio_core::services::candidate_service::CandidateService; use requests::LoginRequest; @@ -47,17 +46,7 @@ async fn create(conn: Connection<'_, Db>, post_form: Json) -> Ok(plain_text_password) } -/* #[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::login_user(db, jwt.application_id).await; - - Ok(refresh_token.ok().unwrap()) -} */ - -#[get("/validate_refresh")] +#[get("/whoami")] async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result) -> Result> { let db = conn.into_inner(); let user = CandidateService::auth_user_session(db, uuid_cookie.ok().unwrap().value()).await; @@ -90,24 +79,6 @@ async fn login(conn: Connection<'_, Db>, login_form: Json) -> Resu } } -#[get("/whoami")] -async fn whoami(conn: Connection<'_, Db>, token_req: Result) -> Result> { - let db = conn.into_inner(); - let token = token_req.ok().unwrap().to_token(); - let user = CandidateService::authenticate_candidate(db, token).await; - - match user { - Ok(user) => Ok( - format!("{} {} {}", - user.application, - user.name.unwrap_or("".to_owned()), - user.surname.unwrap_or("".to_owned()) - ) - ), - Err(e) => Err(custom_err_from_service_err(e)), - } -} - #[get("/hello")] async fn hello() -> &'static str { "Hello, world!" @@ -125,7 +96,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, validate]) + .mount("/", routes![create, login, hello, validate]) .register("/", catchers![]) .launch() .await From 030cd53350dd4a53d3a0f7e6cb3a99d32156e6cb Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 12:27:49 +0200 Subject: [PATCH 12/13] fix: remove candidate jwt from mod.rs --- api/src/guard/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/guard/mod.rs b/api/src/guard/mod.rs index 6503da6..bdf6937 100644 --- a/api/src/guard/mod.rs +++ b/api/src/guard/mod.rs @@ -1,2 +1 @@ -pub mod candidate_jwt; pub mod candidate_refresh_token; \ No newline at end of file From 7fdb0de11be38ac633fbb74cd83c268f88494797 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 29 Oct 2022 12:31:39 +0200 Subject: [PATCH 13/13] refactor: remove jwt completely --- core/src/lib.rs | 1 - core/src/services/candidate_service.rs | 56 +-------------- core/src/token/admin_token.rs | 9 --- core/src/token/candidate_token.rs | 26 ------- core/src/token/mod.rs | 98 -------------------------- core/src/token/secret.key | 1 - 6 files changed, 2 insertions(+), 189 deletions(-) delete mode 100644 core/src/token/admin_token.rs delete mode 100644 core/src/token/candidate_token.rs delete mode 100644 core/src/token/mod.rs delete mode 100644 core/src/token/secret.key diff --git a/core/src/lib.rs b/core/src/lib.rs index 71b5ace..47620eb 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,7 +1,6 @@ mod mutation; mod query; pub mod crypto; -pub mod token; pub mod services; pub mod error; diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 3732c09..e668107 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -2,33 +2,11 @@ use chrono::Duration; use entity::candidate; use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait}; -use crate::{crypto::{self}, 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}; +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}; pub struct CandidateService; impl CandidateService { - #[deprecated(note = "Use session 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 { - Some(candidate) => candidate, - None => return Err(USER_NOT_FOUND_ERROR) - }, - Err(_) => {return Err(DB_ERROR)} - }; - - - let valid = crypto::verify_password(&password,&candidate.code ) - .expect("Invalid password"); - - if !valid { - return Err(INVALID_CREDENTIALS_ERROR) - } - - let jwt = generate_candidate_token(candidate); // TODO better error handling - Ok(jwt) - } - pub async fn new_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 { @@ -61,18 +39,6 @@ impl CandidateService { Ok(session.id.to_string()) } - pub async fn authenticate_candidate(db: &DatabaseConnection, token: CandidateToken) -> Result { - let candidate = match Query::find_candidate_by_id(db, token.application_id).await { - Ok(candidate) => match candidate { - Some(candidate) => candidate, - None => return Err(USER_NOT_FOUND_BY_JWT_ID) - }, - Err(_) => {return Err(DB_ERROR)} - }; - - Ok(candidate) - } - pub async fn auth_user_session(db: &DatabaseConnection, uuid: Uuid) -> Result { let session = match Query::find_session_by_uuid(db, uuid).await { Ok(session) => match session { @@ -111,7 +77,7 @@ mod tests { use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid}; use serde_json::json; - use crate::{crypto, Mutation, services::candidate_service::CandidateService, token}; + use crate::{crypto, Mutation, services::candidate_service::CandidateService}; #[cfg(test)] async fn get_memory_sqlite_connection() -> DbConn { @@ -142,24 +108,6 @@ mod tests { assert_ne!(candidate.code, "Tajny_kod".to_string()); assert!(crypto::verify_password("Tajny_kod", &*candidate.code).ok().unwrap()); } - - - #[tokio::test] - async fn test_candidate_jwt() { - let db = &get_memory_sqlite_connection().await; - - let form = serde_json::from_value(json!({ - "application": 5555555, - })).unwrap(); - - let candidate = Mutation::create_candidate(&db, form, &"Tajny_kod".to_string()).await.unwrap(); - - let jwt = CandidateService::login(db, 5555555, "Tajny_kod".to_string()).await.ok().unwrap(); - - let claims = token::decode_candidate_token(jwt).ok().unwrap().claims; - - assert_eq!(claims.application_id, candidate.application); - } #[tokio::test] async fn test_candidate_session_correct_password() { diff --git a/core/src/token/admin_token.rs b/core/src/token/admin_token.rs deleted file mode 100644 index 73b7a5f..0000000 --- a/core/src/token/admin_token.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct AdminToken { - // issued at - pub iat: i64, - // expiration - pub exp: i64, -} \ No newline at end of file diff --git a/core/src/token/candidate_token.rs b/core/src/token/candidate_token.rs deleted file mode 100644 index 7014965..0000000 --- a/core/src/token/candidate_token.rs +++ /dev/null @@ -1,26 +0,0 @@ -use chrono::Utc; -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct CandidateToken { - // issued at - pub iat: i64, - // expiration - pub exp: i64, - pub application_id: i32, - pub name: String, - pub surname: String, -} - -impl CandidateToken { - pub fn generate(application_id: i32, name: String, surname: String) -> Self { - let now = Utc::now().timestamp(); - CandidateToken { - iat: now, - exp: now + 60 * 60, // 1 hour for now - application_id, - name, - surname, - } - } -} \ No newline at end of file diff --git a/core/src/token/mod.rs b/core/src/token/mod.rs deleted file mode 100644 index 370f28f..0000000 --- a/core/src/token/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub mod admin_token; -pub mod candidate_token; - -use chrono::Utc; - -use entity::{admin, candidate}; -use jsonwebtoken::errors::Result; -use jsonwebtoken::TokenData; -use jsonwebtoken::{DecodingKey, EncodingKey}; -use jsonwebtoken::{Header, Validation}; - -use admin_token::AdminToken; -use candidate_token::CandidateToken; -use serde::Deserialize; - -const ONE_WEEK: i64 = 60 * 60 * 24 * 7; - -pub fn generate_candidate_token(candidate: candidate::Model) -> String { - let now = Utc::now().timestamp(); - let payload = CandidateToken { - iat: now, - exp: now + ONE_WEEK, - application_id: candidate.application, - name: candidate.name.unwrap_or_else(|| "".into()), - surname: candidate.surname.unwrap_or_else(|| "".into()), - }; - - jsonwebtoken::encode( - &Header::default(), - &payload, - &EncodingKey::from_secret(include_bytes!("secret.key")), - ) - .unwrap() -} - -pub fn generate_admin_token(_admin: admin::Model) -> String { - let now = Utc::now().timestamp(); - let payload = AdminToken { - iat: now, - exp: now + ONE_WEEK, - }; - - jsonwebtoken::encode( - &Header::default(), - &payload, - &EncodingKey::from_secret(include_bytes!("secret.key")), - ) - .unwrap() -} - -pub fn decode_token Deserialize<'a>>(token: String) -> Result> { - jsonwebtoken::decode::( - &token, - &DecodingKey::from_secret(include_bytes!("secret.key")), - &Validation::default(), - ) -} - -pub fn decode_candidate_token(token: String) -> Result> { - decode_token(token) -} - -pub fn decode_admin_token(token: String) -> Result> { - decode_token(token) -} - - -#[test] -fn test_encode_decode_token() { - let candidate_model = candidate::Model { - application: 101204, - code: "random_code".to_string(), - birth_surname: None, - birthplace: None, - birthdate: None, - address: None, - telephone: None, - citizenship: None, - sex: None, - study: None, - personal_identification_number: None, - personal_identification_number_hash: None, - public_key: "None".to_owned(), - private_key: "None".to_owned(), - created_at: Utc::now().naive_local(), - updated_at: Utc::now().naive_local(), - name: Some("Uplnej".to_string()), - surname: Some("Magor".to_string()), - email: Some("email.uchazece@centrum.cz".to_string()), - }; - - let jwt = generate_candidate_token(candidate_model.clone()); - - let decoded = decode_candidate_token(jwt).unwrap(); - let token_claims = decoded.claims; - assert_eq!(candidate_model.name.unwrap(), token_claims.name); - assert_eq!(candidate_model.surname.unwrap(), token_claims.surname); -} \ No newline at end of file diff --git a/core/src/token/secret.key b/core/src/token/secret.key deleted file mode 100644 index 3602361..0000000 --- a/core/src/token/secret.key +++ /dev/null @@ -1 +0,0 @@ -temp \ No newline at end of file