feat: implement strong password check (#995)
* extract schema::User as format for MeQuery * refactor: restructure JWTPayload * feat: update frontend to adapt JWT token format change * delete generated files * feat: implement strong password check * [autofix.ci] apply automated fixes * fix format * fix unit test --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>r0.7
parent
8dc09ea4e7
commit
fbceaafbc9
|
|
@ -257,6 +257,9 @@ function useAuthenticatedSession() {
|
||||||
const { data: session, status } = useSession()
|
const { data: session, status } = useSession()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (status === 'loading') return
|
||||||
|
if (status === 'authenticated') return
|
||||||
|
|
||||||
if (data?.isAdminInitialized === false) {
|
if (data?.isAdminInitialized === false) {
|
||||||
router.replace('/auth/signup?isAdmin=true')
|
router.replace('/auth/signup?isAdmin=true')
|
||||||
} else if (status === 'unauthenticated') {
|
} else if (status === 'unauthenticated') {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use argon2::{
|
use argon2::{
|
||||||
password_hash,
|
password_hash,
|
||||||
|
|
@ -5,7 +7,7 @@ use argon2::{
|
||||||
Argon2, PasswordHasher, PasswordVerifier,
|
Argon2, PasswordHasher, PasswordVerifier,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use validator::Validate;
|
use validator::{Validate, ValidationError};
|
||||||
|
|
||||||
use super::db::DbConn;
|
use super::db::DbConn;
|
||||||
use crate::schema::{
|
use crate::schema::{
|
||||||
|
|
@ -41,12 +43,8 @@ struct RegisterInput {
|
||||||
code = "password1",
|
code = "password1",
|
||||||
message = "Password must be at most 20 characters"
|
message = "Password must be at most 20 characters"
|
||||||
))]
|
))]
|
||||||
|
#[validate(custom = "validate_password")]
|
||||||
password1: String,
|
password1: String,
|
||||||
#[validate(length(
|
|
||||||
min = 8,
|
|
||||||
code = "password2",
|
|
||||||
message = "Password must be at least 8 characters"
|
|
||||||
))]
|
|
||||||
#[validate(must_match(
|
#[validate(must_match(
|
||||||
code = "password2",
|
code = "password2",
|
||||||
message = "Passwords do not match",
|
message = "Passwords do not match",
|
||||||
|
|
@ -70,6 +68,38 @@ impl std::fmt::Debug for RegisterInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_password(value: &str) -> Result<(), ValidationError> {
|
||||||
|
let make_validation_error = |message: &'static str| {
|
||||||
|
let mut err = ValidationError::new("password1");
|
||||||
|
err.message = Some(Cow::Borrowed(message));
|
||||||
|
Err(err)
|
||||||
|
};
|
||||||
|
|
||||||
|
let contains_lowercase = value.chars().any(|x| x.is_ascii_lowercase());
|
||||||
|
if !contains_lowercase {
|
||||||
|
return make_validation_error("Password should contains at least one lowercase character");
|
||||||
|
}
|
||||||
|
|
||||||
|
let contains_uppercase = value.chars().any(|x| x.is_ascii_uppercase());
|
||||||
|
if !contains_uppercase {
|
||||||
|
return make_validation_error("Password should contains at least one uppercase character");
|
||||||
|
}
|
||||||
|
|
||||||
|
let contains_digit = value.chars().any(|x| x.is_ascii_digit());
|
||||||
|
if !contains_digit {
|
||||||
|
return make_validation_error("Password should contains at least one numeric character");
|
||||||
|
}
|
||||||
|
|
||||||
|
let contains_special_char = value.chars().any(|x| x.is_ascii_punctuation());
|
||||||
|
if !contains_special_char {
|
||||||
|
return make_validation_error(
|
||||||
|
"Password should contains at least one special character, e.g @#$%^&{}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Input parameters for token_auth mutation
|
/// Input parameters for token_auth mutation
|
||||||
/// See `RegisterInput` for `validate` attribute usage
|
/// See `RegisterInput` for `validate` attribute usage
|
||||||
#[derive(Validate)]
|
#[derive(Validate)]
|
||||||
|
|
@ -292,7 +322,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ADMIN_EMAIL: &str = "test@example.com";
|
static ADMIN_EMAIL: &str = "test@example.com";
|
||||||
static ADMIN_PASSWORD: &str = "123456789";
|
static ADMIN_PASSWORD: &str = "123456789$acR";
|
||||||
|
|
||||||
async fn register_admin_user(conn: &DbConn) -> RegisterResponse {
|
async fn register_admin_user(conn: &DbConn) -> RegisterResponse {
|
||||||
conn.register(
|
conn.register(
|
||||||
|
|
@ -342,7 +372,7 @@ mod tests {
|
||||||
register_admin_user(&conn).await;
|
register_admin_user(&conn).await;
|
||||||
|
|
||||||
let email = "user@user.com";
|
let email = "user@user.com";
|
||||||
let password = "12345678";
|
let password = "12345678dD^";
|
||||||
|
|
||||||
conn.create_invitation(email.to_owned()).await.unwrap();
|
conn.create_invitation(email.to_owned()).await.unwrap();
|
||||||
let invitation = &conn.list_invitations().await.unwrap()[0];
|
let invitation = &conn.list_invitations().await.unwrap()[0];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue