chore: handled password validation on onboarding screen (#4894)
This commit is contained in:
parent
522cdc6873
commit
1b1302dfbd
6 changed files with 277 additions and 125 deletions
|
|
@ -1,69 +1,94 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { FC, useMemo } from "react";
|
||||||
|
// import { CircleCheck } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { CircleCheck } from "lucide-react";
|
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import {
|
||||||
// icons
|
E_PASSWORD_STRENGTH,
|
||||||
|
// PASSWORD_CRITERIA,
|
||||||
|
getPasswordStrength,
|
||||||
|
} from "@/helpers/password.helper";
|
||||||
|
|
||||||
type Props = {
|
type TPasswordStrengthMeter = {
|
||||||
password: string;
|
password: string;
|
||||||
|
isFocused?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
|
export const PasswordStrengthMeter: FC<TPasswordStrengthMeter> = (props) => {
|
||||||
const { password } = props;
|
const { password, isFocused = false } = props;
|
||||||
|
// derived values
|
||||||
const strength = getPasswordStrength(password);
|
const strength = useMemo(() => getPasswordStrength(password), [password]);
|
||||||
let bars = [];
|
const strengthBars = useMemo(() => {
|
||||||
let text = "";
|
switch (strength) {
|
||||||
let textColor = "";
|
case E_PASSWORD_STRENGTH.EMPTY: {
|
||||||
|
return {
|
||||||
if (password.length === 0) {
|
bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
bars = [`bg-[#F0F0F3]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
text: "Please enter your password.",
|
||||||
text = "Password requirements";
|
textColor: "text-custom-text-100",
|
||||||
} else if (password.length < 8) {
|
};
|
||||||
bars = [`bg-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
|
||||||
text = "Password is too short";
|
|
||||||
textColor = `text-[#DC3E42]`;
|
|
||||||
} else if (strength < 3) {
|
|
||||||
bars = [`bg-[#FFBA18]`, `bg-[#FFBA18]`, `bg-[#F0F0F3]`];
|
|
||||||
text = "Password is weak";
|
|
||||||
textColor = `text-[#FFBA18]`;
|
|
||||||
} else {
|
|
||||||
bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`];
|
|
||||||
text = "Password is strong";
|
|
||||||
textColor = `text-[#3E9B4F]`;
|
|
||||||
}
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Password length should me more than 8 characters.",
|
||||||
|
textColor: "text-red-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Password is weak.",
|
||||||
|
textColor: "text-red-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.STRENGTH_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`],
|
||||||
|
text: "Password is strong.",
|
||||||
|
textColor: "text-green-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Please enter your password.",
|
||||||
|
textColor: "text-custom-text-100",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [strength]);
|
||||||
|
|
||||||
const criteria = [
|
const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
|
||||||
{ label: "Min 8 characters", isValid: password.length >= 8 },
|
|
||||||
{ label: "Min 1 upper-case letter", isValid: /[A-Z]/.test(password) },
|
|
||||||
{ label: "Min 1 number", isValid: /\d/.test(password) },
|
|
||||||
{ label: "Min 1 special character", isValid: /[!@#$%^&*]/.test(password) },
|
|
||||||
];
|
|
||||||
|
|
||||||
|
if (!isPasswordMeterVisible) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full space-y-2 pt-2">
|
||||||
<div className="flex w-full gap-1.5">
|
<div className="space-y-1.5">
|
||||||
{bars.map((color, index) => (
|
<div className="relative flex items-center gap-2">
|
||||||
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
|
{strengthBars?.bars.map((color, index) => (
|
||||||
|
<div key={`${color}-${index}`} className={cn("w-full h-1 rounded-full", color)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className={cn("text-xs font-medium py-1", textColor)}>{text}</p>
|
<div className={cn(`text-xs font-medium text-custom-text-100`, strengthBars?.textColor)}>
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-2">
|
{strengthBars?.text}
|
||||||
{criteria.map((criterion, index) => (
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="relative flex flex-wrap gap-x-4 gap-y-2">
|
||||||
|
{PASSWORD_CRITERIA.map((criteria) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={criteria.key}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1 text-xs font-medium",
|
"relative flex items-center gap-1 text-xs",
|
||||||
criterion.isValid ? `text-[#3E9B4F]` : "text-custom-text-400"
|
criteria.isCriteriaValid(password) ? `text-green-500/70` : "text-custom-text-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CircleCheck width={14} height={14} />
|
<CircleCheck width={14} height={14} />
|
||||||
{criterion.label}
|
{criteria.label}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { Button, Checkbox, Input, Spinner } from "@plane/ui";
|
||||||
import { Banner, PasswordStrengthMeter } from "@/components/common";
|
import { Banner, PasswordStrengthMeter } from "@/components/common";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/auth.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||||
formData.first_name &&
|
formData.first_name &&
|
||||||
formData.email &&
|
formData.email &&
|
||||||
formData.password &&
|
formData.password &&
|
||||||
getPasswordStrength(formData.password) >= 3 &&
|
getPasswordStrength(formData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
|
||||||
formData.password === formData.confirm_password
|
formData.password === formData.confirm_password
|
||||||
? false
|
? false
|
||||||
: true,
|
: true,
|
||||||
|
|
@ -271,7 +271,7 @@ export const InstanceSetupForm: FC = (props) => {
|
||||||
{errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && (
|
{errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD && errorData.message && (
|
||||||
<p className="px-1 text-xs text-red-500">{errorData.message}</p>
|
<p className="px-1 text-xs text-red-500">{errorData.message}</p>
|
||||||
)}
|
)}
|
||||||
{isPasswordInputFocused && <PasswordStrengthMeter password={formData.password} />}
|
<PasswordStrengthMeter password={formData.password} isFocused={isPasswordInputFocused} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,67 @@
|
||||||
import zxcvbn from "zxcvbn";
|
import zxcvbn from "zxcvbn";
|
||||||
|
|
||||||
export const isPasswordCriteriaMet = (password: string) => {
|
export enum E_PASSWORD_STRENGTH {
|
||||||
const criteria = [password.length >= 8, /[A-Z]/.test(password), /\d/.test(password), /[!@#$%^&*]/.test(password)];
|
EMPTY = "empty",
|
||||||
|
LENGTH_NOT_VALID = "length_not_valid",
|
||||||
|
STRENGTH_NOT_VALID = "strength_not_valid",
|
||||||
|
STRENGTH_VALID = "strength_valid",
|
||||||
|
}
|
||||||
|
|
||||||
return criteria.every((criterion) => criterion);
|
const PASSWORD_MIN_LENGTH = 8;
|
||||||
};
|
// const PASSWORD_NUMBER_REGEX = /\d/;
|
||||||
|
// const PASSWORD_CHAR_CAPS_REGEX = /[A-Z]/;
|
||||||
export const getPasswordStrength = (password: string) => {
|
// const PASSWORD_SPECIAL_CHAR_REGEX = /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/;
|
||||||
if (password.length === 0) return 0;
|
|
||||||
if (password.length < 8) return 1;
|
export const PASSWORD_CRITERIA = [
|
||||||
if (!isPasswordCriteriaMet(password)) return 2;
|
{
|
||||||
|
key: "min_8_char",
|
||||||
const result = zxcvbn(password);
|
label: "Min 8 characters",
|
||||||
return result.score;
|
isCriteriaValid: (password: string) => password.length >= PASSWORD_MIN_LENGTH,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// key: "min_1_upper_case",
|
||||||
|
// label: "Min 1 upper-case letter",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_NUMBER_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: "min_1_number",
|
||||||
|
// label: "Min 1 number",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_CHAR_CAPS_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: "min_1_special_char",
|
||||||
|
// label: "Min 1 special character",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_SPECIAL_CHAR_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => {
|
||||||
|
let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY;
|
||||||
|
|
||||||
|
if (!password || password === "" || password.length <= 0) {
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length >= PASSWORD_MIN_LENGTH) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
||||||
|
} else {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID;
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordCriteriaValidation = PASSWORD_CRITERIA.map((criteria) => criteria.isCriteriaValid(password)).every(
|
||||||
|
(criterion) => criterion
|
||||||
|
);
|
||||||
|
const passwordStrengthScore = zxcvbn(password).score;
|
||||||
|
|
||||||
|
if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passwordStrength;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { Button, Input, Spinner } from "@plane/ui";
|
||||||
import { PasswordStrengthMeter } from "@/components/account";
|
import { PasswordStrengthMeter } from "@/components/account";
|
||||||
// helpers
|
// helpers
|
||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/auth.service";
|
import { AuthService } from "@/services/auth.service";
|
||||||
// types
|
// types
|
||||||
|
|
@ -67,8 +67,8 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
|
|
||||||
const passwordSupport = passwordFormData.password.length > 0 &&
|
const passwordSupport = passwordFormData.password.length > 0 &&
|
||||||
mode === EAuthModes.SIGN_UP &&
|
mode === EAuthModes.SIGN_UP &&
|
||||||
(getPasswordStrength(passwordFormData.password) < 3 || isPasswordInputFocused) && (
|
getPasswordStrength(passwordFormData.password) != E_PASSWORD_STRENGTH.STRENGTH_VALID && (
|
||||||
<PasswordStrengthMeter password={passwordFormData.password} />
|
<PasswordStrengthMeter password={passwordFormData.password} isFocused={isPasswordInputFocused} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const isButtonDisabled = useMemo(
|
const isButtonDisabled = useMemo(
|
||||||
|
|
@ -76,7 +76,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||||
!isSubmitting &&
|
!isSubmitting &&
|
||||||
!!passwordFormData.password &&
|
!!passwordFormData.password &&
|
||||||
(mode === EAuthModes.SIGN_UP
|
(mode === EAuthModes.SIGN_UP
|
||||||
? getPasswordStrength(passwordFormData.password) >= 3 &&
|
? getPasswordStrength(passwordFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
|
||||||
passwordFormData.password === passwordFormData.confirm_password
|
passwordFormData.password === passwordFormData.confirm_password
|
||||||
: true)
|
: true)
|
||||||
? false
|
? false
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,94 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// icons
|
import { FC, useMemo } from "react";
|
||||||
import { CircleCheck } from "lucide-react";
|
// import { CircleCheck } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { cn } from "@/helpers/common.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
import {
|
||||||
|
E_PASSWORD_STRENGTH,
|
||||||
|
// PASSWORD_CRITERIA,
|
||||||
|
getPasswordStrength,
|
||||||
|
} from "@/helpers/password.helper";
|
||||||
|
|
||||||
type Props = {
|
type TPasswordStrengthMeter = {
|
||||||
password: string;
|
password: string;
|
||||||
|
isFocused?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
|
export const PasswordStrengthMeter: FC<TPasswordStrengthMeter> = (props) => {
|
||||||
const { password } = props;
|
const { password, isFocused = false } = props;
|
||||||
|
// derived values
|
||||||
const strength = getPasswordStrength(password);
|
const strength = useMemo(() => getPasswordStrength(password), [password]);
|
||||||
let bars = [];
|
const strengthBars = useMemo(() => {
|
||||||
let text = "";
|
switch (strength) {
|
||||||
let textColor = "";
|
case E_PASSWORD_STRENGTH.EMPTY: {
|
||||||
|
return {
|
||||||
if (password.length === 0) {
|
bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
bars = [`bg-[#F0F0F3]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
text: "Please enter your password.",
|
||||||
text = "Password requirements";
|
textColor: "text-custom-text-100",
|
||||||
} else if (password.length < 8) {
|
};
|
||||||
bars = [`bg-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
|
||||||
text = "Password is too short";
|
|
||||||
textColor = `text-[#DC3E42]`;
|
|
||||||
} else if (strength < 3) {
|
|
||||||
bars = [`bg-[#FFBA18]`, `bg-[#FFBA18]`, `bg-[#F0F0F3]`];
|
|
||||||
text = "Password is weak";
|
|
||||||
textColor = `text-[#FFBA18]`;
|
|
||||||
} else {
|
|
||||||
bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`];
|
|
||||||
text = "Password is strong";
|
|
||||||
textColor = `text-[#3E9B4F]`;
|
|
||||||
}
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Password length should me more than 8 characters.",
|
||||||
|
textColor: "text-red-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Password is weak.",
|
||||||
|
textColor: "text-red-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case E_PASSWORD_STRENGTH.STRENGTH_VALID: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`],
|
||||||
|
text: "Password is strong.",
|
||||||
|
textColor: "text-green-500",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return {
|
||||||
|
bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||||
|
text: "Please enter your password.",
|
||||||
|
textColor: "text-custom-text-100",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [strength]);
|
||||||
|
|
||||||
const criteria = [
|
const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
|
||||||
{ label: "Min 8 characters", isValid: password.length >= 8 },
|
|
||||||
{ label: "Min 1 upper-case letter", isValid: /[A-Z]/.test(password) },
|
|
||||||
{ label: "Min 1 number", isValid: /\d/.test(password) },
|
|
||||||
{ label: "Min 1 special character", isValid: /[!@#$%^&*]/.test(password) },
|
|
||||||
];
|
|
||||||
|
|
||||||
|
if (!isPasswordMeterVisible) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="w-full p-1">
|
<div className="w-full space-y-2 pt-2">
|
||||||
<div className="flex w-full gap-1.5">
|
<div className="space-y-1.5">
|
||||||
{bars.map((color, index) => (
|
<div className="relative flex items-center gap-2">
|
||||||
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
|
{strengthBars?.bars.map((color, index) => (
|
||||||
|
<div key={`${color}-${index}`} className={cn("w-full h-1 rounded-full", color)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className={cn("text-xs font-medium py-1", textColor)}>{text}</p>
|
<div className={cn(`text-xs font-medium text-custom-text-100`, strengthBars?.textColor)}>
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-2">
|
{strengthBars?.text}
|
||||||
{criteria.map((criterion, index) => (
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="relative flex flex-wrap gap-x-4 gap-y-2">
|
||||||
|
{PASSWORD_CRITERIA.map((criteria) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={criteria.key}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1 text-xs font-medium",
|
"relative flex items-center gap-1 text-xs",
|
||||||
criterion.isValid ? `text-[#3E9B4F]` : "text-custom-text-400"
|
criteria.isCriteriaValid(password) ? `text-green-500/70` : "text-custom-text-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CircleCheck width={14} height={14} />
|
<CircleCheck width={14} height={14} />
|
||||||
{criterion.label}
|
{criteria.label}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,67 @@
|
||||||
import zxcvbn from "zxcvbn";
|
import zxcvbn from "zxcvbn";
|
||||||
|
|
||||||
export const isPasswordCriteriaMet = (password: string) => {
|
export enum E_PASSWORD_STRENGTH {
|
||||||
const criteria = [password.length >= 8, /[A-Z]/.test(password), /\d/.test(password), /[!@#$%^&*]/.test(password)];
|
EMPTY = "empty",
|
||||||
|
LENGTH_NOT_VALID = "length_not_valid",
|
||||||
|
STRENGTH_NOT_VALID = "strength_not_valid",
|
||||||
|
STRENGTH_VALID = "strength_valid",
|
||||||
|
}
|
||||||
|
|
||||||
return criteria.every((criterion) => criterion);
|
const PASSWORD_MIN_LENGTH = 8;
|
||||||
};
|
// const PASSWORD_NUMBER_REGEX = /\d/;
|
||||||
|
// const PASSWORD_CHAR_CAPS_REGEX = /[A-Z]/;
|
||||||
export const getPasswordStrength = (password: string) => {
|
// const PASSWORD_SPECIAL_CHAR_REGEX = /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/;
|
||||||
if (password.length === 0) return 0;
|
|
||||||
if (password.length < 8) return 1;
|
export const PASSWORD_CRITERIA = [
|
||||||
if (!isPasswordCriteriaMet(password)) return 2;
|
{
|
||||||
|
key: "min_8_char",
|
||||||
const result = zxcvbn(password);
|
label: "Min 8 characters",
|
||||||
return result.score;
|
isCriteriaValid: (password: string) => password.length >= PASSWORD_MIN_LENGTH,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// key: "min_1_upper_case",
|
||||||
|
// label: "Min 1 upper-case letter",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_NUMBER_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: "min_1_number",
|
||||||
|
// label: "Min 1 number",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_CHAR_CAPS_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: "min_1_special_char",
|
||||||
|
// label: "Min 1 special character",
|
||||||
|
// isCriteriaValid: (password: string) => PASSWORD_SPECIAL_CHAR_REGEX.test(password),
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => {
|
||||||
|
let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY;
|
||||||
|
|
||||||
|
if (!password || password === "" || password.length <= 0) {
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length >= PASSWORD_MIN_LENGTH) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
||||||
|
} else {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID;
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordCriteriaValidation = PASSWORD_CRITERIA.map((criteria) => criteria.isCriteriaValid(password)).every(
|
||||||
|
(criterion) => criterion
|
||||||
|
);
|
||||||
|
const passwordStrengthScore = zxcvbn(password).score;
|
||||||
|
|
||||||
|
if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) {
|
||||||
|
passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passwordStrength;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue