mirror of
https://github.com/danbulant/Portfolio
synced 2026-07-06 11:30:44 +00:00
feat: session authentication instead of jwt
This commit is contained in:
parent
82f9098ed5
commit
fb27fcff60
7 changed files with 81 additions and 43 deletions
|
|
@ -4,7 +4,6 @@ extern crate rocket;
|
||||||
use guard::candidate_jwt::TokenRequest;
|
use guard::candidate_jwt::TokenRequest;
|
||||||
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 portfolio_core::services::session_service::SessionService;
|
|
||||||
use requests::LoginRequest;
|
use requests::LoginRequest;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
|
|
@ -48,15 +47,15 @@ async fn create(conn: Connection<'_, Db>, post_form: Json<candidate::Model>) ->
|
||||||
Ok(plain_text_password)
|
Ok(plain_text_password)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/refresh")]
|
/* #[get("/refresh")]
|
||||||
async fn refresh_token(conn: Connection<'_, Db>, token_req: Result<TokenRequest, Status>) -> Result<String, Custom<String>> {
|
async fn refresh_token(conn: Connection<'_, Db>, token_req: Result<TokenRequest, Status>) -> Result<String, Custom<String>> {
|
||||||
let db = conn.into_inner();
|
let db = conn.into_inner();
|
||||||
let jwt = token_req.ok().unwrap().to_token();
|
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())
|
Ok(refresh_token.ok().unwrap())
|
||||||
}
|
} */
|
||||||
|
|
||||||
#[get("/validate_refresh")]
|
#[get("/validate_refresh")]
|
||||||
async fn validate(conn: Connection<'_, Db>, uuid_cookie: Result<UUIDCookie, Status>) -> Result<String, Custom<String>> {
|
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();
|
let db = conn.into_inner();
|
||||||
println!("{} {}", login_form.application_id, login_form.password);
|
println!("{} {}", login_form.application_id, login_form.password);
|
||||||
|
|
||||||
let jwt = CandidateService::login(db,
|
let session_token = CandidateService::get_session(db,
|
||||||
login_form.application_id,
|
login_form.application_id,
|
||||||
login_form.password.to_owned()).await;
|
login_form.password.to_string()
|
||||||
|
).await;
|
||||||
|
|
||||||
if jwt.is_ok() {
|
if session_token.is_ok() {
|
||||||
return Ok(
|
return Ok(
|
||||||
jwt.ok().unwrap()
|
session_token.ok().unwrap()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
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(Db::init())
|
||||||
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
.attach(AdHoc::try_on_ignite("Migrations", run_migrations))
|
||||||
//.mount("/", FileServer::from(relative!("/static")))
|
//.mount("/", FileServer::from(relative!("/static")))
|
||||||
.mount("/", routes![create, login, hello, whoami, refresh_token, validate])
|
.mount("/", routes![create, login, hello, whoami, validate])
|
||||||
.register("/", catchers![])
|
.register("/", catchers![])
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ pub struct Status {
|
||||||
|
|
||||||
pub const INVALID_CREDENTIALS_ERROR: ServiceError = ServiceError(Status { code: 401 },
|
pub const INVALID_CREDENTIALS_ERROR: ServiceError = ServiceError(Status { code: 401 },
|
||||||
"Invalid credentials");
|
"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 },
|
pub const JWT_ERROR: ServiceError = ServiceError(Status { code: 500 },
|
||||||
"Error while encoding JWT");
|
"Error while encoding JWT");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,16 @@ impl Mutation {
|
||||||
.insert(db)
|
.insert(db)
|
||||||
.await
|
.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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,12 @@ impl Query {
|
||||||
pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result<Option<session::Model>, DbErr> {
|
pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result<Option<session::Model>, DbErr> {
|
||||||
Session::find_by_id(uuid).one(db).await
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
|
use chrono::Duration;
|
||||||
use entity::candidate;
|
use entity::candidate;
|
||||||
use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait};
|
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;
|
pub struct CandidateService;
|
||||||
|
|
||||||
impl CandidateService {
|
impl CandidateService {
|
||||||
|
#[deprecated(note = "Use login instead")]
|
||||||
pub async fn login(db: &DatabaseConnection, id: i32, password: String) -> Result<String, ServiceError> {
|
pub async fn login(db: &DatabaseConnection, id: i32, password: String) -> Result<String, ServiceError> {
|
||||||
let candidate = match Query::find_candidate_by_id(db, id).await {
|
let candidate = match Query::find_candidate_by_id(db, id).await {
|
||||||
Ok(candidate) => match candidate {
|
Ok(candidate) => match candidate {
|
||||||
|
|
@ -26,7 +27,40 @@ impl CandidateService {
|
||||||
|
|
||||||
let jwt = generate_candidate_token(candidate); // TODO better error handling
|
let jwt = generate_candidate_token(candidate); // TODO better error handling
|
||||||
Ok(jwt)
|
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> {
|
pub async fn authenticate_candidate(db: &DatabaseConnection, token: CandidateToken) -> Result<candidate::Model, ServiceError> {
|
||||||
|
|
@ -50,6 +84,15 @@ 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();
|
||||||
|
// 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 {
|
let candidate = match session.find_related(candidate::Entity).one(db).await {
|
||||||
Ok(candidate) => match candidate {
|
Ok(candidate) => match candidate {
|
||||||
Some(candidate) => candidate,
|
Some(candidate) => candidate,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
pub mod candidate_service;
|
pub mod candidate_service;
|
||||||
pub mod session_service;
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue