mirror of
https://github.com/makeplane/plane.git
synced 2026-01-21 05:39:50 -06:00
[WEB-1681] chore: handled password strength validation and improved the acceptable char (#4891)
* chore: handled password validation on onboarding screen * chore: updated is password focused
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
authErrorHandler,
|
||||
} from "@/helpers/authentication.helper";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||
// wrappers
|
||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||
// services
|
||||
@@ -83,7 +83,7 @@ export default function ResetPasswordPage() {
|
||||
const isButtonDisabled = useMemo(
|
||||
() =>
|
||||
!!resetFormData.password &&
|
||||
getPasswordStrength(resetFormData.password) >= 3 &&
|
||||
getPasswordStrength(resetFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
|
||||
resetFormData.password === resetFormData.confirm_password
|
||||
? false
|
||||
: true,
|
||||
@@ -187,7 +187,7 @@ export default function ResetPasswordPage() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isPasswordInputFocused && <PasswordStrengthMeter password={resetFormData.password} />}
|
||||
<PasswordStrengthMeter password={resetFormData.password} isFocused={isPasswordInputFocused} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Button, Input, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { PasswordStrengthMeter } from "@/components/account";
|
||||
// helpers
|
||||
import { EPageTypes } from "@/helpers/authentication.helper";
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
@@ -79,7 +79,7 @@ const SetPasswordPage = observer(() => {
|
||||
const isButtonDisabled = useMemo(
|
||||
() =>
|
||||
!!passwordFormData.password &&
|
||||
getPasswordStrength(passwordFormData.password) >= 3 &&
|
||||
getPasswordStrength(passwordFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
|
||||
passwordFormData.password === passwordFormData.confirm_password
|
||||
? false
|
||||
: true,
|
||||
@@ -181,7 +181,7 @@ const SetPasswordPage = observer(() => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isPasswordInputFocused && <PasswordStrengthMeter password={passwordFormData.password} />}
|
||||
<PasswordStrengthMeter password={passwordFormData.password} isFocused={isPasswordInputFocused} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
|
||||
@@ -13,7 +13,7 @@ import { PageHead } from "@/components/core";
|
||||
import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile";
|
||||
// helpers
|
||||
import { authErrorHandler } from "@/helpers/authentication.helper";
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
@@ -107,16 +107,17 @@ const SecurityPage = observer(() => {
|
||||
}, [currentUser, router]);
|
||||
|
||||
const isButtonDisabled =
|
||||
getPasswordStrength(password) < 3 ||
|
||||
getPasswordStrength(password) != E_PASSWORD_STRENGTH.STRENGTH_VALID ||
|
||||
oldPassword.trim() === "" ||
|
||||
password.trim() === "" ||
|
||||
confirmPassword.trim() === "" ||
|
||||
password !== confirmPassword ||
|
||||
password === oldPassword;
|
||||
|
||||
const passwordSupport = password.length > 0 && (getPasswordStrength(password) < 3 || isPasswordInputFocused) && (
|
||||
<PasswordStrengthMeter password={password} />
|
||||
);
|
||||
const passwordSupport = password.length > 0 &&
|
||||
getPasswordStrength(password) != E_PASSWORD_STRENGTH.STRENGTH_VALID && (
|
||||
<PasswordStrengthMeter password={password} isFocused={isPasswordInputFocused} />
|
||||
);
|
||||
|
||||
if (isPageLoading)
|
||||
return (
|
||||
@@ -261,4 +262,4 @@ const SecurityPage = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export default SecurityPage;
|
||||
export default SecurityPage;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
// helpers
|
||||
import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||
// hooks
|
||||
import { useEventTracker } from "@/hooks/store";
|
||||
// services
|
||||
@@ -96,8 +96,8 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
</div>
|
||||
) : (
|
||||
passwordFormData.password.length > 0 &&
|
||||
(getPasswordStrength(passwordFormData.password) < 3 || isPasswordInputFocused) && (
|
||||
<PasswordStrengthMeter password={passwordFormData.password} />
|
||||
getPasswordStrength(passwordFormData.password) != E_PASSWORD_STRENGTH.STRENGTH_VALID && (
|
||||
<PasswordStrengthMeter password={passwordFormData.password} isFocused={isPasswordInputFocused} />
|
||||
)
|
||||
);
|
||||
|
||||
@@ -137,7 +137,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
||||
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault(); // Prevent form from submitting by default
|
||||
if (getPasswordStrength(passwordFormData.password) >= 3) {
|
||||
if (getPasswordStrength(passwordFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID) {
|
||||
setIsSubmitting(true);
|
||||
captureEvent(mode === EAuthModes.SIGN_IN ? SIGN_IN_WITH_PASSWORD : SIGN_UP_WITH_PASSWORD);
|
||||
event.currentTarget.submit(); // Manually submit the form if the condition is met
|
||||
|
||||
@@ -1,67 +1,94 @@
|
||||
// icons
|
||||
import { CircleCheck } from "lucide-react";
|
||||
"use client";
|
||||
|
||||
import { FC, useMemo } from "react";
|
||||
// import { CircleCheck } from "lucide-react";
|
||||
// helpers
|
||||
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;
|
||||
isFocused?: boolean;
|
||||
};
|
||||
|
||||
export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
|
||||
const { password } = props;
|
||||
export const PasswordStrengthMeter: FC<TPasswordStrengthMeter> = (props) => {
|
||||
const { password, isFocused = false } = props;
|
||||
// derived values
|
||||
const strength = useMemo(() => getPasswordStrength(password), [password]);
|
||||
const strengthBars = useMemo(() => {
|
||||
switch (strength) {
|
||||
case E_PASSWORD_STRENGTH.EMPTY: {
|
||||
return {
|
||||
bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
|
||||
text: "Please enter your password.",
|
||||
textColor: "text-custom-text-100",
|
||||
};
|
||||
}
|
||||
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 strength = getPasswordStrength(password);
|
||||
let bars = [];
|
||||
let text = "";
|
||||
let textColor = "";
|
||||
|
||||
if (password.length === 0) {
|
||||
bars = [`bg-[#F0F0F3]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
||||
text = "Password requirements";
|
||||
} 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-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
|
||||
text = "Password is weak";
|
||||
textColor = `text-[#DC3E42]`;
|
||||
} else {
|
||||
bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`];
|
||||
text = "Password is strong";
|
||||
textColor = `text-[#3E9B4F]`;
|
||||
}
|
||||
|
||||
const criteria = [
|
||||
{ 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) },
|
||||
];
|
||||
const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
|
||||
|
||||
if (!isPasswordMeterVisible) return <></>;
|
||||
return (
|
||||
<div className="w-full p-1">
|
||||
<div className="flex w-full gap-1.5">
|
||||
{bars.map((color, index) => (
|
||||
<div key={index} className={cn("w-full h-1 rounded-full", color)} />
|
||||
))}
|
||||
<div className="w-full space-y-2 pt-2">
|
||||
<div className="space-y-1.5">
|
||||
<div className="relative flex items-center gap-2">
|
||||
{strengthBars?.bars.map((color, index) => (
|
||||
<div key={`${color}-${index}`} className={cn("w-full h-1 rounded-full", color)} />
|
||||
))}
|
||||
</div>
|
||||
<div className={cn(`text-xs font-medium text-custom-text-100`, strengthBars?.textColor)}>
|
||||
{strengthBars?.text}
|
||||
</div>
|
||||
</div>
|
||||
<p className={cn("text-xs font-medium py-1", textColor)}>{text}</p>
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-2">
|
||||
{criteria.map((criterion, index) => (
|
||||
|
||||
{/* <div className="relative flex flex-wrap gap-x-4 gap-y-2">
|
||||
{PASSWORD_CRITERIA.map((criteria) => (
|
||||
<div
|
||||
key={index}
|
||||
key={criteria.key}
|
||||
className={cn(
|
||||
"flex items-center gap-1 text-xs font-medium",
|
||||
criterion.isValid ? `text-[#3E9B4F]` : "text-custom-text-400"
|
||||
"relative flex items-center gap-1 text-xs",
|
||||
criteria.isCriteriaValid(password) ? `text-green-500/70` : "text-custom-text-300"
|
||||
)}
|
||||
>
|
||||
<CircleCheck width={14} height={14} />
|
||||
{criterion.label}
|
||||
{criteria.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding
|
||||
// constants
|
||||
import { USER_DETAILS, E_ONBOARDING_STEP_1, E_ONBOARDING_STEP_2 } from "@/constants/event-tracker";
|
||||
// helpers
|
||||
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||
import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useUser, useUserProfile } from "@/hooks/store";
|
||||
// services
|
||||
@@ -248,30 +248,30 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
// derived values
|
||||
const isPasswordAlreadySetup = !user?.is_password_autoset;
|
||||
const isSignUpUsingMagicCode = user?.last_login_medium === "magic-code";
|
||||
|
||||
const password = watch("password");
|
||||
const confirmPassword = watch("confirm_password");
|
||||
const isValidPassword = (password: string, confirmPassword?: string) =>
|
||||
getPasswordStrength(password) >= 3 && password === confirmPassword;
|
||||
const isValidPassword = useMemo(() => {
|
||||
const currentPassword = watch("password") || undefined;
|
||||
const currentConfirmPassword = watch("confirm_password") || undefined;
|
||||
if (currentPassword) {
|
||||
if (
|
||||
currentPassword === currentConfirmPassword &&
|
||||
getPasswordStrength(currentPassword) === E_PASSWORD_STRENGTH.STRENGTH_VALID
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}, [watch]);
|
||||
|
||||
// Check for all available fields validation and if password field is available, then checks for password validation (strength + confirmation).
|
||||
// Also handles the condition for optional password i.e if password field is optional it only checks for above validation if it's not empty.
|
||||
const isButtonDisabled = useMemo(
|
||||
() =>
|
||||
!isSubmitting &&
|
||||
isValid &&
|
||||
(isPasswordAlreadySetup
|
||||
? true
|
||||
: isSignUpUsingMagicCode
|
||||
? !!password && isValidPassword(password, confirmPassword)
|
||||
: !!password
|
||||
? isValidPassword(password, confirmPassword)
|
||||
: true)
|
||||
? false
|
||||
: true,
|
||||
[isSubmitting, isValid, isPasswordAlreadySetup, isSignUpUsingMagicCode, password, confirmPassword]
|
||||
() => (!isSubmitting && isValid && (isPasswordAlreadySetup ? true : isValidPassword) ? false : true),
|
||||
[isSubmitting, isValid, isPasswordAlreadySetup, isValidPassword]
|
||||
);
|
||||
|
||||
const isCurrentStepUserPersonalization = profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION;
|
||||
@@ -412,94 +412,98 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
|
||||
{errors.last_name && <span className="text-sm text-red-500">{errors.last_name.message}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* setting up password for the first time */}
|
||||
{!isPasswordAlreadySetup && (
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||
Set a password{" "}
|
||||
{!isSignUpUsingMagicCode && <span className="text-onboarding-text-400">(optional)</span>}
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="password"
|
||||
rules={{
|
||||
required: isSignUpUsingMagicCode ? "Password is required" : false,
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Input
|
||||
type={showPassword.password ? "text" : "password"}
|
||||
name="password"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.password)}
|
||||
placeholder="New password..."
|
||||
className="w-full border-[0.5px] border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
/>
|
||||
{showPassword.password ? (
|
||||
<EyeOff
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
|
||||
Set a password (optional)
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="password"
|
||||
rules={{
|
||||
required: false,
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Input
|
||||
type={showPassword.password ? "text" : "password"}
|
||||
name="password"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.password)}
|
||||
placeholder="New password..."
|
||||
className="w-full border-[0.5px] border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||
onFocus={() => setIsPasswordInputFocused(true)}
|
||||
onBlur={() => setIsPasswordInputFocused(false)}
|
||||
/>
|
||||
) : (
|
||||
<Eye
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
{showPassword.password ? (
|
||||
<EyeOff
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
/>
|
||||
) : (
|
||||
<Eye
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("password")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<PasswordStrengthMeter password={watch("password") ?? ""} isFocused={isPasswordInputFocused} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
Confirm password (optional)
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="confirm_password"
|
||||
rules={{
|
||||
required: watch("password") ? true : false,
|
||||
validate: (value) =>
|
||||
watch("password") ? (value === watch("password") ? true : "Passwords don't match") : true,
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Input
|
||||
type={showPassword.retypePassword ? "text" : "password"}
|
||||
name="confirm_password"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.confirm_password)}
|
||||
placeholder="Confirm password..."
|
||||
className="w-full border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showPassword.retypePassword ? (
|
||||
<EyeOff
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
/>
|
||||
) : (
|
||||
<Eye
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{errors.confirm_password && (
|
||||
<span className="text-sm text-red-500">{errors.confirm_password.message}</span>
|
||||
)}
|
||||
/>
|
||||
{isPasswordInputFocused && <PasswordStrengthMeter password={watch("password") ?? ""} />}
|
||||
{errors.password && <span className="text-sm text-red-500">{errors.password.message}</span>}
|
||||
</div>
|
||||
)}
|
||||
{!isPasswordAlreadySetup && (
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
|
||||
Confirm password
|
||||
</label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="confirm_password"
|
||||
rules={{
|
||||
validate: (value) => value === password || "Passwords don't match",
|
||||
}}
|
||||
render={({ field: { value, onChange, ref } }) => (
|
||||
<div className="relative flex items-center rounded-md">
|
||||
<Input
|
||||
type={showPassword.retypePassword ? "text" : "password"}
|
||||
name="confirm_password"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.password)}
|
||||
placeholder="Confirm password..."
|
||||
className="w-full border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||
/>
|
||||
{showPassword.retypePassword ? (
|
||||
<EyeOff
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
/>
|
||||
) : (
|
||||
<Eye
|
||||
className="absolute right-3 h-4 w-4 stroke-custom-text-400 hover:cursor-pointer"
|
||||
onClick={() => handleShowPassword("retypePassword")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{errors.confirm_password && (
|
||||
<span className="text-sm text-red-500">{errors.confirm_password.message}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* user role once the password is set */}
|
||||
{profileSetupStep !== EProfileSetupSteps.USER_DETAILS && (
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
|
||||
@@ -1,16 +1,67 @@
|
||||
import zxcvbn from "zxcvbn";
|
||||
|
||||
export const isPasswordCriteriaMet = (password: string) => {
|
||||
const criteria = [password.length >= 8, /[A-Z]/.test(password), /\d/.test(password), /[!@#$%^&*]/.test(password)];
|
||||
export enum E_PASSWORD_STRENGTH {
|
||||
EMPTY = "empty",
|
||||
LENGTH_NOT_VALID = "length_not_valid",
|
||||
STRENGTH_NOT_VALID = "strength_not_valid",
|
||||
STRENGTH_VALID = "strength_valid",
|
||||
}
|
||||
|
||||
return criteria.every((criterion) => criterion);
|
||||
};
|
||||
|
||||
export const getPasswordStrength = (password: string) => {
|
||||
if (password.length === 0) return 0;
|
||||
if (password.length < 8) return 1;
|
||||
if (!isPasswordCriteriaMet(password)) return 2;
|
||||
|
||||
const result = zxcvbn(password);
|
||||
return result.score;
|
||||
const PASSWORD_MIN_LENGTH = 8;
|
||||
// const PASSWORD_NUMBER_REGEX = /\d/;
|
||||
// const PASSWORD_CHAR_CAPS_REGEX = /[A-Z]/;
|
||||
// const PASSWORD_SPECIAL_CHAR_REGEX = /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/;
|
||||
|
||||
export const PASSWORD_CRITERIA = [
|
||||
{
|
||||
key: "min_8_char",
|
||||
label: "Min 8 characters",
|
||||
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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user