mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Fixed form (error messages too long). Consolidated tests & errors. Moved regexes to another file. Added regex to check for PII & reject pwd if true. Confirmed hashing & encryption/decryption works with top 50 languages, emojis etc (screen videos & unit tests to come).
This commit is contained in:
@@ -232,16 +232,14 @@
|
|||||||
"current-wrong": "The current password may be wrong",
|
"current-wrong": "The current password may be wrong",
|
||||||
"new": "New password",
|
"new": "New password",
|
||||||
"validate-base": "Password should contain:",
|
"validate-base": "Password should contain:",
|
||||||
"validate-too-short": "at least 14 characters",
|
"validate-tooShort": "at least 14 characters",
|
||||||
"validate-too-long": "at most 100 characters",
|
"validate-tooLong": "at most 100 characters",
|
||||||
"validate-uppercase": "at least 1 uppercase character",
|
"validate-noLetterChar": "at least 1 letter character",
|
||||||
"validate-lowercase": "at least 1 lowercase character",
|
"validate-noNumOrSpecialChar": "at least 1 number or special character",
|
||||||
"validate-number": "at least 1 number",
|
"validate-repeatedChar": "at most 3 repeated, consecutive characters",
|
||||||
"validate-special-char": "at least 1 special character",
|
"validate-escapeChar": "No escape characters allowed.",
|
||||||
"validate-repeated-char": "at most 2 repeated, consecutive characters",
|
"validate-lowEntropy": "Password contains sensitive data.",
|
||||||
"validate-is-email": "The password cannot be an email address.",
|
"validate-breached": "Password was found in a data breach."
|
||||||
"validate-is-url": "The password cannot be a URL.",
|
|
||||||
"validate-is-breached": "The new password 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",
|
||||||
|
|||||||
@@ -229,16 +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": "al menos 14 caracteres",
|
"validate-tooShort": "al menos 14 caracteres",
|
||||||
"validate-too-long": "como máximo 100 caracteres",
|
"validate-tooLong": "como máximo 100 caracteres",
|
||||||
"validate-uppercase": "al menos 1 carácter en mayúscula",
|
"validate-noLetterChar": "al menos 1 carácter alfabético",
|
||||||
"validate-lowercase": "al menos 1 carácter en minúsculas",
|
"validate-noNumOrSpecialChar": "al menos 1 número o carácter especial",
|
||||||
"validate-number": "al menos 1 número",
|
"validate-repeatedChar": "como máximo 3 caracteres repetidos y consecutivos",
|
||||||
"validate-special-char": "al menos 1 carácter especial",
|
"validate-escapeChar": "No se permiten caracteres de escape.",
|
||||||
"validate-repeated-char": "como máximo 2 caracteres repetidos y consecutivos",
|
"validate-lowEntropy": "La contraseña contiene datos sensibles.",
|
||||||
"validate-is-email": "La contraseña no puede ser una dirección de correo electrónico.",
|
"validate-breached": "La contraseña se encontró en una violación de datos."
|
||||||
"validate-is-url": "La contraseña no puede ser una URL.",
|
|
||||||
"validate-is-breached": "La nueva contraseña 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",
|
||||||
|
|||||||
@@ -215,17 +215,15 @@
|
|||||||
"current": "Mot de passe actuel",
|
"current": "Mot de passe actuel",
|
||||||
"current-wrong": "Le mot de passe actuel peut être érroné",
|
"current-wrong": "Le mot de passe actuel peut être érroné",
|
||||||
"new": "Nouveau mot de passe",
|
"new": "Nouveau mot de passe",
|
||||||
"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-tooShort": "au moins 14 caractères",
|
||||||
"validate-too-long": "au maximum 100 caractères",
|
"validate-tooLong": "au plus 100 caractères",
|
||||||
"validate-uppercase": "au moins 1 caractère miniscule",
|
"validate-noLetterChar": "au moins 1 caractère alphabétique",
|
||||||
"validate-lowercase": "au moins 1 caractère majuscule",
|
"validate-noNumOrSpecialChar": "au moins 1 chiffre ou caractère spécial",
|
||||||
"validate-number": "au moins 1 chiffre",
|
"validate-repeatedChar": "au plus 3 caractères consécutifs répétés",
|
||||||
"validate-special-char": "au moins 1 caractère spécial",
|
"validate-escapeChar": "Aucun caractère d'échappement autorisé.",
|
||||||
"validate-repeated-char": "au plus 2 caractères répétés et consécutifs",
|
"validate-lowEntropy": "Le mot de passe contient des données sensibles.",
|
||||||
"validate-is-email": "Le mot de passe ne peut pas être une adresse e-mail.",
|
"validate-breached": "Le mot de passe a été trouvé dans une violation de données."
|
||||||
"validate-is-url": "Le mot de passe ne peut pas être une URL.",
|
|
||||||
"validate-is-breached": "Le nouveau mot de passe se trouve 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",
|
||||||
|
|||||||
@@ -182,17 +182,15 @@
|
|||||||
"current": "현재 비밀번호",
|
"current": "현재 비밀번호",
|
||||||
"new": "새 비밀번호",
|
"new": "새 비밀번호",
|
||||||
"current-wrong": "현재 비밀번호가 잘못되었어요",
|
"current-wrong": "현재 비밀번호가 잘못되었어요",
|
||||||
"validate-base": "비밀번호는 다음 조건을 만족해야 합니다:",
|
"validate-base": "비밀번호는 다음을 포함해야 합니다:",
|
||||||
"validate-too-short": "최소 14자",
|
"validate-tooShort": "최소 14자",
|
||||||
"validate-too-long": "최대 100자",
|
"validate-tooLong": "최대 100자",
|
||||||
"validate-uppercase": "최소 1개의 대문자",
|
"validate-noLetterChar": "최소 1개의 문자를 포함해야 합니다.",
|
||||||
"validate-lowercase": "최소 1개의 소문자",
|
"validate-noNumOrSpecialChar": "최소 1개의 숫자 또는 특수 문자를 포함해야 합니다.",
|
||||||
"validate-number": "숫자 1개 이상",
|
"validate-repeatedChar": "연속으로 최대 3개의 반복된 문자를 포함할 수 있습니다.",
|
||||||
"validate-special-char": "특수 문자 1개 이상",
|
"validate-escapeChar": "이스케이프 문자는 허용되지 않습니다.",
|
||||||
"validate-repeated-char": "최대 2개의 반복된 연속 문자",
|
"validate-lowEntropy": "비밀번호에 민감한 데이터가 포함되어 있습니다.",
|
||||||
"validate-is-email": "비밀번호는 이메일 주소가 될 수 없습니다.",
|
"validate-breached": "비밀번호가 데이터 유출에 포함되었습니다."
|
||||||
"validate-is-url": "비밀번호는 URL일 수 없습니다.",
|
|
||||||
"validate-is-breached": "새 비밀번호는 다른 웹사이트에서 일반적으로 사용되는 비밀번호 목록에 있습니다. 더 강력한 비밀번호로 다시 시도해 주세요."
|
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"add-dialog": {
|
"add-dialog": {
|
||||||
|
|||||||
@@ -211,16 +211,14 @@
|
|||||||
"current-wrong": "A senha atual pode estar errada",
|
"current-wrong": "A senha atual pode estar errada",
|
||||||
"new": "Nova Senha",
|
"new": "Nova Senha",
|
||||||
"validate-base": "A senha deve conter:",
|
"validate-base": "A senha deve conter:",
|
||||||
"validate-too-short": "pelo menos 14 caracteres",
|
"validate-tooShort": "pelo menos 14 caracteres",
|
||||||
"validate-too-long": "no máximo 100 caracteres",
|
"validate-tooLong": "no máximo 100 caracteres",
|
||||||
"validate-uppercase": "pelo menos 1 caractere maiúsculo",
|
"validate-noLetterChar": "pelo menos 1 caractere alfabético",
|
||||||
"validate-lowercase": "pelo menos 1 caractere minúsculo",
|
"validate-noNumOrSpecialChar": "pelo menos 1 número ou caractere especial",
|
||||||
"validate-number": "pelo menos 1 número",
|
"validate-repeatedChar": "no máximo 3 caracteres repetidos e consecutivos",
|
||||||
"validate-special-char": "pelo menos 1 caractere especial",
|
"validate-escapeChar": "Nenhum caractere de escape permitido.",
|
||||||
"validate-repeated-char": "no máximo 2 caracteres repetidos e consecutivos",
|
"validate-lowEntropy": "A senha contém dados sensíveis.",
|
||||||
"validate-is-email": "A senha não pode ser um endereço de e-mail.",
|
"validate-breached": "A senha foi encontrada em uma violação de dados."
|
||||||
"validate-is-url": "A senha não pode ser um URL.",
|
|
||||||
"validate-is-breached": "A nova senha está em uma lista de senhas comumente usadas em outros sites. Tente novamente com uma senha mais forte."
|
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"service-tokens": "Tokens de Serviço",
|
"service-tokens": "Tokens de Serviço",
|
||||||
|
|||||||
@@ -228,17 +228,15 @@
|
|||||||
"current": "Mevcut şifre",
|
"current": "Mevcut şifre",
|
||||||
"current-wrong": "Mevcut şifre yanlış olabilir",
|
"current-wrong": "Mevcut şifre yanlış olabilir",
|
||||||
"new": "Yeni şifre",
|
"new": "Yeni şifre",
|
||||||
"validate-base": "Şifre kısıtlamaları:",
|
"validate-base": "Parola içermelidir:",
|
||||||
"validate-too-short": "en az 14 karakter",
|
"validate-tooShort": "en az 14 karakter",
|
||||||
"validate-too-long": "en fazla 100 karakter",
|
"validate-tooLong": "en fazla 100 karakter",
|
||||||
"validate-uppercase": "en az 1 büyük harf karakter",
|
"validate-noLetterChar": "en az 1 harf karakteri",
|
||||||
"validate-lowercase": "en az 1 küçük harf karakter",
|
"validate-noNumOrSpecialChar": "en az 1 rakam veya özel karakter",
|
||||||
"validate-number": "en az 1 sayı",
|
"validate-repeatedChar": "en fazla 3 tekrarlanan, ardışık karakter",
|
||||||
"validate-special-char": "en az 1 özel karakter",
|
"validate-escapeChar": "Kaçış karakterlerine izin verilmez.",
|
||||||
"validate-repeated-char": "en fazla 2 tekrarlanan, ardışık karakter",
|
"validate-lowEntropy": "Parola hassas veriler içeriyor.",
|
||||||
"validate-is-email": "Şifre bir e-posta adresi olamaz.",
|
"validate-breached": "Parola veri ihlalinde bulundu."
|
||||||
"validate-is-url": "Şifre bir URL olamaz.",
|
|
||||||
"validate-is-breached": "Yeni şifre, diğer web sitelerinde yaygın olarak kullanılan şifrelerin listesinde yer almaktadır. Lütfen daha güçlü bir şifre ile tekrar deneyiniz."
|
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"service-tokens": "Servis Belirteçleri",
|
"service-tokens": "Servis Belirteçleri",
|
||||||
|
|||||||
@@ -40,14 +40,12 @@ interface UserInfoStepProps {
|
|||||||
type Errors = {
|
type Errors = {
|
||||||
tooShort?: string;
|
tooShort?: string;
|
||||||
tooLong?: string;
|
tooLong?: string;
|
||||||
upperCase?: string;
|
noLetterChar?: string;
|
||||||
lowerCase?: string;
|
noNumOrSpecialChar?: string;
|
||||||
number?: string;
|
|
||||||
specialChar?: string;
|
|
||||||
repeatedChar?: string;
|
repeatedChar?: string;
|
||||||
isEmail?: string;
|
escapeChar?: string;
|
||||||
isUrl?: string;
|
lowEntropy?: string;
|
||||||
isBeachedPassword?: string;
|
breached?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,129 +1,87 @@
|
|||||||
import {string} from "yup";
|
import { letterCharRegex, numAndSpecialCharRegex, repeatedCharRegex, escapeCharRegex, lowEntropyRegexes } from "./passwordRegexes";
|
||||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||||
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
interface PasswordCheckProps {
|
interface PasswordCheckProps {
|
||||||
password: string;
|
password: string;
|
||||||
errorCheck: boolean;
|
errorCheck: boolean;
|
||||||
setPasswordErrorTooShort: (value: boolean) => void;
|
setPasswordErrorTooShort: (value: boolean) => void;
|
||||||
setPasswordErrorTooLong: (value: boolean) => void;
|
setPasswordErrorTooLong: (value: boolean) => void;
|
||||||
setPasswordErrorUpperCase: (value: boolean) => void;
|
setPasswordErrorNoLetterChar: (value: boolean) => void;
|
||||||
setPasswordErrorLowerCase: (value: boolean) => void;
|
setPasswordErrorNoNumOrSpecialChar: (value: boolean) => void;
|
||||||
setPasswordErrorNumber: (value: boolean) => void;
|
|
||||||
setPasswordErrorSpecialChar: (value: boolean) => void;
|
|
||||||
setPasswordErrorRepeatedChar: (value: boolean) => void;
|
setPasswordErrorRepeatedChar: (value: boolean) => void;
|
||||||
setPasswordErrorIsEmail: (value: boolean) => void;
|
setPasswordErrorEscapeChar: (value: boolean) => void;
|
||||||
setPasswordErrorIsUrl: (value: boolean) => void;
|
setPasswordErrorLowEntropy: (value: boolean) => void;
|
||||||
setPasswordErrorIsBreachedPassword: (value: boolean) => void;
|
setPasswordErrorBreached: (value: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function checks a user password with respect to some criteria.
|
|
||||||
*/
|
|
||||||
const passwordCheck = async ({
|
const passwordCheck = async ({
|
||||||
password,
|
password,
|
||||||
setPasswordErrorTooShort,
|
setPasswordErrorTooShort,
|
||||||
setPasswordErrorTooLong,
|
setPasswordErrorTooLong,
|
||||||
setPasswordErrorUpperCase,
|
setPasswordErrorNoLetterChar,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorNoNumOrSpecialChar,
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorSpecialChar,
|
|
||||||
setPasswordErrorRepeatedChar,
|
setPasswordErrorRepeatedChar,
|
||||||
setPasswordErrorIsEmail,
|
setPasswordErrorEscapeChar,
|
||||||
setPasswordErrorIsUrl,
|
setPasswordErrorLowEntropy,
|
||||||
setPasswordErrorIsBreachedPassword,
|
setPasswordErrorBreached,
|
||||||
errorCheck
|
errorCheck
|
||||||
}: PasswordCheckProps) => {
|
}: PasswordCheckProps) => {
|
||||||
// tooShort
|
const tests = [
|
||||||
if (!password || password.length < 14) {
|
{
|
||||||
setPasswordErrorTooShort(true);
|
name: "tooShort",
|
||||||
|
validator: (pwd: string) => pwd.length >= 14,
|
||||||
|
setError: setPasswordErrorTooShort,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tooLong",
|
||||||
|
validator: (pwd: string) => pwd.length < 101,
|
||||||
|
setError: setPasswordErrorTooLong,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noLetterChar",
|
||||||
|
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||||
|
setError: setPasswordErrorNoLetterChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noNumOrSpecialChar",
|
||||||
|
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||||
|
setError: setPasswordErrorNoNumOrSpecialChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repeatedChar",
|
||||||
|
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||||
|
setError: setPasswordErrorRepeatedChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escapeChar",
|
||||||
|
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||||
|
setError: setPasswordErrorEscapeChar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lowEntropy",
|
||||||
|
validator: (pwd: string) => (
|
||||||
|
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||||
|
),
|
||||||
|
setError: setPasswordErrorLowEntropy,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const isBreached = await checkIsPasswordBreached(password);
|
||||||
|
|
||||||
|
if (isBreached) {
|
||||||
errorCheck = true;
|
errorCheck = true;
|
||||||
|
setPasswordErrorBreached(true);
|
||||||
} else {
|
} else {
|
||||||
setPasswordErrorTooShort(false);
|
setPasswordErrorBreached(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// tooLong
|
for (const test of tests) {
|
||||||
if (password.length > 100) {
|
if (!test.validator(password)) {
|
||||||
setPasswordErrorTooLong(true);
|
errorCheck = true;
|
||||||
errorCheck = true;
|
test.setError(true);
|
||||||
} else {
|
} else {
|
||||||
setPasswordErrorTooLong(false);
|
test.setError(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// upperCase
|
|
||||||
if (!/[A-Z\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE]/.test(password)) {
|
|
||||||
setPasswordErrorUpperCase(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorUpperCase(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowerCase
|
|
||||||
if (!/[a-z\u0061-\u007A\u00DF-\u00F6\u00F8-\u00FF]/.test(password)) {
|
|
||||||
setPasswordErrorLowerCase(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorLowerCase(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// number
|
|
||||||
if (!/[0-9]/.test(password)) {
|
|
||||||
setPasswordErrorNumber(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorNumber(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// specialChar
|
|
||||||
if (
|
|
||||||
!/[!@#$%^&*(),.?":{}|<>\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00D6\u00C7\u00FC\u00FB\u00F6\u00EB\u00E7\u00C7\p{Emoji}]/u.test(
|
|
||||||
password
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setPasswordErrorSpecialChar(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorSpecialChar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// repeatedChar
|
|
||||||
if (
|
|
||||||
/([!@#$%^&*(),.?":{}|<>0-9A-Za-z\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00D6\u00C7\u00FC\u00FB\u00F6\u00EB\u00E7\u00C7\u003a-\u003f\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\p{Emoji}])\1\1/.test(
|
|
||||||
password
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setPasswordErrorRepeatedChar(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorRepeatedChar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEmail
|
|
||||||
const emailSchema = string().email();
|
|
||||||
|
|
||||||
if (await emailSchema.isValid(password)) {
|
|
||||||
setPasswordErrorIsEmail(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorIsEmail(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// isUrl
|
|
||||||
const urlSchema = string().url();
|
|
||||||
|
|
||||||
if (await urlSchema.isValid(password)) {
|
|
||||||
setPasswordErrorIsUrl(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorIsUrl(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// breachedPassword
|
|
||||||
if (await checkIsPasswordBreached(password)) {
|
|
||||||
setPasswordErrorIsBreachedPassword(true);
|
|
||||||
errorCheck = true;
|
|
||||||
} else {
|
|
||||||
setPasswordErrorIsBreachedPassword(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorCheck;
|
return errorCheck;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import {string} from "yup"
|
import { letterCharRegex, numAndSpecialCharRegex, repeatedCharRegex, escapeCharRegex, lowEntropyRegexes } from "./passwordRegexes";
|
||||||
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
import { checkIsPasswordBreached } from "./checkIsPasswordBreached";
|
||||||
|
|
||||||
type Errors = {
|
type Errors = {
|
||||||
tooShort?: string;
|
tooShort?: string;
|
||||||
tooLong?: string;
|
tooLong?: string;
|
||||||
upperCase?: string;
|
noLetterChar?: string;
|
||||||
lowerCase?: string;
|
noNumOrSpecialChar?: string;
|
||||||
number?: string;
|
|
||||||
specialChar?: string;
|
|
||||||
repeatedChar?: string;
|
repeatedChar?: string;
|
||||||
isEmail?: string;
|
escapeChar?: string;
|
||||||
isUrl?: string;
|
lowEntropy?: string;
|
||||||
isBreachedPassword?: string;
|
breached?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface CheckPasswordParams {
|
interface CheckPasswordParams {
|
||||||
@@ -23,12 +21,11 @@ interface CheckPasswordParams {
|
|||||||
* Validate that the password [password]:
|
* Validate that the password [password]:
|
||||||
* - Contains at least 14 characters
|
* - Contains at least 14 characters
|
||||||
* - Contains at most 100 characters
|
* - Contains at most 100 characters
|
||||||
* - Contains at least 1 uppercase character (A-Z)
|
* - Contains at least 1 letter character (many languages supported) (case insensitive)
|
||||||
* - Contains at least 1 lowercase character (a-z)
|
* - Contains at least 1 number (0-9) or special character (emojis included)
|
||||||
* - 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 an email address
|
* - Does not contain any escape characters/sequences
|
||||||
|
* - Does not contain PII and/or low entropy data (eg. email address, URL, phone number, DoB, SSN, driver's license, passport)
|
||||||
* - Is not in a database of breached passwords
|
* - Is not in a database of breached passwords
|
||||||
*
|
*
|
||||||
* The function returns whether or not the password [password]
|
* The function returns whether or not the password [password]
|
||||||
@@ -39,82 +36,64 @@ interface CheckPasswordParams {
|
|||||||
* @param {String} obj.password - the password to check
|
* @param {String} obj.password - the password to check
|
||||||
* @param {Function} obj.setErrors - set state function to set error object
|
* @param {Function} obj.setErrors - set state function to set error object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Promise<boolean> => {
|
const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Promise<boolean> => {
|
||||||
const errors: Errors = {};
|
const errors: Errors = {};
|
||||||
|
|
||||||
// tooShort
|
const tests = [
|
||||||
if (password.length < 14) {
|
{
|
||||||
errors.tooShort = "at least 14 characters";
|
name: "tooShort",
|
||||||
|
validator: (pwd: string) => pwd.length >= 14,
|
||||||
|
errorText: "at least 14 characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tooLong",
|
||||||
|
validator: (pwd: string) => pwd.length < 101,
|
||||||
|
errorText: "at most 100 characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noLetterChar",
|
||||||
|
validator: (pwd: string) => letterCharRegex.test(pwd),
|
||||||
|
errorText: "at least 1 letter character",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "noNumOrSpecialChar",
|
||||||
|
validator: (pwd: string) => numAndSpecialCharRegex.test(pwd),
|
||||||
|
errorText: "at least 1 number or special character",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repeatedChar",
|
||||||
|
validator: (pwd: string) => !repeatedCharRegex.test(pwd),
|
||||||
|
errorText: "at most 3 repeated, consecutive characters",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escapeChar",
|
||||||
|
validator: (pwd: string) => !escapeCharRegex.test(pwd),
|
||||||
|
errorText: "No escape characters allowed.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lowEntropy",
|
||||||
|
validator: (pwd: string) => (
|
||||||
|
!lowEntropyRegexes.some(regex => regex.test(pwd))
|
||||||
|
),
|
||||||
|
errorText: "Password contains sensitive data.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const isBreached = await checkIsPasswordBreached(password);
|
||||||
|
|
||||||
|
if (isBreached) {
|
||||||
|
errors.breached = "Password was found in a data breach.";
|
||||||
}
|
}
|
||||||
|
|
||||||
// tooLong
|
for (const test of tests) {
|
||||||
if (password.length > 100) {
|
if (test.validator && !test.validator(password)) {
|
||||||
errors.tooLong = "at most 100 characters";
|
errors[test.name as keyof Errors] = test.errorText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// upperCase
|
|
||||||
// this adds support for the user to select an uppercase character from many major languages
|
|
||||||
// NB. ES2018 is required to run this
|
|
||||||
if (!/[A-Z\u0041-\u005A\u00C0-\u00D6\u00D8-\u00DE]/.test(password)) {
|
|
||||||
errors.upperCase = "at least 1 uppercase character"; // most major langauges supported
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowerCase
|
|
||||||
// this adds support for the user to select a lowercase character from many major languages
|
|
||||||
// NB. ES2018 is required to run this
|
|
||||||
if (!/[a-z\u0061-\u007A\u00DF-\u00F6\u00F8-\u00FF]/.test(password)) {
|
|
||||||
errors.lowerCase = "at least 1 lowercase character"; // most major langauges supported
|
|
||||||
}
|
|
||||||
|
|
||||||
// number
|
|
||||||
if (!/[0-9]/.test(password)) {
|
|
||||||
errors.number = "at least 1 number";
|
|
||||||
}
|
|
||||||
|
|
||||||
// specialChar
|
|
||||||
// this adds support for the user to select a special character from many major languages and emojis
|
|
||||||
// NB. ES2018 is required to run this
|
|
||||||
if (
|
|
||||||
!/[!@#$%^&*(),.?":{}|<>\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00D6\u00C7\u00FC\u00FB\u00F6\u00EB\u00E7\u00C7\p{Emoji}]/u.test(
|
|
||||||
password
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
errors.specialChar = "at least 1 special character (emojis, symbols & non-Latin languages)";
|
|
||||||
}
|
|
||||||
|
|
||||||
// repeatedChar
|
|
||||||
// this prevents the user from selecting repeated characters from many major languages, emojis as well as numbers and symbols
|
|
||||||
// NB. ES2018 is required to run this
|
|
||||||
if (
|
|
||||||
/([!@#$%^&*(),.?":{}|<>0-9A-Za-z\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00D6\u00C7\u00FC\u00FB\u00F6\u00EB\u00E7\u00C7\u003a-\u003f\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\p{Emoji}])\1\1/.test(
|
|
||||||
password
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
errors.repeatedChar = "at most 2 repeated, consecutive characters";
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEmail
|
|
||||||
const emailSchema = string().email();
|
|
||||||
|
|
||||||
if (await emailSchema.isValid(password)) {
|
|
||||||
errors.isEmail = "The password cannot be an email address";
|
|
||||||
}
|
|
||||||
|
|
||||||
// isUrl
|
|
||||||
const urlSchema = string().url();
|
|
||||||
|
|
||||||
if (await urlSchema.isValid(password)) {
|
|
||||||
errors.isUrl = "The password cannot be a URL";
|
|
||||||
}
|
|
||||||
|
|
||||||
// breachedPassword
|
|
||||||
if (await checkIsPasswordBreached(password)) {
|
|
||||||
errors.isBreachedPassword =
|
|
||||||
"The new password is in a list of passwords commonly used on other websites. Please try again with a stronger password.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(errors);
|
setErrors(errors);
|
||||||
return Object.keys(errors).length > 0;
|
return Object.keys(errors).length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default checkPassword;
|
export default checkPassword;
|
||||||
35
frontend/src/components/utilities/checks/passwordRegexes.ts
Normal file
35
frontend/src/components/utilities/checks/passwordRegexes.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// This regex covers letters (case insensitive) for the top 50 most spoken languages
|
||||||
|
export const letterCharRegex = /[A-Za-z\u00C0-\u00D6\u00D8-\u00DE\u00DF-\u00F6\u00F8-\u00FF\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u0600-\u06FF\u0400-\u04FF\u0500-\u052F\u2DE0-\u2DFF\uA640-\uA69F\u05B0-\u05FF\u0980-\u09FF\u1F00-\u1FFF\u0130\u015E\u011E\u00C7\u00FC\u00FB\u00EB\u00E7]/u;
|
||||||
|
|
||||||
|
// This regex covers digits, special characters, symbols, and emojis.
|
||||||
|
export const numAndSpecialCharRegex = /[\d!@#$%^&*(),.?":{}|<>]|[^\p{L}\p{N}\s]/gu;
|
||||||
|
|
||||||
|
// This regex covers 3 repeated consecutive chars (incl. spaces)
|
||||||
|
export const repeatedCharRegex = /(.)\1\1\1|\s{4,}/;
|
||||||
|
|
||||||
|
// This regex covers the escape sequences as a precaution
|
||||||
|
export const escapeCharRegex = /[\n\t\r\\]/;
|
||||||
|
|
||||||
|
// This regex covers some PII and/or low entropy data
|
||||||
|
export const lowEntropyRegexes = [
|
||||||
|
// Email address
|
||||||
|
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/,
|
||||||
|
|
||||||
|
// URL (incl. subdomains, paths, top-level domains & query params)
|
||||||
|
/^(?:(?:https?|ftp):\/\/)?(?:\w+\.)?[a-zA-Z0-9.-]+\.(?:com|org|net|edu)(?:\/\S*)?(?:\?\S*)?$/,
|
||||||
|
|
||||||
|
// Date in various formats
|
||||||
|
/(\b\d{1,4}[-\/.]?\d{1,2}[-\/.]?\d{1,4}\b)|(\b\d{1,4}[-\/.]?\w{3}[-\/.]?\d{1,4}\b)/,
|
||||||
|
|
||||||
|
// Phone numbers (generalized)
|
||||||
|
/(?:\+(?:[1-9]\d{0,2})\s?)?(?:\(\d{1,4}\)\s?)?(?:\d[-.\s]?){5,}\d/,
|
||||||
|
|
||||||
|
// Passport numbers (generalized)
|
||||||
|
/\b(?:[A-Z0-9]{6,9}|[A-Z0-9]{8,9}|[A-Z0-9]{9}|[A-Z0-9]{10,11})\b/,
|
||||||
|
|
||||||
|
// Driver's license numbers (generalized)
|
||||||
|
/\b(?:[A-Z0-9]{7,10}|[A-Z0-9]{10,11}|[A-Z0-9]{7,10})\b/,
|
||||||
|
|
||||||
|
// US social security number
|
||||||
|
/\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/,
|
||||||
|
];
|
||||||
@@ -30,14 +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 [passwordErrorUpperCase, setPasswordErrorUpperCase] = useState(false);
|
const [passwordErrorNoLetterChar, setPasswordErrorNoLetterChar] = useState(false);
|
||||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
const [passwordErrorNoNumOrSpecialChar, setPasswordErrorNoNumOrSpecialChar] = useState(false);
|
||||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
|
||||||
const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = useState(false);
|
|
||||||
const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false);
|
const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false);
|
||||||
const [passwordErrorIsEmail, setPasswordErrorIsEmail] = useState(false);
|
const [passwordErrorEscapeChar, setPasswordErrorEscapeChar] = useState(false);
|
||||||
const [passwordErrorIsUrl, setPasswordErrorIsUrl] = useState(false);
|
const [passwordErrorLowEntropy, setPasswordErrorLowEntropy] = useState(false);
|
||||||
const [passwordErrorIsBreachedPassword, setPasswordErrorIsBreachedPassword] = useState(false);
|
const [passwordErrorBreached, setPasswordErrorBreached] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -76,14 +74,12 @@ export default function PasswordReset() {
|
|||||||
password: newPassword,
|
password: newPassword,
|
||||||
setPasswordErrorTooShort,
|
setPasswordErrorTooShort,
|
||||||
setPasswordErrorTooLong,
|
setPasswordErrorTooLong,
|
||||||
setPasswordErrorUpperCase,
|
setPasswordErrorNoLetterChar,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorNoNumOrSpecialChar,
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorSpecialChar,
|
|
||||||
setPasswordErrorRepeatedChar,
|
setPasswordErrorRepeatedChar,
|
||||||
setPasswordErrorIsEmail,
|
setPasswordErrorEscapeChar,
|
||||||
setPasswordErrorIsUrl,
|
setPasswordErrorLowEntropy,
|
||||||
setPasswordErrorIsBreachedPassword,
|
setPasswordErrorBreached,
|
||||||
errorCheck: false
|
errorCheck: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,7 +223,7 @@ export default function PasswordReset() {
|
|||||||
</p>
|
</p>
|
||||||
<div className="mt-1 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
<div className="mt-1 flex flex-row items-center justify-center md:mx-2 md:pb-4">
|
||||||
<p className="flex w-max max-w-md justify-center text-sm text-gray-400">
|
<p className="flex w-max max-w-md justify-center text-sm text-gray-400">
|
||||||
Make sure you save it somewhere save.
|
Make sure you save it somewhere safe.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
<div className="mt-4 flex max-h-24 w-full items-center justify-center rounded-lg md:mt-0 md:max-h-28 md:p-2">
|
||||||
@@ -239,14 +235,12 @@ export default function PasswordReset() {
|
|||||||
password,
|
password,
|
||||||
setPasswordErrorTooShort,
|
setPasswordErrorTooShort,
|
||||||
setPasswordErrorTooLong,
|
setPasswordErrorTooLong,
|
||||||
setPasswordErrorUpperCase,
|
setPasswordErrorNoLetterChar,
|
||||||
setPasswordErrorLowerCase,
|
setPasswordErrorNoNumOrSpecialChar,
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorSpecialChar,
|
|
||||||
setPasswordErrorRepeatedChar,
|
setPasswordErrorRepeatedChar,
|
||||||
setPasswordErrorIsEmail,
|
setPasswordErrorEscapeChar,
|
||||||
setPasswordErrorIsUrl,
|
setPasswordErrorLowEntropy,
|
||||||
setPasswordErrorIsBreachedPassword,
|
setPasswordErrorBreached,
|
||||||
errorCheck: false
|
errorCheck: false
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -256,14 +250,12 @@ export default function PasswordReset() {
|
|||||||
error={
|
error={
|
||||||
passwordErrorTooShort &&
|
passwordErrorTooShort &&
|
||||||
passwordErrorTooLong &&
|
passwordErrorTooLong &&
|
||||||
passwordErrorUpperCase &&
|
passwordErrorNoLetterChar &&
|
||||||
passwordErrorLowerCase &&
|
passwordErrorNoNumOrSpecialChar &&
|
||||||
passwordErrorNumber &&
|
|
||||||
passwordErrorSpecialChar &&
|
|
||||||
passwordErrorRepeatedChar &&
|
passwordErrorRepeatedChar &&
|
||||||
passwordErrorIsEmail &&
|
passwordErrorEscapeChar &&
|
||||||
passwordErrorIsUrl &&
|
passwordErrorLowEntropy &&
|
||||||
passwordErrorIsBreachedPassword
|
passwordErrorBreached
|
||||||
}
|
}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="new-password"
|
id="new-password"
|
||||||
@@ -271,14 +263,12 @@ export default function PasswordReset() {
|
|||||||
</div>
|
</div>
|
||||||
{passwordErrorTooShort ||
|
{passwordErrorTooShort ||
|
||||||
passwordErrorTooLong ||
|
passwordErrorTooLong ||
|
||||||
passwordErrorUpperCase ||
|
passwordErrorNoLetterChar ||
|
||||||
passwordErrorLowerCase ||
|
passwordErrorNoNumOrSpecialChar ||
|
||||||
passwordErrorNumber ||
|
|
||||||
passwordErrorSpecialChar ||
|
|
||||||
passwordErrorRepeatedChar ||
|
passwordErrorRepeatedChar ||
|
||||||
passwordErrorIsEmail ||
|
passwordErrorEscapeChar ||
|
||||||
passwordErrorIsUrl ||
|
passwordErrorLowEntropy ||
|
||||||
passwordErrorIsBreachedPassword ? (
|
passwordErrorBreached ? (
|
||||||
<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">
|
||||||
@@ -302,53 +292,30 @@ 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">
|
||||||
{passwordErrorUpperCase ? (
|
{passwordErrorNoLetterChar ? (
|
||||||
<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={`${passwordErrorUpperCase ? "text-gray-400" : "text-gray-600"} text-sm`}
|
className={`${passwordErrorNoLetterChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
>
|
>
|
||||||
at least 1 uppercase character
|
at least 1 letter 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">
|
||||||
{passwordErrorLowerCase ? (
|
{passwordErrorNoNumOrSpecialChar ? (
|
||||||
<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={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}
|
className={`${passwordErrorNoNumOrSpecialChar ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
>
|
>
|
||||||
at least 1 lowercase character
|
at least 1 number or special 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">
|
||||||
{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">
|
|
||||||
{passwordErrorSpecialChar ? (
|
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
passwordErrorSpecialChar ? "text-gray-400" : "text-gray-600"
|
|
||||||
} text-sm`}
|
|
||||||
>
|
|
||||||
at least 1 special character (emojis and many langauge scripts supported)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
|
||||||
{passwordErrorRepeatedChar ? (
|
{passwordErrorRepeatedChar ? (
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
) : (
|
) : (
|
||||||
@@ -359,48 +326,48 @@ export default function PasswordReset() {
|
|||||||
passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600"
|
passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600"
|
||||||
} text-sm`}
|
} text-sm`}
|
||||||
>
|
>
|
||||||
at most 2 repeated, consecutive characters
|
at most 3 repeated, consecutive characters
|
||||||
</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">
|
||||||
{passwordErrorIsEmail ? (
|
{passwordErrorEscapeChar ? (
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`${passwordErrorIsEmail ? "text-gray-400" : "text-gray-600"} text-sm`}
|
|
||||||
>
|
|
||||||
The password cannot be an email address.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
|
||||||
{passwordErrorIsUrl ? (
|
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`${passwordErrorIsUrl ? "text-gray-400" : "text-gray-600"} text-sm`}
|
|
||||||
>
|
|
||||||
The password cannot be a URL.
|
|
||||||
</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={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={`${
|
className={`${
|
||||||
passwordErrorIsBreachedPassword ? "text-gray-400" : "text-gray-600"
|
passwordErrorEscapeChar ? "text-gray-400" : "text-gray-600"
|
||||||
} text-sm`}
|
} text-sm`}
|
||||||
>
|
>
|
||||||
The new password is in a list of passwords commonly used on other websites. Please
|
No escape characters allowed.
|
||||||
try again with a stronger password.
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorLowEntropy ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${passwordErrorLowEntropy ? "text-gray-400" : "text-gray-600"} text-sm`}
|
||||||
|
>
|
||||||
|
Password contains sensitive data.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-1 flex flex-row items-center justify-start">
|
||||||
|
{passwordErrorBreached ? (
|
||||||
|
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
||||||
|
) : (
|
||||||
|
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${
|
||||||
|
passwordErrorBreached ? "text-gray-400" : "text-gray-600"
|
||||||
|
} text-sm`}
|
||||||
|
>
|
||||||
|
Password was found in a data breach.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -31,14 +31,12 @@ const client = new jsrp.client();
|
|||||||
type Errors = {
|
type Errors = {
|
||||||
tooShort?: string;
|
tooShort?: string;
|
||||||
tooLong?: string;
|
tooLong?: string;
|
||||||
upperCase?: string;
|
noLetterChar?: string;
|
||||||
lowerCase?: string;
|
noNumOrSpecialChar?: string;
|
||||||
number?: string;
|
|
||||||
specialChar?: string;
|
|
||||||
repeatedChar?: string;
|
repeatedChar?: string;
|
||||||
isEmail?: string;
|
escapeChar?: string;
|
||||||
isUrl?: string;
|
lowEntropy?: string;
|
||||||
breachedPassword?: string;
|
breached?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function SignupInvite() {
|
export default function SignupInvite() {
|
||||||
|
|||||||
@@ -15,14 +15,12 @@ import { useUser } from "@app/context";
|
|||||||
type Errors = {
|
type Errors = {
|
||||||
tooShort?: string;
|
tooShort?: string;
|
||||||
tooLong?: string;
|
tooLong?: string;
|
||||||
upperCase?: string;
|
noLetterChar?: string;
|
||||||
lowerCase?: string;
|
noNumOrSpecialChar?: string;
|
||||||
number?: string;
|
|
||||||
specialChar?: string;
|
|
||||||
repeatedChar?: string;
|
repeatedChar?: string;
|
||||||
isEmail?: string;
|
escapeChar?: string;
|
||||||
isUrl?: string;
|
lowEntropy?: string;
|
||||||
isBreachedPassword?: string;
|
breached?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const schema = yup
|
const schema = yup
|
||||||
|
|||||||
@@ -35,14 +35,12 @@ type Props = {
|
|||||||
type Errors = {
|
type Errors = {
|
||||||
tooShort?: string;
|
tooShort?: string;
|
||||||
tooLong?: string;
|
tooLong?: string;
|
||||||
upperCase?: string;
|
noLetterChar?: string;
|
||||||
lowerCase?: string;
|
noNumOrSpecialChar?: string;
|
||||||
number?: string;
|
|
||||||
specialChar?: string;
|
|
||||||
repeatedChar?: string;
|
repeatedChar?: string;
|
||||||
isEmail?: string;
|
escapeChar?: string;
|
||||||
isUrl?: string;
|
lowEntropy?: string;
|
||||||
isBeachedPassword?: string;
|
breached?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user