feat(api): implemented password criteria checking on registration
This commit is contained in:
parent
4bccfeef96
commit
441dc842f4
|
@ -15,6 +15,14 @@ This verification link will time out after 10 minutes.
|
||||||
| password | The password used for authentication. |
|
| password | The password used for authentication. |
|
||||||
| email | The email address used for validation. |
|
| 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
|
## Responses
|
||||||
### 200 - Success
|
### 200 - Success
|
||||||
The verification request was sent.
|
The verification request was sent.
|
||||||
|
@ -26,8 +34,15 @@ Blocked for security reasons.
|
||||||
The requested username or email is already taken.
|
The requested username or email is already taken.
|
||||||
|
|
||||||
__Content - JSON:__
|
__Content - JSON:__
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
|----------|----------------------------------------------------------------------|
|
|----------|----------------------------------------------------------------------|
|
||||||
| conflict | Can be `username` or `email`, depending on what caused the conflict. |
|
| conflict | Can be `username` or `email`, depending on what caused the conflict. |
|
||||||
### 422 - Error: Unprocessable Entity
|
### 422 - Error: Unprocessable Entity
|
||||||
Malformed email address.
|
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. |
|
|
@ -13,7 +13,7 @@ async fn register(
|
||||||
Ok(resp) => match resp {
|
Ok(resp) => match resp {
|
||||||
data::RegisterResponse::Success => HttpResponse::Ok().finish(),
|
data::RegisterResponse::Success => HttpResponse::Ok().finish(),
|
||||||
data::RegisterResponse::Conflict(b) => HttpResponse::Conflict().json(web::Json(b)),
|
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(),
|
data::RegisterResponse::Blocked => HttpResponse::Forbidden().finish(),
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -14,11 +14,18 @@ pub enum RegisterConflict {
|
||||||
Email,
|
Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(tag = "conflict", rename_all = "snake_case")]
|
||||||
|
pub enum RegisterUnprocessable {
|
||||||
|
Email,
|
||||||
|
Password,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RegisterResponse {
|
pub enum RegisterResponse {
|
||||||
Success,
|
Success,
|
||||||
Conflict(RegisterConflict),
|
Conflict(RegisterConflict),
|
||||||
MalformedEmail,
|
Unprocessable(RegisterUnprocessable),
|
||||||
Blocked,
|
Blocked,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
accounts::Account,
|
accounts::Account,
|
||||||
api::account::data,
|
api::account::data,
|
||||||
security::{is_sql_injection, AlphaExt},
|
security::{is_sql_injection, AlphaExt, meet_password_criteria},
|
||||||
tokens::{AuthToken, VerificationToken},
|
tokens::{AuthToken, VerificationToken},
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -13,18 +13,10 @@ pub async fn register(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
request: data::RegisterRequest,
|
request: data::RegisterRequest,
|
||||||
) -> Result<data::RegisterResponse> {
|
) -> Result<data::RegisterResponse> {
|
||||||
if is_sql_injection(&request.username) || is_sql_injection(&request.email) {
|
if is_sql_injection(&request.username) {
|
||||||
return Ok(data::RegisterResponse::Blocked);
|
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
|
// Check if the username is already taken
|
||||||
if let Some(account) = Account::from_username(pool, &request.username).await? {
|
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.
|
// 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?;
|
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 account = Account::new(pool, &request.username, &request.email, &request.password).await?;
|
||||||
|
|
||||||
let token = VerificationToken::new(pool, &account.id, chrono::Duration::minutes(10)).await?;
|
let token = VerificationToken::new(pool, &account.id, chrono::Duration::minutes(10)).await?;
|
||||||
|
|
|
@ -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 {
|
pub trait AlphaExt {
|
||||||
fn is_alpha(&self) -> bool;
|
fn is_alpha(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue