feat: session authentication instead of jwt

This commit is contained in:
Sebastian Pravda 2022-10-28 19:33:30 +02:00
parent 82f9098ed5
commit fb27fcff60
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
7 changed files with 81 additions and 43 deletions

View file

@ -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<candidate::Model>) ->
Ok(plain_text_password)
}
#[get("/refresh")]
/* #[get("/refresh")]
async fn refresh_token(conn: Connection<'_, Db>, token_req: Result<TokenRequest, Status>) -> Result<String, Custom<String>> {
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<UUIDCookie, Status>) -> Result<String, Custom<String>> {
@ -75,17 +74,18 @@ async fn login(conn: Connection<'_, Db>, login_form: Json<LoginRequest>) -> 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

View file

@ -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");

View file

@ -43,4 +43,16 @@ impl Mutation {
.insert(db)
.await
}
pub async fn delete_session(
db: &DbConn,
session_id: Uuid
) -> Result<DeleteResult, DbErr> {
session::ActiveModel {
id: Set(session_id),
..Default::default()
}
.delete(db)
.await
}
}

View file

@ -13,4 +13,12 @@ impl Query {
pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result<Option<session::Model>, 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<Option<session::Model>, DbErr> {
Session::find()
.filter(session::Column::UserId.eq(user_id))
.one(db)
.await
}
}

View file

@ -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<String, ServiceError> {
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<String, ServiceError> {
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<candidate::Model, ServiceError> {
@ -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,

View file

@ -1,2 +1 @@
pub mod candidate_service;
pub mod session_service;
pub mod candidate_service;

View file

@ -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<String, ServiceError> {
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())
}
}