diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 990b03377..150b373a2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -87,6 +87,7 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", + "validator": "^13.11.0", "yaml": "^2.2.2", "yup": "^0.32.11" }, @@ -105,6 +106,7 @@ "@types/node": "^18.11.9", "@types/react": "^18.0.26", "@types/sanitize-html": "^2.9.0", + "@types/validator": "^13.11.1", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.7", @@ -8155,6 +8157,12 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, + "node_modules/@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -22658,6 +22666,14 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -28916,6 +28932,12 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, + "@types/validator": { + "version": "13.11.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.1.tgz", + "integrity": "sha512-d/MUkJYdOeKycmm75Arql4M5+UuXmf4cHdHKsyw1GcvnNgL6s77UkgSgJ8TE/rI5PYsnwYq5jkcWBLuN/MpQ1A==", + "dev": true + }, "@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -39687,6 +39709,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index fab8b7033..3104ff1a3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -95,6 +95,7 @@ "tweetnacl-util": "^0.15.1", "uuid": "^8.3.2", "uuidv4": "^6.2.13", + "validator": "^13.11.0", "yaml": "^2.2.2", "yup": "^0.32.11" }, @@ -113,6 +114,7 @@ "@types/node": "^18.11.9", "@types/react": "^18.0.26", "@types/sanitize-html": "^2.9.0", + "@types/validator": "^13.11.1", "@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/parser": "^5.45.0", "autoprefixer": "^10.4.7", diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index 0bf36622f..776810383 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -238,7 +238,8 @@ "validate-lowercase": "at least 1 lowercase character", "validate-number": "at least 1 number", "validate-special-char": "at least 1 special character", - "validate-repeated-char": "at most 2 repeated consecutive characters", + "validate-repeated-char": "at most 2 repeated, consecutive characters", + "validate-is-email": "The password cannot be an email address.", "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": { diff --git a/frontend/public/locales/es/translations.json b/frontend/public/locales/es/translations.json index 885f27688..8891efcbe 100644 --- a/frontend/public/locales/es/translations.json +++ b/frontend/public/locales/es/translations.json @@ -235,7 +235,8 @@ "validate-lowercase": "al menos 1 carácter en minúsculas", "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-repeated-char": "como máximo 2 caracteres repetidos y consecutivos", + "validate-is-email": "La contraseña no puede ser una dirección de correo electrónico.", "validate-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": { diff --git a/frontend/public/locales/fr/translations.json b/frontend/public/locales/fr/translations.json index f38171085..a8638dc3b 100644 --- a/frontend/public/locales/fr/translations.json +++ b/frontend/public/locales/fr/translations.json @@ -222,7 +222,8 @@ "validate-lowercase": "au moins 1 caractère majuscule", "validate-number": "au moins 1 chiffre", "validate-special-char": "au moins 1 caractère spécial", - "validate-repeated-char": "au plus 2 caractères consécutifs répétés", + "validate-repeated-char": "au plus 2 caractères répétés et consécutifs", + "validate-is-email": "Le mot de passe ne peut pas être une adresse e-mail.", "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": { diff --git a/frontend/public/locales/ko/translations.json b/frontend/public/locales/ko/translations.json index d869b0891..8f21f6349 100644 --- a/frontend/public/locales/ko/translations.json +++ b/frontend/public/locales/ko/translations.json @@ -189,7 +189,8 @@ "validate-lowercase": "최소 1개의 소문자", "validate-number": "숫자 1개 이상", "validate-special-char": "특수 문자 1개 이상", - "validate-repeated-char": "최대 2개의 반복되는 연속 문자", + "validate-repeated-char": "최대 2개의 반복된 연속 문자", + "validate-is-email": "비밀번호는 이메일 주소가 될 수 없습니다.", "validate-breached": "새 비밀번호는 다른 웹사이트에서 일반적으로 사용되는 비밀번호 목록에 있습니다. 더 강력한 비밀번호로 다시 시도해 주세요." }, "token": { diff --git a/frontend/public/locales/pt-BR/translations.json b/frontend/public/locales/pt-BR/translations.json index d5ed68fba..238cee3fa 100644 --- a/frontend/public/locales/pt-BR/translations.json +++ b/frontend/public/locales/pt-BR/translations.json @@ -217,7 +217,8 @@ "validate-lowercase": "pelo menos 1 caractere minúsculo", "validate-number": "pelo menos 1 número", "validate-special-char": "pelo menos 1 caractere especial", - "validate-repeated-char": "au plus 2 caractères consécutifs répétés", + "validate-repeated-char": "no máximo 2 caracteres repetidos e consecutivos", + "validate-is-email": "A senha não pode ser um endereço de e-mail.", "validate-breached": "A nova senha está em uma lista de senhas comumente usadas em outros sites. Tente novamente com uma senha mais forte." }, "token": { diff --git a/frontend/public/locales/tr/translations.json b/frontend/public/locales/tr/translations.json index cd494fe07..32c3d4635 100644 --- a/frontend/public/locales/tr/translations.json +++ b/frontend/public/locales/tr/translations.json @@ -235,7 +235,8 @@ "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-repeated-char": "en fazla 2 tekrarlanan, ardışık karakter", + "validate-is-email": "Şifre bir e-posta adresi olamaz.", "validate-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": { diff --git a/frontend/src/components/signup/UserInfoStep.tsx b/frontend/src/components/signup/UserInfoStep.tsx index 0cce660e0..907ce4c57 100644 --- a/frontend/src/components/signup/UserInfoStep.tsx +++ b/frontend/src/components/signup/UserInfoStep.tsx @@ -45,6 +45,7 @@ type Errors = { number?: string; specialChar?: string; repeatedChar?: string; + isEmail?: string; isBeachedPassword?: string; }; diff --git a/frontend/src/components/utilities/checks/PasswordCheck.ts b/frontend/src/components/utilities/checks/PasswordCheck.ts index b19b64007..7ab633c05 100644 --- a/frontend/src/components/utilities/checks/PasswordCheck.ts +++ b/frontend/src/components/utilities/checks/PasswordCheck.ts @@ -1,3 +1,4 @@ +import isEmail from "validator/lib/isEmail"; import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; /* eslint-disable no-param-reassign */ @@ -11,6 +12,7 @@ interface PasswordCheckProps { setPasswordErrorNumber: (value: boolean) => void; setPasswordErrorSpecialChar: (value: boolean) => void; setPasswordErrorRepeatedChar: (value: boolean) => void; + setPasswordErrorIsEmail: (value: boolean) => void; setPasswordErrorIsBreachedPassword: (value: boolean) => void; } @@ -26,6 +28,7 @@ const passwordCheck = async ({ setPasswordErrorNumber, setPasswordErrorSpecialChar, setPasswordErrorRepeatedChar, + setPasswordErrorIsEmail, setPasswordErrorIsBreachedPassword, errorCheck }: PasswordCheckProps) => { @@ -93,6 +96,14 @@ const passwordCheck = async ({ setPasswordErrorRepeatedChar(false); } + // isEmail + if (isEmail(password)) { + setPasswordErrorIsEmail(true); + errorCheck = true; + } else { + setPasswordErrorIsEmail(false); + } + // breachedPassword if (await checkIsPasswordBreached(password)) { setPasswordErrorIsBreachedPassword(true); diff --git a/frontend/src/components/utilities/checks/checkPassword.ts b/frontend/src/components/utilities/checks/checkPassword.ts index cf925b674..34c09ac9d 100644 --- a/frontend/src/components/utilities/checks/checkPassword.ts +++ b/frontend/src/components/utilities/checks/checkPassword.ts @@ -1,3 +1,4 @@ +import isEmail from "validator/lib/isEmail"; import { checkIsPasswordBreached } from "./checkIsPasswordBreached"; type Errors = { @@ -8,6 +9,7 @@ type Errors = { number?: string; specialChar?: string; repeatedChar?: string; + isEmail?: string; isBreachedPassword?: string; }; @@ -25,6 +27,7 @@ interface CheckPasswordParams { * - Contains at least 1 number (0-9) * - Contains at least 1 special character * - Does not contain 3 repeat, consecutive characters + * - Is not an email address * - Is not in a database of breached passwords * * The function returns whether or not the password [password] @@ -64,7 +67,7 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom // number if (!/[0-9]/.test(password)) { - errors.number = "at least 1 number (0-9)"; + errors.number = "at least 1 number"; } // specialChar @@ -76,7 +79,7 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom ) ) { errors.specialChar = - 'at least 1 special character from !@#$%^&*(),.?":{}|<> or many languages including Korean, Devanagari, Cyrillic, Turkish and emojis.'; + "at least 1 special character (emojis and many langauge scripts supported)"; } // repeatedChar @@ -87,7 +90,12 @@ const checkPassword = async ({ password, setErrors }: CheckPasswordParams): Prom password ) ) { - errors.repeatedChar = "At most 2 repeated, consecutive characters"; + errors.repeatedChar = "at most 2 repeated, consecutive characters"; + } + + // isEmail + if (isEmail(password)) { + errors.isEmail = "The password cannot be an email address"; } // breachedPassword diff --git a/frontend/src/pages/password-reset.tsx b/frontend/src/pages/password-reset.tsx index 4a58f09fb..c1ba52605 100644 --- a/frontend/src/pages/password-reset.tsx +++ b/frontend/src/pages/password-reset.tsx @@ -35,6 +35,7 @@ export default function PasswordReset() { const [passwordErrorNumber, setPasswordErrorNumber] = useState(false); const [passwordErrorSpecialChar, setPasswordErrorSpecialChar] = useState(false); const [passwordErrorRepeatedChar, setPasswordErrorRepeatedChar] = useState(false); + const [passwordErrorIsEmail, setPasswordErrorIsEmail] = useState(false); const [passwordErrorIsBreachedPassword, setPasswordErrorIsBreachedPassword] = useState(false); const router = useRouter(); @@ -79,6 +80,7 @@ export default function PasswordReset() { setPasswordErrorNumber, setPasswordErrorSpecialChar, setPasswordErrorRepeatedChar, + setPasswordErrorIsEmail, setPasswordErrorIsBreachedPassword, errorCheck: false }); @@ -240,6 +242,7 @@ export default function PasswordReset() { setPasswordErrorNumber, setPasswordErrorSpecialChar, setPasswordErrorRepeatedChar, + setPasswordErrorIsEmail, setPasswordErrorIsBreachedPassword, errorCheck: false }); @@ -255,6 +258,7 @@ export default function PasswordReset() { passwordErrorNumber && passwordErrorSpecialChar && passwordErrorRepeatedChar && + passwordErrorIsEmail && passwordErrorIsBreachedPassword } autoComplete="new-password" @@ -268,6 +272,7 @@ export default function PasswordReset() { passwordErrorNumber || passwordErrorSpecialChar || passwordErrorRepeatedChar || + passwordErrorIsEmail || passwordErrorIsBreachedPassword ? (
Password should contain:
@@ -335,7 +340,7 @@ export default function PasswordReset() { passwordErrorSpecialChar ? "text-gray-400" : "text-gray-600" } text-sm`} > - at least 1 special character + at least 1 special character (emojis and many langauge scripts supported)
@@ -349,7 +354,19 @@ export default function PasswordReset() { passwordErrorRepeatedChar ? "text-gray-400" : "text-gray-600" } text-sm`} > - at most 2 repeated characters + at most 2 repeated, consecutive characters +
+ +
+ {passwordErrorIsEmail ? ( + + ) : ( + + )} +
+ The password cannot be an email address.
@@ -363,8 +380,8 @@ export default function PasswordReset() { passwordErrorIsBreachedPassword ? "text-gray-400" : "text-gray-600" } text-sm`} > - The password you provided is in a list of passwords commonly used on other websites. - Please try again with a stronger password. + The new password is in a list of passwords commonly used on other websites. Please + try again with a stronger password.
diff --git a/frontend/src/pages/signupinvite.tsx b/frontend/src/pages/signupinvite.tsx index 7eb478352..081dfeadc 100644 --- a/frontend/src/pages/signupinvite.tsx +++ b/frontend/src/pages/signupinvite.tsx @@ -35,6 +35,7 @@ type Errors = { number?: string; specialChar?: string; repeatedChar?: string; + isEmail?: string; breachedPassword?: string; }; diff --git a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx index 680b0fdbc..a7611f8bf 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx @@ -20,6 +20,7 @@ type Errors = { number?: string; specialChar?: string; repeatedChar?: string; + isEmail?: string; isBreachedPassword?: string; }; diff --git a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx index 493d2b0cc..c159d7784 100644 --- a/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx +++ b/frontend/src/views/Signup/components/UserInfoSSOStep/UserInfoSSOStep.tsx @@ -40,6 +40,7 @@ type Errors = { number?: string; specialChar?: string; repeatedChar?: string; + isEmail?: string; isBeachedPassword?: string; };