Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve password validation #164

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
.env.development.local
.env.test.local
.env.production.local

.idea
npm-debug.log*
yarn-debug.log*
yarn-error.log*
1 change: 0 additions & 1 deletion frontend/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import 'mini.css/dist/mini-nord.css'
import { createRoot } from 'react-dom/client';

import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
Expand Down
316 changes: 166 additions & 150 deletions frontend/src/pages/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,173 +1,189 @@
import React, { useState } from "react";
import React, {useState} from "react";
import toast from "react-hot-toast";
import { useHistory } from "react-router-dom";
import { register } from "../helpers/api";
import {useHistory} from "react-router-dom";
import {register} from "../helpers/api";
import Helmet from "react-helmet";

interface FormData {
email: string;
password: string;
passwordValidate: string;
email: string;
password: string;
passwordValidate: string;
}

interface FormErrors {
email: string;
password: string;
repeatPassword?: string;
passwordValidate: string;
email: string;
password: string;
repeatPassword?: string;
passwordValidate: string;
}

const textCenter: React.CSSProperties = {
textAlign: "center",
textAlign: "center",
};

const errorStyle: React.CSSProperties = {
display: 'block',
textAlign:'end',
maxWidth: '400px'
}
const REQUIRED = "Required";
const PASSWORD_REQUIREMENTS_MISMATCH = "Password must contain at least 8 characters, one uppercase letter, one lowercase letter, and one number and special character";
const PASSWORDS_DO_NOT_MATCH = "Passwords do not match";

function SignUp(): JSX.Element {
const history = useHistory();
const [formData, setFormData] = useState<FormData>({
email: "",
password: "",
passwordValidate: "",
});
const [errors, setErrors] = useState<FormErrors>({
email: "",
password: "",
repeatPassword: "",
passwordValidate: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);

const validateEmpty = (value: string): string =>
value.trim() ? "" : REQUIRED;

const validate = (field: keyof FormData, value: string): void => {
setFormData({ ...formData, [field]: value });

if (field === "email") {
setErrors({ ...errors, [field]: validateEmpty(value) });
} else {
const isPasswordField = field === "password";
const passwordsMatch = isPasswordField
? value === formData.passwordValidate
: value === formData.password;
setErrors({
...errors,
[field]: validateEmpty(value),
repeatPassword: passwordsMatch ? "" : PASSWORDS_DO_NOT_MATCH,
});
}
};

const handleSubmit = async (
e: React.FormEvent<HTMLFormElement>
): Promise<void> => {
e.preventDefault();

const { email, password, passwordValidate } = formData;
const emailError = validateEmpty(email);
const passwordError = validateEmpty(password);
const passwordValidateError = !passwordValidate.trim()
? REQUIRED
: password !== passwordValidate
? PASSWORDS_DO_NOT_MATCH
: "";

setErrors({
email: emailError,
password: passwordError,
passwordValidate: passwordValidateError,
const history = useHistory();
const [formData, setFormData] = useState<FormData>({
email: "",
password: "",
passwordValidate: "",
});

if (emailError || passwordError || passwordValidateError) {
return;
const [errors, setErrors] = useState<FormErrors>({
email: "",
password: "",
repeatPassword: "",
passwordValidate: "",
});
const [isSubmitting, setIsSubmitting] = useState(false);

const validateEmpty = (value: string): string =>
value.trim() ? "" : REQUIRED;

const validatePassword = (password: string): string => {
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_])[a-zA-Z\d\W_]{8,}$/;
if (!password.trim()) {
return REQUIRED;
}
if (!passwordRegex.test(password)) {
return PASSWORD_REQUIREMENTS_MISMATCH
}
return "";
}

setIsSubmitting(true);

try {
const response = await register({ email, password });
if (response.message) {
toast.error(response.message as string);
} else {
toast.success("Registered successfully");
history.push("/login");
}
} catch (error) {
toast.error("An error occurred while registering");
} finally {
setIsSubmitting(false);
}
};

return (
<div>
<Helmet>
<title>The Lord of the Rings API - The one API | Sign up </title>
</Helmet>
<p>
<br />
Get your access token now by registering a free API account.
</p>
<form onSubmit={handleSubmit}>
<div className="input-group fluid">
<label>
E-Mail:{" "}
<input
type="email"
value={formData.email}
onChange={(e) => validate("email", e.target.value)}
onBlur={(e) => validate("email", e.target.value)}
/>
{errors.email && <em>{errors.email}</em>}
</label>
</div>
<div className="input-group fluid">
<label>
Password:{" "}
<input
type="password"
value={formData.password}
onChange={(e) => validate("password", e.target.value)}
onBlur={(e) => validate("password", e.target.value)}
/>
{errors.password && <em>{errors.password}</em>}
</label>
</div>
<div className="input-group fluid">
<label>
Repeat Password:{" "}
<input
type="password"
value={formData.passwordValidate}
onChange={(e) => validate("passwordValidate", e.target.value)}
onBlur={(e) => validate("passwordValidate", e.target.value)}
/>
{errors.passwordValidate && <em>{errors.passwordValidate}</em>}
</label>
</div>
<div className="input-group fluid">
<button
className="primary"
type="submit"
disabled={
isSubmitting ||
!!errors.email ||
!!errors.password ||
!!errors.passwordValidate
const validate = (field: keyof FormData, value: string): void => {
setFormData({...formData, [field]: value});

if (field === "email") {
setErrors({...errors, [field]: validateEmpty(value)});
} else {
const isPasswordField = field === "password";
const passwordsMatch = isPasswordField
? value === formData.passwordValidate
: value === formData.password;
setErrors({
...errors,
[field]: validateEmpty(value),
repeatPassword: passwordsMatch ? "" : PASSWORDS_DO_NOT_MATCH,
});
}
};

const handleSubmit = async (
e: React.FormEvent<HTMLFormElement>
): Promise<void> => {
e.preventDefault();

const {email, password, passwordValidate} = formData;
const emailError = validateEmpty(email);
const passwordError = validatePassword(password);
const passwordValidateError = !passwordValidate.trim()
? REQUIRED
: password !== passwordValidate
? PASSWORDS_DO_NOT_MATCH
: "";

setErrors({
email: emailError,
password: passwordError,
passwordValidate: passwordValidateError,
});

if (emailError || passwordError || passwordValidateError) {
return;
}

setIsSubmitting(true);

try {
const response = await register({email, password});
if (response.message) {
toast.error(response.message as string);
} else {
toast.success("Registered successfully");
history.push("/login");
}
>
Submit
</button>
</div>
<div style={textCenter}>
<em>{isSubmitting ? "Submitting..." : null}</em>
{errors.repeatPassword && <em>{errors.repeatPassword}</em>}
} catch (error) {
toast.error("An error occurred while registering");
} finally {
setIsSubmitting(false);
}
};

return (
<div>
<Helmet>
<title>The Lord of the Rings API - The one API | Sign up </title>
</Helmet>
<p>
<br/>
Get your access token now by registering a free API account.
</p>
<form onSubmit={handleSubmit}>
<div className="input-group fluid">
<label>
E-Mail:{" "}
<input
type="email"
value={formData.email}
onChange={(e) => validate("email", e.target.value)}
onBlur={(e) => validate("email", e.target.value)}
/>
{errors.email && <em style={errorStyle}>{errors.email}</em>}
</label>
</div>
<div className="input-group fluid">
<label>
Password:{" "}
<input
type="password"
value={formData.password}
onChange={(e) => validate("password", e.target.value)}
onBlur={(e) => validate("password", e.target.value)}
/>
{errors.password && <em style={errorStyle}>{errors.password}</em>}
</label>
</div>
<div className="input-group fluid">
<label>
Repeat Password:{" "}
<input
type="password"
value={formData.passwordValidate}
onChange={(e) => validate("passwordValidate", e.target.value)}
onBlur={(e) => validate("passwordValidate", e.target.value)}
/>
{errors.passwordValidate && <em style={errorStyle}>{errors.passwordValidate}</em>}
</label>
</div>
<div className="input-group fluid">
<button
className="primary"
type="submit"
disabled={
isSubmitting ||
!!errors.email ||
!!errors.password ||
!!errors.passwordValidate
}
>
Submit
</button>
</div>
<div style={textCenter}>
<em>{isSubmitting ? "Submitting..." : null}</em>
{errors.repeatPassword && <em style={errorStyle}>{errors.repeatPassword}</em>}
</div>
</form>
</div>
</form>
</div>
);
);
}

export default SignUp;
Loading