use crate::constants::StaticConstraints; use crate::controllers::HttpResult; use crate::models::{User, UserID}; use crate::services::rate_limiter_service::RatedAction; use crate::services::{login_token_service, rate_limiter_service, users_service}; use actix_remote_ip::RemoteIP; use actix_web::{web, HttpResponse}; #[derive(serde::Deserialize)] pub struct CreateAccountBody { name: String, email: String, } /// Create a new account pub async fn create_account(remote_ip: RemoteIP, req: web::Json) -> HttpResult { // Rate limiting if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::CreateAccount).await? { return Ok(HttpResponse::TooManyRequests().finish()); } rate_limiter_service::record_action(remote_ip.0, RatedAction::CreateAccount).await?; // Check if email is valid if !mailchecker::is_valid(&req.email) { return Ok(HttpResponse::BadRequest().json("Email address is invalid!")); } // Check parameters let constraints = StaticConstraints::default(); if !constraints.user_name_len.validate(&req.name) || !constraints.mail_len.validate(&req.email) { return Ok(HttpResponse::BadRequest().json("Size constraints were not respected!")); } // Perform cleanup users_service::delete_not_validated_accounts().await?; // Check if email is already attached to an account if users_service::exists_email(&req.email).await? { return Ok( HttpResponse::Conflict().json("An account with the same email address already exists!") ); } // Create the account let mut user = users_service::create_account(&req.name, &req.email).await?; // Trigger reset password (send mail) users_service::request_reset_password(&mut user).await?; // Account successfully created Ok(HttpResponse::Created().finish()) } #[derive(serde::Deserialize)] pub struct RequestResetPasswordBody { mail: String, } /// Request the creation of a new password reset link pub async fn request_reset_password( remote_ip: RemoteIP, req: web::Json, ) -> HttpResult { // Rate limiting if rate_limiter_service::should_block_action( remote_ip.0, RatedAction::RequestNewPasswordResetLink, ) .await? { return Ok(HttpResponse::TooManyRequests().finish()); } rate_limiter_service::record_action(remote_ip.0, RatedAction::RequestNewPasswordResetLink) .await?; match users_service::get_by_mail(&req.mail).await { Ok(mut user) => users_service::request_reset_password(&mut user).await?, Err(e) => { log::error!( "Could not locate user account {}! (error silently ignored)", e ); } } Ok(HttpResponse::Created().finish()) } #[derive(serde::Deserialize)] pub struct CheckResetPasswordTokenBody { token: String, } #[derive(serde::Serialize)] pub struct CheckResetPasswordTokenResponse { name: String, } /// Check reset password token pub async fn check_reset_password_token( remote_ip: RemoteIP, req: web::Json, ) -> HttpResult { // Rate limiting if rate_limiter_service::should_block_action( remote_ip.0, RatedAction::CheckResetPasswordTokenFailed, ) .await? { return Ok(HttpResponse::TooManyRequests().finish()); } let user = match users_service::get_by_pwd_reset_token(&req.token).await { Ok(t) => t, Err(e) => { rate_limiter_service::record_action( remote_ip.0, RatedAction::CheckResetPasswordTokenFailed, ) .await?; log::error!("Password reset token could not be used: {}", e); return Ok(HttpResponse::NotFound().finish()); } }; Ok(HttpResponse::Ok().json(CheckResetPasswordTokenResponse { name: user.name })) } #[derive(serde::Deserialize)] pub struct ResetPasswordBody { token: String, password: String, } /// Reset password pub async fn reset_password(remote_ip: RemoteIP, req: web::Json) -> HttpResult { // Rate limiting if rate_limiter_service::should_block_action( remote_ip.0, RatedAction::CheckResetPasswordTokenFailed, ) .await? { return Ok(HttpResponse::TooManyRequests().finish()); } let user = match users_service::get_by_pwd_reset_token(&req.token).await { Ok(t) => t, Err(e) => { rate_limiter_service::record_action( remote_ip.0, RatedAction::CheckResetPasswordTokenFailed, ) .await?; log::error!("Password reset token could not be used: {}", e); return Ok(HttpResponse::NotFound().finish()); } }; if !StaticConstraints::default() .password_len .validate(&req.password) { return Ok(HttpResponse::BadRequest().json("Taille du mot de passe invalide!")); } // Validate account, if required users_service::validate_account(&user).await?; // Change user password users_service::change_password(&user, &req.password).await?; 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) -> 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?, })) }