fixed more error handling for password checks & translations

This commit is contained in:
Joel Biddle
2023-08-22 22:42:02 +10:00
parent 00089a6bba
commit 1f60a3d73e
12 changed files with 294 additions and 163 deletions
+5 -2
View File
@@ -234,9 +234,12 @@
"validate-base": "Password should contain:", "validate-base": "Password should contain:",
"validate-too-short": "at least 14 characters", "validate-too-short": "at least 14 characters",
"validate-too-long": "at most 100 characters", "validate-too-long": "at most 100 characters",
"validate-uppercase": "at least 1 uppercase character",
"validate-lowercase": "at least 1 lowercase character",
"validate-number": "at least 1 number", "validate-number": "at least 1 number",
"validate-case": "at least 1 lowercase character", "validate-special-char": "at least 1 special character",
"validate-breached": "The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password." "validate-repeated-char": "at most 2 repeated consecutive characters",
"validate-is-breached": "The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password."
}, },
"token": { "token": {
"service-tokens": "Service Tokens", "service-tokens": "Service Tokens",
+7 -4
View File
@@ -229,11 +229,14 @@
"current-wrong": "La contraseña actual puede puede que sea incorrecta", "current-wrong": "La contraseña actual puede puede que sea incorrecta",
"new": "Nueva contraseña", "new": "Nueva contraseña",
"validate-base": "La contraseña debe contener:", "validate-base": "La contraseña debe contener:",
"validate-too-short": "como mínimo 14 caracteres", "validate-too-short": "al menos 14 caracteres",
"validate-too-long": "como máximo 100 caracteres", "validate-too-long": "como máximo 100 caracteres",
"validate-number": "como mínimo 1 número", "validate-uppercase": "al menos 1 carácter en mayúscula",
"validate-case": "como mínimo 1 letra en minúsculas", "validate-lowercase": "al menos 1 carácter en minúsculas",
"validate-breached": "La contraseña que proporcionó se encuentra en una lista de contraseñas comúnmente utilizadas en otros sitios web. Inténtelo de nuevo con una contraseña más segura." "validate-number": "al menos 1 número",
"validate-special-char": "al menos 1 carácter especial",
"validate-repeated-char": "au plus 2 caracteres consecutivos repetidos",
"validate-breached": "La contraseña que proporcionó se encuentra en una lista de contraseñas comúnmente utilizadas en otros sitios web. Vuelva a intentarlo con una contraseña más segura."
}, },
"token": { "token": {
"service-tokens": "Tokens de servicio", "service-tokens": "Tokens de servicio",
+5 -2
View File
@@ -218,9 +218,12 @@
"validate-base": "Le mot de passe doit contenir:", "validate-base": "Le mot de passe doit contenir:",
"validate-too-short": "au moins 14 caractères", "validate-too-short": "au moins 14 caractères",
"validate-too-long": "au maximum 100 caractères", "validate-too-long": "au maximum 100 caractères",
"validate-uppercase": "au moins 1 caractère miniscule",
"validate-lowercase": "au moins 1 caractère majuscule",
"validate-number": "au moins 1 chiffre", "validate-number": "au moins 1 chiffre",
"validate-case": "au moins 1 caractère miniscule", "validate-special-char": "au moins 1 caractère spécial",
"validate-breached": "Le mot de passe que vous avez fourni figure dans une liste de mots de passe couramment utilisés sur d'autres sites Web. Veuillez réessayer avec un mot de passe plus fort." "validate-repeated-char": "au plus 2 caractères consécutifs répétés",
"validate-is-breached": "Le mot de passe que vous avez fourni figure dans une liste de mots de passe couramment utilisés sur d'autres sites Web. Veuillez réessayer avec un mot de passe plus fort."
}, },
"token": { "token": {
"service-tokens": "Jetons de service", "service-tokens": "Jetons de service",
+7 -4
View File
@@ -183,10 +183,13 @@
"new": "새 비밀번호", "new": "새 비밀번호",
"current-wrong": "현재 비밀번호가 잘못되었어요", "current-wrong": "현재 비밀번호가 잘못되었어요",
"validate-base": "비밀번호는 다음 조건을 만족해야 합니다:", "validate-base": "비밀번호는 다음 조건을 만족해야 합니다:",
"validate-too-short": "14 글자 이상", "validate-too-short": "최소 14자",
"validate-too-long": "100 자 이하", "validate-too-long": "최대 100자",
"validate-number": "1개 이상의 숫자", "validate-uppercase": "최소 1개의 대문자",
"validate-case": "1개 이상의 소문자", "validate-lowercase": "최소 1개의 소문자",
"validate-number": "숫자 1개 이상",
"validate-special-char": "특수 문자 1개 이상",
"validate-repeated-char": "최대 2개의 반복되는 연속 문자",
"validate-breached": "귀하가 제공한 비밀번호는 다른 웹사이트에서 일반적으로 사용되는 비밀번호 목록에 포함되어 있습니다. 더 강력한 비밀번호로 다시 시도해 주세요." "validate-breached": "귀하가 제공한 비밀번호는 다른 웹사이트에서 일반적으로 사용되는 비밀번호 목록에 포함되어 있습니다. 더 강력한 비밀번호로 다시 시도해 주세요."
}, },
"token": { "token": {
@@ -213,8 +213,11 @@
"validate-base": "A senha deve conter:", "validate-base": "A senha deve conter:",
"validate-too-short": "pelo menos 14 caracteres", "validate-too-short": "pelo menos 14 caracteres",
"validate-too-long": "no máximo 100 caracteres", "validate-too-long": "no máximo 100 caracteres",
"validate-uppercase": "pelo menos 1 caractere maiúsculo",
"validate-lowercase": "pelo menos 1 caractere minúsculo",
"validate-number": "pelo menos 1 número", "validate-number": "pelo menos 1 número",
"validate-case": "pelo menos 1 caractere minúsculo", "validate-special-char": "pelo menos 1 caractere especial",
"validate-repeated-char": "au plus 2 caractères consécutifs répétés",
"validate-breached": "A senha que você forneceu está em uma lista de senhas comumente usadas em outros sites. Tente novamente com uma senha mais forte." "validate-breached": "A senha que você forneceu está em uma lista de senhas comumente usadas em outros sites. Tente novamente com uma senha mais forte."
}, },
"token": { "token": {
+5 -2
View File
@@ -231,8 +231,11 @@
"validate-base": "Şifre kısıtlamaları:", "validate-base": "Şifre kısıtlamaları:",
"validate-too-short": "en az 14 karakter", "validate-too-short": "en az 14 karakter",
"validate-too-long": "en fazla 100 karakter", "validate-too-long": "en fazla 100 karakter",
"validate-number": "en az 1 rakam", "validate-uppercase": "en az 1 büyük harf karakter",
"validate-case": "en az 1 küçük harf", "validate-lowercase": "en az 1 küçük harf karakter",
"validate-number": "en az 1 sayı",
"validate-special-char": "en az 1 özel karakter",
"validate-repeated-char": "veya artı 2 karakter ardışık tekrar",
"validate-breached": "Sağladığınız şifre, diğer web sitelerinde yaygın olarak kullanılan şifreler listesinde yer almaktadır. Lütfen daha güçlü bir şifre ile tekrar deneyiniz." "validate-breached": "Sağladığınız şifre, diğer web sitelerinde yaygın olarak kullanılan şifreler listesinde yer almaktadır. Lütfen daha güçlü bir şifre ile tekrar deneyiniz."
}, },
"token": { "token": {
+54 -39
View File
@@ -39,14 +39,15 @@ interface UserInfoStepProps {
} }
type Errors = { type Errors = {
tooShort?: string, tooShort?: string;
tooLong?: string, tooLong?: string;
upperCase?: string, upperCase?: string;
lowerCase?: string, lowerCase?: string;
number?: string, number?: string;
specialChar?: string, specialChar?: string;
repeatedChar?: string, repeatedChar?: string;
breachedPassword?: string isBeachedPassword?: string;
isCommonPassword?: string;
}; };
/** /**
@@ -73,7 +74,7 @@ export default function UserInfoStep({
setOrganizationName, setOrganizationName,
attributionSource, attributionSource,
setAttributionSource, setAttributionSource,
providerAuthToken, providerAuthToken
}: UserInfoStepProps): JSX.Element { }: UserInfoStepProps): JSX.Element {
const { data: commonPasswords } = useGetCommonPasswords(); const { data: commonPasswords } = useGetCommonPasswords();
const [nameError, setNameError] = useState(false); const [nameError, setNameError] = useState(false);
@@ -101,7 +102,7 @@ export default function UserInfoStep({
} else { } else {
setOrganizationNameError(false); setOrganizationNameError(false);
} }
errorCheck = await checkPassword({ errorCheck = await checkPassword({
password, password,
commonPasswords, commonPasswords,
@@ -176,7 +177,7 @@ export default function UserInfoStep({
salt: result.salt, salt: result.salt,
verifier: result.verifier, verifier: result.verifier,
organizationName, organizationName,
attributionSource, attributionSource
}); });
// unset signup JWT token and set JWT token // unset signup JWT token and set JWT token
@@ -193,7 +194,7 @@ export default function UserInfoStep({
}); });
const userOrgs = await fetchOrganizations(); const userOrgs = await fetchOrganizations();
const orgId = userOrgs[0]?._id; const orgId = userOrgs[0]?._id;
const project = await ProjectService.initProject({ const project = await ProjectService.initProject({
organizationId: orgId, organizationId: orgId,
@@ -217,13 +218,15 @@ export default function UserInfoStep({
}; };
return ( return (
<div className="h-full mx-auto mb-36 w-max rounded-xl md:px-8 md:mb-16"> <div className="mx-auto mb-36 h-full w-max rounded-xl md:mb-16 md:px-8">
<p className="mx-8 mb-6 flex justify-center text-xl font-bold text-medium md:mx-16 text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="text-medium mx-8 mb-6 flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-bold text-transparent md:mx-16">
{t("signup.step3-message")} {t("signup.step3-message")}
</p> </p>
<div className="h-full mx-auto mb-36 w-max rounded-xl py-6 md:px-8 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800"> <div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Your Name</p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Your Name
</p>
<Input <Input
placeholder="Jane Doe" placeholder="Jane Doe"
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
@@ -232,10 +235,16 @@ export default function UserInfoStep({
autoComplete="given-name" autoComplete="given-name"
className="h-12" className="h-12"
/> />
{nameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your name</p>} {nameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your name
</p>
)}
</div> </div>
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Organization Name
</p>
<Input <Input
placeholder="Infisical" placeholder="Infisical"
onChange={(e) => setOrganizationName(e.target.value)} onChange={(e) => setOrganizationName(e.target.value)}
@@ -243,10 +252,16 @@ export default function UserInfoStep({
isRequired isRequired
className="h-12" className="h-12"
/> />
{organizationNameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your organization name</p>} {organizationNameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your organization name
</p>
)}
</div> </div>
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Where did you hear about us? <span className="font-light">(optional)</span></p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Where did you hear about us? <span className="font-light">(optional)</span>
</p>
<Input <Input
placeholder="" placeholder=""
onChange={(e) => setAttributionSource(e.target.value)} onChange={(e) => setAttributionSource(e.target.value)}
@@ -254,7 +269,7 @@ export default function UserInfoStep({
className="h-12" className="h-12"
/> />
</div> </div>
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2"> <div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
<InputField <InputField
label={t("section.password.password")} label={t("section.password.password")}
onChangeHandler={(pass: string) => { onChangeHandler={(pass: string) => {
@@ -274,23 +289,20 @@ export default function UserInfoStep({
/> />
{Object.keys(errors).length > 0 && ( {Object.keys(errors).length > 0 && (
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2"> <div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
<div className="mb-2 text-sm text-gray-400">{t("section.password.validate-base")}</div> <div className="mb-2 text-sm text-gray-400">
{t("section.password.validate-base")}
</div>
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( return (
<div <div className="items-top ml-1 flex flex-row justify-start" key={key}>
className="ml-1 flex flex-row items-top justify-start"
key={key}
>
<div> <div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
className="text-md text-red ml-0.5 mr-2.5" className="text-md ml-0.5 mr-2.5 text-red"
/> />
</div> </div>
<p className="text-gray-400 text-sm"> <p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
{errors[key as keyof Errors]}
</p>
</div> </div>
); );
} }
@@ -300,18 +312,21 @@ export default function UserInfoStep({
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l py-1 text-lg w-full"> <div className="text-l w-full py-1 text-lg">
<Button <Button
type="submit" type="submit"
onClick={signupErrorCheck} onClick={signupErrorCheck}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-14' className="h-14"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
isLoading={isLoading} isLoading={isLoading}
> {String(t("signup.signup"))} </Button> >
{" "}
{String(t("signup.signup"))}{" "}
</Button>
</div> </div>
</div> </div>
</div> </div>
@@ -6,9 +6,12 @@ interface PasswordCheckProps {
errorCheck: boolean; errorCheck: boolean;
setPasswordErrorTooShort: (value: boolean) => void; setPasswordErrorTooShort: (value: boolean) => void;
setPasswordErrorTooLong: (value: boolean) => void; setPasswordErrorTooLong: (value: boolean) => void;
setPasswordErrorNumber: (value: boolean) => void; setPasswordErrorUpperCase: (value: boolean) => void;
setPasswordErrorLowerCase: (value: boolean) => void; setPasswordErrorLowerCase: (value: boolean) => void;
setPasswordErrorIsBreached: (value: boolean) => void; setPasswordErrorNumber: (value: boolean) => void;
setPasswordErrorSpecialChar: (value: boolean) => void;
setPasswordErrorRepeatedChar: (value: boolean) => void;
setPasswordErrorIsBreachedPassword: (value: boolean) => void;
} }
/** /**
@@ -18,11 +21,15 @@ const passwordCheck = async ({
password, password,
setPasswordErrorTooShort, setPasswordErrorTooShort,
setPasswordErrorTooLong, setPasswordErrorTooLong,
setPasswordErrorNumber, setPasswordErrorUpperCase,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
setPasswordErrorIsBreached, setPasswordErrorNumber,
setPasswordErrorSpecialChar,
setPasswordErrorRepeatedChar,
setPasswordErrorIsBreachedPassword,
errorCheck errorCheck
}: PasswordCheckProps) => { }: PasswordCheckProps) => {
// tooShort
if (!password || password.length < 14) { if (!password || password.length < 14) {
setPasswordErrorTooShort(true); setPasswordErrorTooShort(true);
errorCheck = true; errorCheck = true;
@@ -30,6 +37,7 @@ const passwordCheck = async ({
setPasswordErrorTooShort(false); setPasswordErrorTooShort(false);
} }
// tooLong
if (password.length > 100) { if (password.length > 100) {
setPasswordErrorTooLong(true); setPasswordErrorTooLong(true);
errorCheck = true; errorCheck = true;
@@ -37,51 +45,54 @@ const passwordCheck = async ({
setPasswordErrorTooLong(false); setPasswordErrorTooLong(false);
} }
if (!/\d/.test(password)) { // upperCase
if (!/[A-Z]/.test(password)) {
setPasswordErrorUpperCase(true);
errorCheck = true;
} else {
setPasswordErrorUpperCase(false);
}
// lowerCase
if (!/[a-z]/.test(password)) {
setPasswordErrorLowerCase(true);
errorCheck = true;
} else {
setPasswordErrorLowerCase(false);
}
// number
if (!/[0-9]/.test(password)) {
setPasswordErrorNumber(true); setPasswordErrorNumber(true);
errorCheck = true; errorCheck = true;
} else { } else {
setPasswordErrorNumber(false); setPasswordErrorNumber(false);
} }
if (!/[a-z]/.test(password)) { // specialChar
setPasswordErrorLowerCase(true); if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
setPasswordErrorSpecialChar(true);
errorCheck = true; errorCheck = true;
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
// console.log(111)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain repeating characters.");
// errorCheck = true;
// } else if (RegExp(`[${email}]`).test(password)) {
// console.log(222)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain your email.");
// errorCheck = true;
} else { } else {
setPasswordErrorLowerCase(false); setPasswordErrorSpecialChar(false);
} }
// repeatedChar
if (/([A-Za-z0-9])\1\1\1/.test(password)) {
setPasswordErrorRepeatedChar(true);
errorCheck = true;
} else {
setPasswordErrorRepeatedChar(false);
}
// breachedPassword
if (await checkIsPasswordBreached(password)) { if (await checkIsPasswordBreached(password)) {
setPasswordErrorIsBreached(true); setPasswordErrorIsBreachedPassword(true);
errorCheck = true; errorCheck = true;
} else { } else {
setPasswordErrorIsBreached(false); setPasswordErrorIsBreachedPassword(false);
} }
// if (!/[A-Z]/.test(password)) {
// setPasswordErrorUpperCase(true);
// errorCheck = true;
// } else {
// setPasswordErrorUpperCase(false);
// }
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
// setPasswordErrorSpecialChar(true);
// // "Please add at least 1 special character (*, !, #, %)."
// errorCheck = true;
// } else {
// setPasswordErrorSpecialChar(false);
// }
return errorCheck; return errorCheck;
}; };
@@ -8,8 +8,8 @@ type Errors = {
number?: string; number?: string;
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
commonPassword?: string; isBreachedPassword?: string;
breachedPassword?: string; isCommonPassword?: string;
}; };
interface CheckPasswordParams { interface CheckPasswordParams {
@@ -20,12 +20,15 @@ interface CheckPasswordParams {
/** /**
* Validate that the password [password]: * Validate that the password [password]:
* - Contains at least 14 characters long * - Contains at least 14 characters
* - Contains at most 100 characters long * - Contains at most 100 characters
* - Contains at least 1 uppercase character (A-Z) * - Contains at least 1 uppercase character (A-Z)
* - Contains at least 1 lowercase character (a-z) * - Contains at least 1 lowercase character (a-z)
* - Contains at least 1 number (0-9) * - Contains at least 1 number (0-9)
* - Contains at least 1 special character
* - Does not contain 3 repeat, consecutive characters * - Does not contain 3 repeat, consecutive characters
* - Is not in a database of breached passwords
* - Is not in a list of common passwords
* *
* The function returns whether or not the password [password] * The function returns whether or not the password [password]
* passes the minimum requirements above. It sets errors on * passes the minimum requirements above. It sets errors on
@@ -42,41 +45,50 @@ const checkPassword = async ({
}: CheckPasswordParams): Promise<boolean> => { }: CheckPasswordParams): Promise<boolean> => {
const errors: Errors = {}; const errors: Errors = {};
// tooShort
if (password.length < 14) { if (password.length < 14) {
errors.tooShort = "at least 14 characters"; errors.tooShort = "at least 14 characters";
} }
// toolong
if (password.length > 100) { if (password.length > 100) {
errors.tooLong = "at most 100 characters"; errors.tooLong = "at most 100 characters";
} }
// upperCase
if (!/[A-Z]/.test(password)) { if (!/[A-Z]/.test(password)) {
errors.upperCase = "at least 1 uppercase character (A-Z)"; errors.upperCase = "at least 1 uppercase character (A-Z)";
} }
// lowerCase
if (!/[a-z]/.test(password)) { if (!/[a-z]/.test(password)) {
errors.lowerCase = "at least 1 lowercase character (a-z)"; errors.lowerCase = "at least 1 lowercase character (a-z)";
} }
// number
if (!/[0-9]/.test(password)) { if (!/[0-9]/.test(password)) {
errors.number = "at least 1 number (0-9)"; errors.number = "at least 1 number (0-9)";
} }
// specialChar
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.specialChar = "at least 1 special character (!@#$%^&*(),.?)"; errors.specialChar = "at least 1 special character (!@#$%^&*(),.?)";
} }
// repeatedChar
if (/([A-Za-z0-9])\1\1\1/.test(password)) { if (/([A-Za-z0-9])\1\1\1/.test(password)) {
errors.repeatedChar = "No 3 repeat, consecutive characters"; errors.repeatedChar = "No 3 repeat, consecutive characters";
} }
// breachedPassword
if (await checkIsPasswordBreached(password)) { if (await checkIsPasswordBreached(password)) {
errors.breachedPassword = errors.isBreachedPassword =
"The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password."; "The password you provided is in a list of passwords commonly used on other websites. Please try again with a stronger password.";
} }
// commonPassword
if (commonPasswords.includes(password)) { if (commonPasswords.includes(password)) {
errors.commonPassword = "No common passwords"; errors.isCommonPassword = "No common passwords";
} }
setErrors(errors); setErrors(errors);
+73 -16
View File
@@ -30,9 +30,12 @@ export default function PasswordReset() {
const [backupKeyError, setBackupKeyError] = useState(false); const [backupKeyError, setBackupKeyError] = useState(false);
const [passwordErrorTooShort, setPasswordErrorTooShort] = useState(false); const [passwordErrorTooShort, setPasswordErrorTooShort] = useState(false);
const [passwordErrorTooLong, setPasswordErrorTooLong] = useState(false); const [passwordErrorTooLong, setPasswordErrorTooLong] = useState(false);
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false);
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false); const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
const [passwordErrorIsBreached, setPasswordErrorIsBreached] = useState(false); const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = useState(false);
const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false);
const [passwordErrorIsBreachedPassword, setPasswordErrorIsBreachedPassword] = useState(false);
const router = useRouter(); const router = useRouter();
@@ -43,7 +46,7 @@ export default function PasswordReset() {
const token = parsedUrl.token as string; const token = parsedUrl.token as string;
const email = (parsedUrl.to as string)?.replace(" ", "+").trim(); const email = (parsedUrl.to as string)?.replace(" ", "+").trim();
// Unencrypt the private key with a backup key // Decrypt the private key with a backup key
const getEncryptedKeyHandler = async (e: FormEvent<HTMLFormElement>) => { const getEncryptedKeyHandler = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
try { try {
@@ -71,9 +74,12 @@ export default function PasswordReset() {
password: newPassword, password: newPassword,
setPasswordErrorTooShort, setPasswordErrorTooShort,
setPasswordErrorTooLong, setPasswordErrorTooLong,
setPasswordErrorNumber, setPasswordErrorUpperCase,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
setPasswordErrorIsBreached, setPasswordErrorNumber,
setPasswordErrorSpecialChar,
setPasswordErrorRepeatedChar,
setPasswordErrorIsBreachedPassword,
errorCheck: false errorCheck: false
}); });
@@ -229,9 +235,12 @@ export default function PasswordReset() {
password, password,
setPasswordErrorTooShort, setPasswordErrorTooShort,
setPasswordErrorTooLong, setPasswordErrorTooLong,
setPasswordErrorNumber, setPasswordErrorUpperCase,
setPasswordErrorLowerCase, setPasswordErrorLowerCase,
setPasswordErrorIsBreached, setPasswordErrorNumber,
setPasswordErrorSpecialChar,
setPasswordErrorRepeatedChar,
setPasswordErrorIsBreachedPassword,
errorCheck: false errorCheck: false
}); });
}} }}
@@ -241,9 +250,12 @@ export default function PasswordReset() {
error={ error={
passwordErrorTooShort && passwordErrorTooShort &&
passwordErrorTooLong && passwordErrorTooLong &&
passwordErrorNumber && passwordErrorUpperCase &&
passwordErrorLowerCase && passwordErrorLowerCase &&
passwordErrorIsBreached passwordErrorNumber &&
passwordErrorSpecialChar &&
passwordErrorRepeatedChar &&
passwordErrorIsBreachedPassword
} }
autoComplete="new-password" autoComplete="new-password"
id="new-password" id="new-password"
@@ -251,9 +263,12 @@ export default function PasswordReset() {
</div> </div>
{passwordErrorTooShort || {passwordErrorTooShort ||
passwordErrorTooLong || passwordErrorTooLong ||
passwordErrorNumber || passwordErrorUpperCase ||
passwordErrorLowerCase || passwordErrorLowerCase ||
passwordErrorIsBreached ? ( passwordErrorNumber ||
passwordErrorSpecialChar ||
passwordErrorRepeatedChar ||
passwordErrorIsBreachedPassword ? (
<div className="mx-2 mt-3 mb-2 flex w-full max-w-md flex-col items-start rounded-md bg-white/5 px-2 py-2"> <div className="mx-2 mt-3 mb-2 flex w-full max-w-md flex-col items-start rounded-md bg-white/5 px-2 py-2">
<div className="mb-1 text-sm text-gray-400">Password should contain:</div> <div className="mb-1 text-sm text-gray-400">Password should contain:</div>
<div className="ml-1 flex flex-row items-center justify-start"> <div className="ml-1 flex flex-row items-center justify-start">
@@ -277,13 +292,15 @@ export default function PasswordReset() {
</div> </div>
</div> </div>
<div className="ml-1 flex flex-row items-center justify-start"> <div className="ml-1 flex flex-row items-center justify-start">
{passwordErrorNumber ? ( {passwordErrorUpperCase ? (
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" /> <FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
) : ( ) : (
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" /> <FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
)} )}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}> <div
at least 1 number className={`${passwordErrorUpperCase ? "text-gray-400" : "text-gray-600"} text-sm`}
>
at least 1 uppercase character
</div> </div>
</div> </div>
<div className="ml-1 flex flex-row items-center justify-start"> <div className="ml-1 flex flex-row items-center justify-start">
@@ -297,14 +314,54 @@ export default function PasswordReset() {
> >
at least 1 lowercase character at least 1 lowercase character
</div> </div>
</div>
<div className="ml-1 flex flex-row items-center justify-start">
{passwordErrorNumber ? (
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
) : (
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
)}
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>
at least 1 number
</div>
<div className="ml-1 flex flex-row items-center justify-start"> <div className="ml-1 flex flex-row items-center justify-start">
{passwordErrorIsBreached ? ( {passwordErrorSpecialChar ? (
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" /> <FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
) : ( ) : (
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" /> <FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
)} )}
<div <div
className={`${passwordErrorIsBreached ? "text-gray-400" : "text-gray-600"} text-sm`} className={`${
passwordErrorSpecialChar ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
at least 1 special character
</div>
</div>
<div className="ml-1 flex flex-row items-center justify-start">
{passwordErrorRepeatedChar ? (
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
) : (
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
)}
<div
className={`${
passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600"
} text-sm`}
>
at most 2 repeated characters
</div>
</div>
<div className="ml-1 flex flex-row items-center justify-start">
{passwordErrorIsBreachedPassword ? (
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
) : (
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
)}
<div
className={`${
passwordErrorIsBreachedPassword ? "text-gray-400" : "text-gray-600"
} text-sm`}
> >
The password you provided is in a list of passwords commonly used on other websites. The password you provided is in a list of passwords commonly used on other websites.
Please try again with a stronger password. Please try again with a stronger password.
@@ -21,8 +21,8 @@ type Errors = {
number?: string; number?: string;
specialChar?: string; specialChar?: string;
repeatedChar?: string; repeatedChar?: string;
commonPassword?: string; isBreachedPassword?: string;
breachedPassword?: string; isCommonPassword?: string;
}; };
const schema = yup const schema = yup
@@ -1,4 +1,3 @@
import crypto from "crypto"; import crypto from "crypto";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
@@ -25,24 +24,25 @@ import ProjectService from "@app/services/ProjectService";
const client = new jsrp.client(); const client = new jsrp.client();
type Props = { type Props = {
setStep: (step: number) => void; setStep: (step: number) => void;
email: string; email: string;
password: string; password: string;
setPassword: (value: string) => void; setPassword: (value: string) => void;
name: string; name: string;
providerOrganizationName: string; providerOrganizationName: string;
providerAuthToken?: string; providerAuthToken?: string;
} };
type Errors = { type Errors = {
tooShort?: string, tooShort?: string;
tooLong?: string, tooLong?: string;
upperCase?: string, upperCase?: string;
lowerCase?: string, lowerCase?: string;
number?: string, number?: string;
specialChar?: string, specialChar?: string;
repeatedChar?: string, repeatedChar?: string;
breachedPassword?: string isBeachedPassword?: string;
isCommonPassword?: string;
}; };
/** /**
@@ -65,7 +65,7 @@ export const UserInfoSSOStep = ({
password, password,
setPassword, setPassword,
setStep, setStep,
providerAuthToken, providerAuthToken
}: Props) => { }: Props) => {
const { data: commonPasswords } = useGetCommonPasswords(); const { data: commonPasswords } = useGetCommonPasswords();
const [nameError, setNameError] = useState(false); const [nameError, setNameError] = useState(false);
@@ -99,7 +99,7 @@ export const UserInfoSSOStep = ({
} else { } else {
setOrganizationNameError(false); setOrganizationNameError(false);
} }
errorCheck = await checkPassword({ errorCheck = await checkPassword({
password, password,
commonPasswords, commonPasswords,
@@ -212,15 +212,17 @@ export const UserInfoSSOStep = ({
setIsLoading(false); setIsLoading(false);
} }
}; };
return ( return (
<div className="h-full mx-auto mb-36 w-max rounded-xl md:px-8 md:mb-16"> <div className="mx-auto mb-36 h-full w-max rounded-xl md:mb-16 md:px-8">
<p className="mx-8 mb-6 flex justify-center text-xl font-bold text-medium md:mx-16 text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200"> <p className="text-medium mx-8 mb-6 flex justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-xl font-bold text-transparent md:mx-16">
{t("signup.step3-message")} {t("signup.step3-message")}
</p> </p>
<div className="h-full mx-auto mb-36 w-max rounded-xl py-6 md:px-8 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800"> <div className="mx-auto mb-36 h-full w-max rounded-xl py-6 md:mb-16 md:border md:border-mineshaft-600 md:bg-mineshaft-800 md:px-8">
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Your Name</p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Your Name
</p>
<Input <Input
placeholder="Jane Doe" placeholder="Jane Doe"
value={name} value={name}
@@ -229,11 +231,17 @@ export const UserInfoSSOStep = ({
autoComplete="given-name" autoComplete="given-name"
className="h-12" className="h-12"
/> />
{nameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your name</p>} {nameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your name
</p>
)}
</div> </div>
{providerOrganizationName === undefined && ( {providerOrganizationName === undefined && (
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Organization Name
</p>
<Input <Input
placeholder="Infisical" placeholder="Infisical"
value={organizationName} value={organizationName}
@@ -242,12 +250,18 @@ export const UserInfoSSOStep = ({
className="h-12" className="h-12"
disabled disabled
/> />
{organizationNameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your organization name</p>} {organizationNameError && (
<p className="mt-1 ml-1 w-full text-left text-xs text-red-600">
Please, specify your organization name
</p>
)}
</div> </div>
)} )}
{providerOrganizationName === undefined && ( {providerOrganizationName === undefined && (
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg"> <div className="relative z-0 flex w-1/4 w-full min-w-[20rem] flex-col items-center justify-end rounded-lg py-2 lg:w-1/6">
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Where did you hear about us? <span className="font-light">(optional)</span></p> <p className="mb-1 ml-1 w-full text-left text-sm font-medium text-bunker-300">
Where did you hear about us? <span className="font-light">(optional)</span>
</p>
<Input <Input
placeholder="" placeholder=""
onChange={(e) => setAttributionSource(e.target.value)} onChange={(e) => setAttributionSource(e.target.value)}
@@ -256,12 +270,12 @@ export const UserInfoSSOStep = ({
/> />
</div> </div>
)} )}
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2"> <div className="mt-2 flex max-h-60 w-1/4 w-full min-w-[20rem] flex-col items-center justify-center rounded-lg py-2 lg:w-1/6">
<InputField <InputField
label="Infisical Password" label="Infisical Password"
onChangeHandler={(pass: string) => { onChangeHandler={async (pass: string) => {
setPassword(pass); setPassword(pass);
checkPassword({ await checkPassword({
password: pass, password: pass,
commonPasswords, commonPasswords,
setErrors setErrors
@@ -274,26 +288,27 @@ export const UserInfoSSOStep = ({
autoComplete="new-password" autoComplete="new-password"
id="new-password" id="new-password"
/> />
<div className="mt-2 w-min min-w-[20rem] max-h-60 flex-col items-center justify-center rounded-md px-1.5 bg-mineshaft-500 text-mineshaft-300 text-xs p-1.5"><FontAwesomeIcon icon={faInfoCircle} className="mr-1.5" />Infisical Password is used as part of the encryption mechanism so that even the authentication provider is not able to access your secrets.</div> <div className="mt-2 max-h-60 w-min min-w-[20rem] flex-col items-center justify-center rounded-md bg-mineshaft-500 p-1.5 px-1.5 text-xs text-mineshaft-300">
<FontAwesomeIcon icon={faInfoCircle} className="mr-1.5" />
Infisical Password is used as part of the encryption mechanism so that even the
authentication provider is not able to access your secrets.
</div>
{Object.keys(errors).length > 0 && ( {Object.keys(errors).length > 0 && (
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2"> <div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
<div className="mb-2 text-sm text-gray-400">{t("section.password.validate-base")}</div> <div className="mb-2 text-sm text-gray-400">
{t("section.password.validate-base")}
</div>
{Object.keys(errors).map((key) => { {Object.keys(errors).map((key) => {
if (errors[key as keyof Errors]) { if (errors[key as keyof Errors]) {
return ( return (
<div <div className="items-top ml-1 flex flex-row justify-start" key={key}>
className="ml-1 flex flex-row items-top justify-start"
key={key}
>
<div> <div>
<FontAwesomeIcon <FontAwesomeIcon
icon={faXmark} icon={faXmark}
className="text-md text-red ml-0.5 mr-2.5" className="text-md ml-0.5 mr-2.5 text-red"
/> />
</div> </div>
<p className="text-gray-400 text-sm"> <p className="text-sm text-gray-400">{errors[key as keyof Errors]}</p>
{errors[key as keyof Errors]}
</p>
</div> </div>
); );
} }
@@ -303,21 +318,24 @@ export const UserInfoSSOStep = ({
</div> </div>
)} )}
</div> </div>
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left"> <div className="mx-auto mt-2 flex w-1/4 min-w-[20rem] max-w-xs flex-col items-center justify-center text-center text-sm md:max-w-md md:text-left lg:w-[19%]">
<div className="text-l py-1 text-lg w-full"> <div className="text-l w-full py-1 text-lg">
<Button <Button
type="submit" type="submit"
onClick={signupErrorCheck} onClick={signupErrorCheck}
size="sm" size="sm"
isFullWidth isFullWidth
className='h-12' className="h-12"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
isLoading={isLoading} isLoading={isLoading}
> {String(t("signup.signup"))} </Button> >
{" "}
{String(t("signup.signup"))}{" "}
</Button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
} };