fix: Strict password validation in login form

Strict password validation is for registration, change password,
reset password and similar forms. In login form, we don't want
to interrupt user with unnecessary validation as it's completely
irrelevant what they actually entered. The one thing it's about
is the correctness of the email and the password.

Disabling strict validation for password is a kind of security
through obscurity, which could filter some untargeted attempts
for trying passwords by bots. If the bot immediatelly receives
notes about the requirements, it has more imformation for better
attacks.
This commit is contained in:
Michal Šmahel committed 2025-06-09 21:54:47 +02:00
1 parent 3ac0eb2128
commit 74915f6840
10 files changed
+49 -35

No files matched your search

@@ -4,7 +4,7 @@ import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
import { PasswordEndAdornment } from "../../Inputs/TextInput/Adornments";
import TextInput from "../../Inputs/TextInput";
import { credentials } from "../../../Validation/validation";
import { newOrChangedCredentials } from "../../../Validation/validation";
import Alert from "../../Alert";
import { update } from "../../../Features/Auth/authSlice";
import { useDispatch, useSelector } from "react-redux";
@@ -61,7 +61,7 @@ const PasswordPanel = () => {
[name]: true,
};
const validation = credentials.validate(
const validation = newOrChangedCredentials.validate(
{ ...updatedData },
{ abortEarly: false, context: { password: updatedData.newPassword } }
);
@@ -79,7 +79,7 @@ const PasswordPanel = () => {
const handleSubmit = async (event) => {
event.preventDefault();
const { error } = credentials.validate(localData, {
const { error } = newOrChangedCredentials.validate(localData, {
abortEarly: false,
context: { password: localData.newPassword },
});
@@ -5,7 +5,7 @@ import { Box, Button, Divider, Stack, Typography } from "@mui/material";
import Avatar from "../../Avatar";
import TextInput from "../../Inputs/TextInput";
import ImageUpload from "../../Inputs/ImageUpload";
import { credentials } from "../../../Validation/validation";
import { newOrChangedCredentials } from "../../../Validation/validation";
import { useDispatch, useSelector } from "react-redux";
import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
@@ -58,7 +58,7 @@ const ProfilePanel = () => {
[name]: value,
}));
validateField({ [name]: value }, credentials, name);
validateField({ [name]: value }, newOrChangedCredentials, name);
};
// Validates input against provided schema and updates error state
@@ -4,7 +4,7 @@ import { Button, ButtonGroup, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import TextInput from "../../Inputs/TextInput";
import { credentials } from "../../../Validation/validation";
import { newOrChangedCredentials } from "../../../Validation/validation";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Select from "../../Inputs/Select";
@@ -115,7 +115,7 @@ const TeamPanel = () => {
email: newEmail,
}));
const validation = credentials.validate({ email: newEmail }, { abortEarly: false });
const validation = newOrChangedCredentials.validate({ email: newEmail }, { abortEarly: false });
setErrors((prev) => {
const updatedErrors = { ...prev };
@@ -142,7 +142,7 @@ const TeamPanel = () => {
if (!toInvite.role.includes("user") || !toInvite.role.includes("admin"))
setToInvite((prev) => ({ ...prev, role: ["user"] }));
const { error } = credentials.validate(
const { error } = newOrChangedCredentials.validate(
{ email: toInvite.email },
{
abortEarly: false,
+3 -3
View File
@@ -4,7 +4,7 @@ import { createToast } from "../../Utils/toastUtils";
import { useDispatch, useSelector } from "react-redux";
import { forgotPassword } from "../../Features/Auth/authSlice";
import { useEffect, useState } from "react";
import { credentials } from "../../Validation/validation";
import { newOrChangedCredentials } from "../../Validation/validation";
import { useNavigate } from "react-router-dom";
import TextInput from "../../Components/Inputs/TextInput";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
@@ -35,7 +35,7 @@ const ForgotPassword = () => {
const handleSubmit = async (event) => {
event.preventDefault();
const { error } = credentials.validate(form, { abortEarly: false });
const { error } = newOrChangedCredentials.validate(form, { abortEarly: false });
if (error) {
// validation errors
@@ -75,7 +75,7 @@ const ForgotPassword = () => {
const { value } = event.target;
setForm({ email: value });
const { error } = credentials.validate({ email: value }, { abortEarly: false });
const { error } = newOrChangedCredentials.validate({ email: value }, { abortEarly: false });
if (error) setErrors({ email: error.details[0].message });
else delete errors.email;
+4 -4
View File
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Box, Stack, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
import { credentials } from "../../../Validation/validation";
import { loginCredentials } from "../../../Validation/validation";
import { login } from "../../../Features/Auth/authSlice";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../../Utils/toastUtils";
@@ -59,7 +59,7 @@ const Login = () => {
[name]: lowerCasedValue,
}));
const { error } = credentials.validate(
const { error } = loginCredentials.validate(
{ [name]: lowerCasedValue },
{ abortEarly: false }
);
@@ -76,7 +76,7 @@ const Login = () => {
event.preventDefault();
if (step === 0) {
const { error } = credentials.validate(
const { error } = loginCredentials.validate(
{ email: form.email },
{ abortEarly: false }
);
@@ -91,7 +91,7 @@ const Login = () => {
setStep(1);
}
} else if (step === 1) {
const { error } = credentials.validate(form, { abortEarly: false });
const { error } = loginCredentials.validate(form, { abortEarly: false });
if (error) {
// validation errors
+3 -3
View File
@@ -8,7 +8,7 @@ import { StepOne } from "./StepOne";
import { StepTwo } from "./StepTwo";
import { StepThree } from "./StepThree";
import { networkService } from "../../../main";
import { credentials } from "../../../Validation/validation";
import { newOrChangedCredentials } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { register } from "../../../Features/Auth/authSlice";
import Background from "../../../assets/Images/background-grid.svg?react";
@@ -171,7 +171,7 @@ const Register = ({ isSuperAdmin }) => {
* @returns {Object | undefined} - Returns the validation error object if there are validation errors; otherwise, `undefined`.
*/
const validateForm = (data, options = {}) => {
const { error } = credentials.validate(data, {
const { error } = newOrChangedCredentials.validate(data, {
abortEarly: false,
...options,
});
@@ -271,7 +271,7 @@ const Register = ({ isSuperAdmin }) => {
[name]: lowerCasedValue,
}));
const { error } = credentials.validate(
const { error } = newOrChangedCredentials.validate(
{ [name]: lowerCasedValue },
{ abortEarly: false, context: { password: form.password } }
);
+2 -2
View File
@@ -6,7 +6,7 @@ import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button } from "@mui/material";
import { setNewPassword } from "../../Features/Auth/authSlice";
import { createToast } from "../../Utils/toastUtils";
import { credentials } from "../../Validation/validation";
import { newOrChangedCredentials } from "../../Validation/validation";
import Check from "../../Components/Check/Check";
import TextInput from "../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
@@ -34,7 +34,7 @@ const SetNewPassword = () => {
const handleSubmit = async (e) => {
e.preventDefault();
const { error } = credentials.validate(form, {
const { error } = newOrChangedCredentials.validate(form, {
abortEarly: false,
context: { password: form.password },
});
@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { credentials } from "../../../Validation/validation";
import { newOrChangedCredentials } from "../../../Validation/validation";
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors[field];
@@ -36,7 +36,7 @@ function useValidatePassword() {
validateValue.password = form.password;
}
const { error } = credentials.validate(validateValue, validateOptions);
const { error } = newOrChangedCredentials.validate(validateValue, validateOptions);
const errors = error?.details.map((error) => ({
path: error.path[0],
type: error.type,
+23 -5
View File
@@ -27,7 +27,7 @@ const lastnameSchema = joi
"Surname must contain only letters, spaces, apostrophes, or hyphens",
});
const passwordSchema = joi
const newPasswordSchema = joi
.string()
.trim()
.min(8)
@@ -64,7 +64,7 @@ const passwordSchema = joi
special: "Password must contain at least one special character",
});
const credentials = joi.object({
const newOrChangedCredentials = joi.object({
firstName: nameSchema,
lastName: lastnameSchema,
email: joi
@@ -76,8 +76,8 @@ const credentials = joi.object({
"string.empty": "authRegisterEmailRequired",
"string.email": "authRegisterEmailInvalid",
}),
password: passwordSchema,
newPassword: passwordSchema,
password: newPasswordSchema,
newPassword: newPasswordSchema,
confirm: joi
.string()
.trim()
@@ -97,6 +97,23 @@ const credentials = joi.object({
inviteToken: joi.string().allow(""),
});
const loginCredentials = joi.object({
email: joi
.string()
.trim()
.email({ tlds: { allow: false } })
.lowercase()
.messages({
"string.empty": "authRegisterEmailRequired",
"string.email": "authRegisterEmailInvalid",
}),
password: joi
.string()
.messages({
"string.empty": "Please enter your password",
}),
});
const monitorValidation = joi.object({
url: joi.when("type", {
is: "docker",
@@ -391,7 +408,8 @@ const infrastructureMonitorValidation = joi.object({
});
export {
credentials,
newOrChangedCredentials,
loginCredentials,
imageValidation,
monitorValidation,
settingsValidation,
+4 -8
View File
@@ -26,14 +26,10 @@ const loginValidation = joi.object({
.string()
.email()
.required()
.custom((value, helpers) => {
const lowercasedValue = value.toLowerCase();
if (value !== lowercasedValue) {
return helpers.message("Email must be in lowercase");
}
return lowercasedValue;
}),
password: joi.string().min(8).required().pattern(passwordPattern),
.lowercase(),
password: joi
.string()
.required(),
});
const nameValidation = joi
.string()