Skip to content

Commit

Permalink
refactor(ui): polish validatePassword and add minor optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Mar 14, 2024
1 parent d380bf7 commit c59d810
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 59 deletions.
15 changes: 7 additions & 8 deletions webapp/public/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"global.group": "Group",
"global.variants": "Variants management",
"global.password": "Password",
"global.confirmPassword": "Confirm password",
"global.create": "Create",
"global.open": "Open",
"global.name": "Name",
Expand Down Expand Up @@ -118,14 +117,13 @@
"form.field.minValue": "The minimum value is {{0}}",
"form.field.maxValue": "The maximum value is {{0}}",
"form.field.notAllowedValue": "Not allowed value",
"form.field.allowedChars": "Special characters allowed: {{0}}",
"form.field.specialChars": "Special characters allowed: {{0}}",
"form.field.specialCharsNotAllowed": "Special characters are not allowed",
"form.field.spacesNotAllowed": "Spaces are not allowed",
"form.field.requireLowercase": "Password must contain at least one lowercase letter.",
"form.field.requireUppercase": "Password must contain at least one uppercase letter.",
"form.field.requireDigit": "Password must contain at least one digit.",
"form.field.requireSpecialChars": "Password must contain at least one special character.",
"form.field.requireMinimumLength": "Password must be at least 8 characters long.",
"form.field.requireLowercase": "Must contain at least one lowercase letter.",
"form.field.requireUppercase": "Must contain at least one uppercase letter.",
"form.field.requireDigit": "Must contain at least one digit.",
"form.field.requireSpecialChars": "Must contain at least one special character.",
"matrix.graphSelector": "Columns",
"matrix.message.importHint": "Click or drag and drop a matrix here",
"matrix.importNewMatrix": "Import a new matrix",
Expand Down Expand Up @@ -194,7 +192,8 @@
"settings.error.groupRolesSave": "Role(s) for group '{{0}}' not saved",
"settings.error.tokenSave": "'{{0}}' token not saved",
"settings.error.updateMaintenance": "Maintenance mode not updated",
"settings.error.passwordMismatch": "Passwords do not match",
"settings.user.form.confirmPassword":"Confirm password",
"settings.user.form.error.passwordMismatch": "Passwords do not match",
"launcher.additionalModes": "Additional modes",
"launcher.autoUnzip": "Automatically unzip",
"launcher.xpress": "Xpress (>= 8.3)",
Expand Down
15 changes: 7 additions & 8 deletions webapp/public/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"global.group": "Groupe",
"global.variants": "Gestion des variantes",
"global.password": "Mot de passe",
"global.confirmPassword": "Confirmer le mot de passe",
"global.create": "Créer",
"global.open": "Ouvrir",
"global.name": "Nom",
Expand Down Expand Up @@ -118,14 +117,13 @@
"form.field.minValue": "La valeur minimum est {{0}}",
"form.field.maxValue": "La valeur maximum est {{0}}",
"form.field.notAllowedValue": "Valeur non autorisée",
"form.field.allowedChars": "Caractères spéciaux autorisés: {{0}}",
"form.field.specialChars": "Caractères spéciaux autorisés: {{0}}",
"form.field.specialCharsNotAllowed": "Les caractères spéciaux ne sont pas autorisés",
"form.field.spacesNotAllowed": "Les espaces ne sont pas autorisés",
"form.field.requireLowercase": "Le mot de passe doit contenir au moins une lettre minuscule.",
"form.field.requireUppercase": "Le mot de passe doit contenir au moins une lettre majuscule.",
"form.field.requireDigit": "Le mot de passe doit contenir au moins un chiffre.",
"form.field.requireSpecialChars": "Le mot de passe doit contenir au moins un caractère spécial.",
"form.field.requireMinimumLength": "Le mot de passe doit comporter au moins 8 caractères.",
"form.field.requireLowercase": "Doit contenir au moins une lettre minuscule.",
"form.field.requireUppercase": "Doit contenir au moins une lettre majuscule.",
"form.field.requireDigit": "Doit contenir au moins un chiffre.",
"form.field.requireSpecialChars": "Doit contenir au moins un caractère spécial.",
"matrix.graphSelector": "Colonnes",
"matrix.message.importHint": "Cliquer ou glisser une matrice ici",
"matrix.importNewMatrix": "Import d'une nouvelle matrice",
Expand Down Expand Up @@ -194,7 +192,8 @@
"settings.error.groupRolesSave": "Role(s) pour le groupe '{{0}}' non sauvegardé",
"settings.error.tokenSave": "Token '{{0}}' non sauvegardé",
"settings.error.updateMaintenance": "Erreur lors du changement du status de maintenance",
"settings.error.passwordMismatch": "Les mots de passe ne correspondent pas",
"settings.user.form.confirmPassword": "Confirmer le mot de passe",
"settings.user.form.error.passwordMismatch": "Les mots de passe ne correspondent pas",
"launcher.additionalModes": "Mode additionnels",
"launcher.autoUnzip": "Dézippage automatique",
"launcher.xpress": "Xpress (>= 8.3)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function GroupForm(props: UseFormReturnPlus) {
validateString(v, {
existingValues: existingGroups,
excludedValues: RESERVED_GROUP_NAMES,
}) || undefined,
}),
})}
/>
{/* Permissions */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function UserForm(props: Props) {
validateString(v, {
existingValues: existingUsers,
excludedValues: RESERVED_USER_NAMES,
}) || undefined,
}),
})}
/>
<TextField
Expand All @@ -125,18 +125,20 @@ function UserForm(props: Props) {
helperText={errors.password?.message?.toString()}
{...commonTextFieldProps}
{...register("password", {
validate: (v) => validatePassword(v) || undefined,
validate: (v) => validatePassword(v),
})}
/>
<TextField
label={t("global.confirmPassword")}
label={t("settings.user.form.confirmPassword")}
type="password"
spellCheck
error={!!errors.confirmPassword}
helperText={errors.confirmPassword?.message?.toString()}
{...commonTextFieldProps}
{...register("confirmPassword", {
validate: (v) => validatePassword(v, getValues("password")),
validate: (v) =>
v === getValues("password") ||
t("settings.user.form.error.passwordMismatch"),
})}
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function CreateCandidateDialog(props: PropType) {
validateString(v, {
existingValues: existingCandidates,
allowSpaces: false,
allowedChars: "&_*",
specialChars: "&_*",
}),
}}
sx={{ mx: 0 }}
Expand Down
64 changes: 27 additions & 37 deletions webapp/src/utils/validationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface ValidationOptions {
excludedValues?: string[];
isCaseSensitive?: boolean;
allowSpecialChars?: boolean;
allowedChars?: string;
specialChars?: string;
allowSpaces?: boolean;
editedValue?: string;
min?: number;
Expand All @@ -27,16 +27,16 @@ interface ValidationOptions {
* character validations, and uniqueness against provided arrays of existing and excluded values.
*
* @param value - The string to validate. Leading and trailing spaces will be trimmed.
* @param options - Configuration options for validation.
* @param options.existingValues - An array of strings to check against for duplicates. Comparison is case-insensitive by default.
* @param options.excludedValues - An array of strings that the value should not match.
* @param options.isCaseSensitive - Whether the comparison with `existingValues` and `excludedValues` is case-sensitive. Defaults to false.
* @param options.allowSpecialChars - Flags if special characters are permitted in the value.
* @param options.allowedChars - A string representing additional allowed characters outside the typical alphanumeric scope.
* @param options.allowSpaces - Flags if spaces are allowed in the value.
* @param options.editedValue - The current value being edited, to exclude it from duplicate checks.
* @param options.min - Minimum length required for the string. Defaults to 0.
* @param options.max - Maximum allowed length for the string. Defaults to 255.
* @param options - Configuration options for validation. (Optional)
* @param [options.existingValues=[]] - An array of strings to check against for duplicates. Comparison is case-insensitive by default.
* @param [options.excludedValues=[]] - An array of strings that the value should not match.
* @param [options.isCaseSensitive=false] - Whether the comparison with `existingValues` and `excludedValues` is case-sensitive. Defaults to false.
* @param [options.allowSpecialChars=true] - Flags if special characters are permitted in the value.
* @param [options.specialChars="&()_-"] - A string representing additional allowed characters outside the typical alphanumeric scope.
* @param [options.allowSpaces=true] - Flags if spaces are allowed in the value.
* @param [options.editedValue=""] - The current value being edited, to exclude it from duplicate checks.
* @param [options.min=0] - Minimum length required for the string. Defaults to 0.
* @param [options.max=255] - Maximum allowed length for the string. Defaults to 255.
* @returns True if validation is successful, or a localized error message if it fails.
*/
export function validateString(
Expand All @@ -49,7 +49,7 @@ export function validateString(
isCaseSensitive = false,
allowSpecialChars = true,
allowSpaces = true,
allowedChars = "&()_-",
specialChars = "&()_-",
editedValue = "",
min = 0,
max = 255,
Expand All @@ -74,15 +74,15 @@ export function validateString(
}

// Compiles a regex pattern based on allowed characters and flags.
const allowedCharsPattern = new RegExp(
generatePattern(allowSpaces, allowSpecialChars, allowedChars),
const specialCharsPattern = new RegExp(
generatePattern(allowSpaces, allowSpecialChars, specialChars),
);

// Validates the string against the allowed characters regex.
if (!allowedCharsPattern.test(trimmedValue)) {
return allowSpecialChars
? t("form.field.allowedChars", { 0: allowedChars })
: t("form.field.specialCharsNotAllowed");
if (!specialCharsPattern.test(trimmedValue)) {
return specialChars === "" || !allowSpecialChars
? t("form.field.specialCharsNotAllowed")
: t("form.field.specialChars", { 0: specialChars });
}

// Normalize the value for comparison, based on case sensitivity option.
Expand Down Expand Up @@ -114,13 +114,9 @@ export function validateString(
* Validates a password string for strong security criteria.
*
* @param password - The password to validate.
* @param confirmPassword - An optional second password to compare against the first for matching.
* @returns True if validation is successful, or a localized error message if it fails.
*/
export function validatePassword(
password: string,
confirmPassword?: string,
): string | true {
export function validatePassword(password: string): string | true {
const trimmedPassword = password.trim();

if (!trimmedPassword) {
Expand Down Expand Up @@ -151,42 +147,36 @@ export function validatePassword(
return t("form.field.requireSpecialChars");
}

if (
confirmPassword !== undefined &&
trimmedPassword !== confirmPassword.trim()
) {
return t("settings.error.passwordMismatch");
}

return true;
}

////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////

// Function to escape special characters in allowedChars
const escapeSpecialChars = (chars: string) =>
chars.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
// Escape special characters in specialChars
function escapeSpecialChars(chars: string) {
return chars.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
}

/**
* Generates a regular expression pattern for string validation based on specified criteria.
* This pattern includes considerations for allowing spaces, special characters, and any additional
* characters specified in `allowedChars`.
* characters specified in `specialChars`.
*
* @param allowSpaces - Indicates if spaces are permitted in the string.
* @param allowSpecialChars - Indicates if special characters are permitted.
* @param allowedChars - Specifies additional characters to allow in the string.
* @param specialChars - Specifies additional characters to allow in the string.
* @returns The regular expression pattern as a string.
*/
function generatePattern(
allowSpaces: boolean,
allowSpecialChars: boolean,
allowedChars: string,
specialChars: string,
): string {
const basePattern = "^[a-zA-Z0-9";
const spacePattern = allowSpaces ? " " : "";
const specialCharsPattern =
allowSpecialChars && allowedChars ? escapeSpecialChars(allowedChars) : "";
allowSpecialChars && specialChars ? escapeSpecialChars(specialChars) : "";
return basePattern + spacePattern + specialCharsPattern + "]*$";
}

0 comments on commit c59d810

Please sign in to comment.