use crate::constants::StaticConstraints; use crate::controllers::HttpResult; use crate::models::{User, UserID}; use crate::services::login_token_service::LoginTokenValue; use crate::services::rate_limiter_service::RatedAction; use crate::services::{login_token_service, openid_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?, })) } #[derive(serde::Deserialize)] pub struct StartOpenIDLoginQuery { provider: String, } #[derive(serde::Serialize)] pub struct StartOpenIDLoginResponse { url: String, } /// Start OpenID login pub async fn start_openid_login( remote_ip: RemoteIP, req: web::Json, ) -> HttpResult { // Rate limiting if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::StartOpenIDLogin).await? { return Ok(HttpResponse::TooManyRequests().finish()); } rate_limiter_service::record_action(remote_ip.0, RatedAction::StartOpenIDLogin).await?; let url = openid_service::start_login(&req.provider, remote_ip.0).await?; Ok(HttpResponse::Ok().json(StartOpenIDLoginResponse { url })) } #[derive(serde::Deserialize)] pub struct FinishOpenIDLoginQuery { code: String, state: String, } /// Finish OpenID login pub async fn finish_openid_login( remote_ip: RemoteIP, req: web::Json, ) -> HttpResult { let user_info = openid_service::finish_login(remote_ip.0, &req.code, &req.state).await?; if user_info.email_verified != Some(true) { log::error!("Email is not verified!"); return Ok( HttpResponse::Unauthorized().json("Email non vérifié par le fournisseur d'identité !") ); } let mail = match user_info.email { Some(m) => m, None => { return Ok(HttpResponse::Unauthorized() .json("Email non spécifié par le fournisseur d'identité !")); } }; // Create the account, if required if !users_service::exists_email(&mail).await? { let name = match (user_info.name, user_info.given_name, user_info.family_name) { (Some(name), _, _) => name, (None, Some(g), Some(f)) => format!("{g} {f}"), (_, _, _) => { return Ok(HttpResponse::Unauthorized() .json("Nom non spécifié par le fournisseur d'identité !")); } }; users_service::create_account(&name, &mail).await?; } let user = users_service::get_by_mail(&mail).await?; // OpenID auth is enough to validate accounts users_service::validate_account(&user).await?; finish_login(&user).await } /// Logout user pub async fn logout(token: LoginTokenValue) -> HttpResult { login_token_service::delete_token(&token).await?; Ok(HttpResponse::NoContent().finish()) }