GeneIT/geneit_backend/src/controllers/auth_controller.rs

230 lines
6.8 KiB
Rust
Raw Normal View History

2023-05-24 14:19:46 +00:00
use crate::constants::StaticConstraints;
2023-05-26 15:55:19 +00:00
use crate::controllers::HttpResult;
2023-05-31 13:17:00 +00:00
use crate::models::{User, UserID};
2023-05-26 15:55:19 +00:00
use crate::services::rate_limiter_service::RatedAction;
2023-05-31 13:17:00 +00:00
use crate::services::{login_token_service, rate_limiter_service, users_service};
2023-05-24 14:19:46 +00:00
use actix_remote_ip::RemoteIP;
use actix_web::{web, HttpResponse};
#[derive(serde::Deserialize)]
pub struct CreateAccountBody {
name: String,
email: String,
}
/// Create a new account
2023-05-26 15:55:19 +00:00
pub async fn create_account(remote_ip: RemoteIP, req: web::Json<CreateAccountBody>) -> 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?;
2023-05-24 14:19:46 +00:00
2023-05-25 07:22:49 +00:00
// Check if email is valid
if !mailchecker::is_valid(&req.email) {
return Ok(HttpResponse::BadRequest().json("Email address is invalid!"));
}
2023-05-24 14:19:46 +00:00
// 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?;
2023-05-25 07:42:43 +00:00
// Check if email is already attached to an account
2023-05-26 15:55:19 +00:00
if users_service::exists_email(&req.email).await? {
return Ok(
HttpResponse::Conflict().json("An account with the same email address already exists!")
);
2023-05-25 07:42:43 +00:00
}
2023-05-24 14:19:46 +00:00
2023-05-25 07:42:43 +00:00
// Create the account
2023-05-30 13:12:58 +00:00
let mut user = users_service::create_account(&req.name, &req.email).await?;
2023-05-24 14:19:46 +00:00
2023-05-30 13:12:58 +00:00
// Trigger reset password (send mail)
users_service::request_reset_password(&mut user).await?;
2023-05-24 14:19:46 +00:00
// Account successfully created
Ok(HttpResponse::Created().finish())
}
2023-05-31 11:56:18 +00:00
#[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<RequestResetPasswordBody>,
) -> 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<CheckResetPasswordTokenBody>,
) -> 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 }))
}
2023-05-31 11:33:26 +00:00
#[derive(serde::Deserialize)]
pub struct ResetPasswordBody {
token: String,
password: String,
}
/// Reset password
pub async fn reset_password(remote_ip: RemoteIP, req: web::Json<ResetPasswordBody>) -> 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())
}
2023-05-31 13:17:00 +00:00
#[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?,
}))
}