Implement password authentication
This commit is contained in:
parent
5ed74260a8
commit
652541cd59
@ -1,7 +1,8 @@
|
|||||||
use crate::constants::StaticConstraints;
|
use crate::constants::StaticConstraints;
|
||||||
use crate::controllers::HttpResult;
|
use crate::controllers::HttpResult;
|
||||||
|
use crate::models::{User, UserID};
|
||||||
use crate::services::rate_limiter_service::RatedAction;
|
use crate::services::rate_limiter_service::RatedAction;
|
||||||
use crate::services::{rate_limiter_service, users_service};
|
use crate::services::{login_token_service, rate_limiter_service, users_service};
|
||||||
use actix_remote_ip::RemoteIP;
|
use actix_remote_ip::RemoteIP;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
@ -173,3 +174,56 @@ pub async fn reset_password(remote_ip: RemoteIP, req: web::Json<ResetPasswordBod
|
|||||||
|
|
||||||
Ok(HttpResponse::Accepted().finish())
|
Ok(HttpResponse::Accepted().finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct PasswordLoginQuery {
|
||||||
|
mail: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle login with password
|
||||||
|
pub async fn password_login(remote_ip: RemoteIP, req: web::Json<PasswordLoginQuery>) -> HttpResult {
|
||||||
|
// Rate limiting
|
||||||
|
if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::FailedPasswordLogin)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
return Ok(HttpResponse::TooManyRequests().finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user account
|
||||||
|
let user = match users_service::get_by_mail(&req.mail).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Auth failed: could not find account by mail! {}", e);
|
||||||
|
rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin)
|
||||||
|
.await?;
|
||||||
|
return Ok(HttpResponse::Unauthorized().json("Identifiants incorrects"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !user.check_password(&req.password) {
|
||||||
|
log::error!("Auth failed: invalid password for mail {}", user.email);
|
||||||
|
rate_limiter_service::record_action(remote_ip.0, RatedAction::FailedPasswordLogin).await?;
|
||||||
|
return Ok(HttpResponse::Unauthorized().json("Identifiants incorrects"));
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_login(&user).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct LoginResponse {
|
||||||
|
user_id: UserID,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn finish_login(user: &User) -> HttpResult {
|
||||||
|
if !user.active {
|
||||||
|
log::error!("Auth failed: account for mail {} is disabled!", user.email);
|
||||||
|
return Ok(HttpResponse::ExpectationFailed().json("Ce compte est désactivé !"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(LoginResponse {
|
||||||
|
user_id: user.id(),
|
||||||
|
token: login_token_service::gen_new_token(user).await?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@ -39,6 +39,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/auth/reset_password",
|
"/auth/reset_password",
|
||||||
web::post().to(auth_controller::reset_password),
|
web::post().to(auth_controller::reset_password),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/auth/password_login",
|
||||||
|
web::post().to(auth_controller::password_login),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.bind(AppConfig::get().listen_address.as_str())?
|
.bind(AppConfig::get().listen_address.as_str())?
|
||||||
.run()
|
.run()
|
||||||
|
@ -23,6 +23,18 @@ impl User {
|
|||||||
pub fn id(&self) -> UserID {
|
pub fn id(&self) -> UserID {
|
||||||
UserID(self.id)
|
UserID(self.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_password(&self, password: &str) -> bool {
|
||||||
|
self.password
|
||||||
|
.as_deref()
|
||||||
|
.map(|hash| {
|
||||||
|
bcrypt::verify(password, hash).unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to validate password! {}", e);
|
||||||
|
false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
|
58
geneit_backend/src/services/login_token_service.rs
Normal file
58
geneit_backend/src/services/login_token_service.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//! # User tokens management
|
||||||
|
|
||||||
|
use crate::connections::redis_connection;
|
||||||
|
use crate::models::{User, UserID};
|
||||||
|
use crate::utils::string_utils;
|
||||||
|
use crate::utils::time_utils::time;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
struct LoginToken {
|
||||||
|
expire: u64,
|
||||||
|
hb: u64,
|
||||||
|
user_id: UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginToken {
|
||||||
|
pub fn new(user: &User) -> (String, Self) {
|
||||||
|
let key = format!("tok-{}-{}", user.id().0, string_utils::rand_str(40));
|
||||||
|
|
||||||
|
(
|
||||||
|
key,
|
||||||
|
Self {
|
||||||
|
expire: time() + 3600 * 24,
|
||||||
|
hb: time(),
|
||||||
|
user_id: user.id(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
self.expire > time() && self.hb + 3600 > time()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_hb(&self) -> Option<Self> {
|
||||||
|
if self.hb + 60 * 5 < time() {
|
||||||
|
let mut new = self.clone();
|
||||||
|
new.hb = time();
|
||||||
|
Some(new)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lifetime(&self) -> Duration {
|
||||||
|
Duration::from_secs(if self.expire <= time() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.expire - time()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a new login token
|
||||||
|
pub async fn gen_new_token(user: &User) -> anyhow::Result<String> {
|
||||||
|
let (key, token) = LoginToken::new(user);
|
||||||
|
redis_connection::set_value(&key, &token, token.lifetime()).await?;
|
||||||
|
Ok(key)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
//! # Backend services
|
//! # Backend services
|
||||||
|
|
||||||
|
pub mod login_token_service;
|
||||||
pub mod mail_service;
|
pub mod mail_service;
|
||||||
pub mod rate_limiter_service;
|
pub mod rate_limiter_service;
|
||||||
pub mod users_service;
|
pub mod users_service;
|
||||||
|
@ -8,6 +8,7 @@ pub enum RatedAction {
|
|||||||
CreateAccount,
|
CreateAccount,
|
||||||
CheckResetPasswordTokenFailed,
|
CheckResetPasswordTokenFailed,
|
||||||
RequestNewPasswordResetLink,
|
RequestNewPasswordResetLink,
|
||||||
|
FailedPasswordLogin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RatedAction {
|
impl RatedAction {
|
||||||
@ -16,6 +17,7 @@ impl RatedAction {
|
|||||||
RatedAction::CreateAccount => "create-account",
|
RatedAction::CreateAccount => "create-account",
|
||||||
RatedAction::CheckResetPasswordTokenFailed => "check-reset-password-token",
|
RatedAction::CheckResetPasswordTokenFailed => "check-reset-password-token",
|
||||||
RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk",
|
RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk",
|
||||||
|
RatedAction::FailedPasswordLogin => "failed-login",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ impl RatedAction {
|
|||||||
RatedAction::CreateAccount => 5,
|
RatedAction::CreateAccount => 5,
|
||||||
RatedAction::CheckResetPasswordTokenFailed => 100,
|
RatedAction::CheckResetPasswordTokenFailed => 100,
|
||||||
RatedAction::RequestNewPasswordResetLink => 5,
|
RatedAction::RequestNewPasswordResetLink => 5,
|
||||||
|
RatedAction::FailedPasswordLogin => 15,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user