From 441dc842f4c2ed5c1345f5b6271f62be5b81f55b Mon Sep 17 00:00:00 2001 From: antifallobst Date: Sun, 10 Sep 2023 03:53:50 +0200 Subject: [PATCH] feat(api): implemented password criteria checking on registration --- docs/account/register.md | 17 ++++++++++++++++- src/api/account/calls.rs | 2 +- src/api/account/data.rs | 9 ++++++++- src/api/account/handlers.rs | 24 ++++++++++++++---------- src/security.rs | 14 ++++++++++++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/docs/account/register.md b/docs/account/register.md index 62beb32..f8301ff 100644 --- a/docs/account/register.md +++ b/docs/account/register.md @@ -15,6 +15,14 @@ This verification link will time out after 10 minutes. | password | The password used for authentication. | | email | The email address used for validation. | +The password has to meet the following criteria: +- minimum length: 12 characters +- numbers +- special characters +- lowercase letters +- uppercase letters + + ## Responses ### 200 - Success The verification request was sent. @@ -26,8 +34,15 @@ Blocked for security reasons. The requested username or email is already taken. __Content - JSON:__ + | Field | Description | |----------|----------------------------------------------------------------------| | conflict | Can be `username` or `email`, depending on what caused the conflict. | ### 422 - Error: Unprocessable Entity -Malformed email address. \ No newline at end of file +The email is malformed, or the password does not meet the criteria. + +__Content - JSON:__ + +| Field | Description | +|---------|---------------------------------------------------------------------| +| problem | Can be `email` or `password`, depending on what caused the problem. | \ No newline at end of file diff --git a/src/api/account/calls.rs b/src/api/account/calls.rs index 0359fa1..f636b6c 100644 --- a/src/api/account/calls.rs +++ b/src/api/account/calls.rs @@ -13,7 +13,7 @@ async fn register( Ok(resp) => match resp { data::RegisterResponse::Success => HttpResponse::Ok().finish(), data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)), - data::RegisterResponse::MalformedEmail => HttpResponse::UnprocessableEntity().finish(), + data::RegisterResponse::Unprocessable(b) => HttpResponse::UnprocessableEntity().json(web::Json(b)), data::RegisterResponse::Blocked => HttpResponse::Forbidden().finish(), }, Err(e) => { diff --git a/src/api/account/data.rs b/src/api/account/data.rs index 353b2bc..8cf590d 100644 --- a/src/api/account/data.rs +++ b/src/api/account/data.rs @@ -14,11 +14,18 @@ pub enum RegisterConflict { Email, } +#[derive(Debug, Serialize)] +#[serde(tag = "conflict", rename_all = "snake_case")] +pub enum RegisterUnprocessable { + Email, + Password, +} + #[derive(Debug)] pub enum RegisterResponse { Success, Conflict(RegisterConflict), - MalformedEmail, + Unprocessable(RegisterUnprocessable), Blocked, } diff --git a/src/api/account/handlers.rs b/src/api/account/handlers.rs index 216843c..5022d38 100644 --- a/src/api/account/handlers.rs +++ b/src/api/account/handlers.rs @@ -1,7 +1,7 @@ use crate::{ accounts::Account, api::account::data, - security::{is_sql_injection, AlphaExt}, + security::{is_sql_injection, AlphaExt, meet_password_criteria}, tokens::{AuthToken, VerificationToken}, }; use anyhow::Result; @@ -13,18 +13,10 @@ pub async fn register( pool: &PgPool, request: data::RegisterRequest, ) -> Result { - if is_sql_injection(&request.username) || is_sql_injection(&request.email) { + if is_sql_injection(&request.username) { return Ok(data::RegisterResponse::Blocked); } - let email_regex = regex::Regex::new( - r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})", - )?; - - if !email_regex.is_match(&request.email) { - return Ok(data::RegisterResponse::MalformedEmail); - } - // Check if the username is already taken if let Some(account) = Account::from_username(pool, &request.username).await? { // Check if the account that has taken the username is verified or has an open verification request. @@ -55,6 +47,18 @@ pub async fn register( account.delete(pool).await?; } + let email_regex = regex::Regex::new( + r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})", + )?; + + if !email_regex.is_match(&request.email) { + return Ok(data::RegisterResponse::Unprocessable(data::RegisterUnprocessable::Email)); + } + + if !meet_password_criteria(&request.password) { + return Ok(data::RegisterResponse::Unprocessable(data::RegisterUnprocessable::Password)); + } + let account = Account::new(pool, &request.username, &request.email, &request.password).await?; let token = VerificationToken::new(pool, &account.id, chrono::Duration::minutes(10)).await?; diff --git a/src/security.rs b/src/security.rs index a729e9a..1ba6aae 100644 --- a/src/security.rs +++ b/src/security.rs @@ -5,6 +5,20 @@ pub fn is_sql_injection(string: &String) -> bool { } } +pub fn meet_password_criteria(string: &String) -> bool { + if string.len() < 12 || + !string.chars().any(|c| c.is_numeric()) || + !string.chars().any(|c| c.is_lowercase()) || + !string.chars().any(|c| c.is_uppercase()) || + !string.chars().any(|c|{ + let x = c as u8; + (x >= 32 && x <= 47) || (x >= 58 && x <= 64) || (x >= 91 && x <= 96) || (x >= 123 && x <= 126) + }) { + return false; + } + true +} + pub trait AlphaExt { fn is_alpha(&self) -> bool; }