feat: added client side signup/verify data validation

This commit is contained in:
antifallobst 2023-10-11 17:34:33 +02:00
parent ca7ed256f8
commit f49205a2c6
Signed by: antifallobst
GPG Key ID: 2B4F402172791BAF
5 changed files with 99 additions and 32 deletions

View File

@ -30,6 +30,7 @@ impl Display for AccountRegisterConflictResponse {
#[serde(tag = "problem", rename_all = "snake_case")] #[serde(tag = "problem", rename_all = "snake_case")]
pub enum AccountRegisterCriteriaProblemResponse { pub enum AccountRegisterCriteriaProblemResponse {
Email, Email,
Username,
Password, Password,
} }

View File

@ -125,6 +125,9 @@ impl Session {
data::AccountRegisterCriteriaProblemResponse::Email => { data::AccountRegisterCriteriaProblemResponse::Email => {
Err(Error::msg(format!("Not a valid email!"))) Err(Error::msg(format!("Not a valid email!")))
} }
data::AccountRegisterCriteriaProblemResponse::Username => Err(Error::msg(
format!("The username has to be alphanumerical!"),
)),
data::AccountRegisterCriteriaProblemResponse::Password => Err(Error::msg( data::AccountRegisterCriteriaProblemResponse::Password => Err(Error::msg(
format!("The password does not meet the criteria!"), format!("The password does not meet the criteria!"),
)), )),

View File

@ -2,7 +2,6 @@ mod signin;
mod signup; mod signup;
use crate::topbar::Tab; use crate::topbar::Tab;
use anyhow::Result;
use signin::SignIn; use signin::SignIn;
use signup::SignUp; use signup::SignUp;
use std::rc::Rc; use std::rc::Rc;

View File

@ -1,6 +1,6 @@
use crate::backend::Session; use crate::backend::Session;
use crate::Callbacks; use crate::Callbacks;
use anyhow::{Error, Result}; use anyhow::Result;
use std::ops::Deref; use std::ops::Deref;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::HtmlInputElement; use web_sys::HtmlInputElement;
@ -27,11 +27,50 @@ struct Data {
pub username: String, pub username: String,
pub email: String, pub email: String,
pub password: String, pub password: String,
pub password_reenter: String, pub password_confirm: String,
pub token: String, pub token: String,
} }
impl Data {
fn confirm_password(&self) {
let document = web_sys::window()
.expect("Global window does not exist!")
.document()
.expect("Expecting a document on window!");
if self.password != self.password_confirm {
document
.get_element_by_id("signup-password")
.unwrap()
.dyn_into::<HtmlInputElement>()
.unwrap()
.set_custom_validity("The passwords don't match!");
document
.get_element_by_id("signup-password-confirm")
.unwrap()
.dyn_into::<HtmlInputElement>()
.unwrap()
.set_custom_validity("The passwords don't match!");
} else {
document
.get_element_by_id("signup-password")
.unwrap()
.dyn_into::<HtmlInputElement>()
.unwrap()
.set_custom_validity("");
document
.get_element_by_id("signup-password-confirm")
.unwrap()
.dyn_into::<HtmlInputElement>()
.unwrap()
.set_custom_validity("");
}
}
}
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct Props { pub struct Props {
pub close: Callback<()>, pub close: Callback<()>,
@ -47,7 +86,7 @@ pub fn SignUp(props: &Props) -> Html {
let on_username_changed = Callback::from(move |event: Event| { let on_username_changed = Callback::from(move |event: Event| {
let username = event let username = event
.target() .target()
.expect("WTF") .expect("Expected event target!")
.unchecked_into::<HtmlInputElement>() .unchecked_into::<HtmlInputElement>()
.value(); .value();
@ -60,46 +99,48 @@ pub fn SignUp(props: &Props) -> Html {
let on_email_changed = Callback::from(move |event: Event| { let on_email_changed = Callback::from(move |event: Event| {
let email = event let email = event
.target() .target()
.expect("WTF") .expect("Expected event target!")
.unchecked_into::<HtmlInputElement>() .unchecked_into::<HtmlInputElement>()
.value(); .value();
let mut data = cloned_state.deref().clone(); let mut state = cloned_state.deref().clone();
data.email = email; state.email = email;
cloned_state.set(data); cloned_state.set(state);
}); });
let cloned_state = state.clone(); let cloned_state = state.clone();
let on_password_changed = Callback::from(move |event: Event| { let on_password_input = Callback::from(move |event: InputEvent| {
let password = event let password = event
.target() .target()
.expect("WTF") .expect("Expected event target!")
.unchecked_into::<HtmlInputElement>() .unchecked_into::<HtmlInputElement>();
.value();
let mut data = cloned_state.deref().clone(); let mut state = cloned_state.deref().clone();
data.password = password; state.password = password.value();
cloned_state.set(data); state.confirm_password();
cloned_state.set(state);
}); });
let cloned_state = state.clone(); let cloned_state = state.clone();
let on_password_reenter_changed = Callback::from(move |event: Event| { let on_password_confirm_input = Callback::from(move |event: InputEvent| {
let password_reenter = event let password_confirm = event
.target() .target()
.expect("WTF") .expect("Expected event target!")
.unchecked_into::<HtmlInputElement>() .unchecked_into::<HtmlInputElement>();
.value();
let mut data = cloned_state.deref().clone(); let mut state = cloned_state.deref().clone();
data.password_reenter = password_reenter; state.password_confirm = password_confirm.value();
cloned_state.set(data); if password_confirm.check_validity() {
state.confirm_password();
}
cloned_state.set(state);
}); });
let cloned_state = state.clone(); let cloned_state = state.clone();
let on_token_changed = Callback::from(move |event: Event| { let on_token_changed = Callback::from(move |event: Event| {
let token = event let token = event
.target() .target()
.expect("WTF") .expect("Expected event target!")
.unchecked_into::<HtmlInputElement>() .unchecked_into::<HtmlInputElement>()
.value(); .value();
@ -171,22 +212,29 @@ pub fn SignUp(props: &Props) -> Html {
<form class="auth-container" onsubmit={onsubmit}> <form class="auth-container" onsubmit={onsubmit}>
<div class="auth-field"> <div class="auth-field">
<label for="username">{"Username"}</label><br/> <label for="username">{"Username"}</label><br/>
<input class="auth-input" type="text" name="username" onchange={on_username_changed}/><br/> <input class="auth-input" type="text" name="username" onchange={on_username_changed}
required=true pattern="^[a-zA-Z0-9]+$" /><br/>
<div class="auth-input-tooltip">{"The username has to be alphanumeric."}</div>
</div> </div>
<div class="auth-field"> <div class="auth-field">
<label for="email">{"Email"}</label><br/> <label for="email">{"Email"}</label><br/>
<input class="auth-input" type="text" name="email" onchange={on_email_changed}/><br/> <input class="auth-input" type="email" name="email" onchange={on_email_changed} required=true
pattern=r"^([a-z0-9_+]([a-z0-9_+.]*[a-z0-9_+])?)@([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})"/>
<div class="auth-input-tooltip">{"Enter a valid email."}</div>
</div> </div>
<div class="auth-field"> <div class="auth-field">
<label for="password">{"Password"}</label><br/> <label for="password">{"Password"}</label><br/>
<input class="auth-input" type="password" name="password" onchange={on_password_changed}/><br/> <input id="signup-password" class="auth-input" type="password" name="password" oninput={on_password_input} required=true
</div> pattern=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E])[A-Za-z0-9\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{12,}$" /><br/>
</div>
<div class="auth-field"> <div class="auth-field">
<label for="password-reenter">{"Password"}</label><br/> <label for="password-confirm">{"Confirm Password"}</label><br/>
<input class="auth-input" type="password" name="password-reenter" onchange={on_password_reenter_changed}/><br/> <input id="signup-password-confirm" class="auth-input" type="password" name="password-confirm" oninput={on_password_confirm_input} required=true
pattern=r"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E])[A-Za-z0-9\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{12,}$" /><br/>
<div class="auth-input-tooltip">{"A password must contain uppercase, lowercase, numbers and special characters."}</div>
</div> </div>
if !state.loading { if !state.loading {
@ -218,7 +266,8 @@ pub fn SignUp(props: &Props) -> Html {
<div class="auth-field"> <div class="auth-field">
<label for="token">{"Verification Token"}</label><br/> <label for="token">{"Verification Token"}</label><br/>
<input class="auth-input" type="text" name="token" onchange={on_token_changed}/><br/> <input class="auth-input" type="text" name="token" onchange={on_token_changed} required=true
pattern=r"^([0-9a-f]{32})" /><br/>
</div> </div>
if !state.loading { if !state.loading {

View File

@ -107,6 +107,21 @@
outline: none; outline: none;
} }
.auth-input:valid {
border-color: limegreen;
}
.auth-input-tooltip {
display: none;
color: red;
}
/*
.auth-input:invalid ~ .auth-input-tooltip {
display: block;
}
*/
.auth-status { .auth-status {
font: normal 14px Roboto; font: normal 14px Roboto;
color: #ff0000; color: #ff0000;