Skip to content

Commit

Permalink
Merge pull request #43 from TeskaLabs/feature/password-criteria
Browse files Browse the repository at this point in the history
Advanced password validation
  • Loading branch information
byewokko authored May 13, 2024
2 parents a14f645 + 397f223 commit d36073f
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 90 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# CHANGELOG

## v24.19

### Releases

- v24.19-alpha

### Compatibility

No breaking changes, tested with Seacat Auth v24.17-beta2

### Features

- Advanced password validation (#43, v24.19-alpha, PLUM Sprint 240503)


## v23.29

- v23.29-beta
Expand Down
19 changes: 14 additions & 5 deletions public/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@
"Fill in your login credentials": "Vložte prosím Vaše přihlašovací údaje"
},
"ChangePwdScreen": {
"Set password": "Nastavení hesla",
"Set new password here": "Zde si můžete nastavit Vaše nové heslo",
"Password change": "Změna hesla",
"Set password": "Změnit heslo",
"Set new password here": "Zde si můžete nastavit nové heslo",
"Current Password": "Stávající heslo",
"New Password": "Nové heslo",
"Re-enter Password": "Nové heslo znovu",
Expand All @@ -159,7 +160,14 @@
"Password changed": "Heslo bylo změněno",
"Go back": "Zpět"
},
"Something went wrong": "Something went wrong"
"Something went wrong": "Something went wrong",
"New password must be different from your old password": "Nové heslo nesmí být stejné jako vaše stávající heslo",
"The password must meet the following criteria:": "Heslo musí splňovat tato kritéria:",
"It must consist of {{minLength}} or more characters": "Musí být alespoň {{minLength}} znaků dlouhé",
"It must contain at least {{minLowercaseCount}} lowercase characters": "Musí obsahovat alespoň {{minLowercaseCount}} malých písmen",
"It must contain at least {{minUppercaseCount}} uppercase characters": "Musí obsahovat alespoň {{minUppercaseCount}} velkých písmen",
"It must contain at least {{minDigitCount}} digits": "Musí obsahovat alespoň {{minDigitCount}} číslic",
"It must contain at least {{minSpecialCount}} special characters": "Musí obsahovat alespoň {{minSpecialCount}} speciálních znaků"
},
"PhoneNumberScreen": {
"Do you want to change your number?": "Opravdu chcete změnit vaše telefonní číslo?",
Expand All @@ -182,15 +190,16 @@
},
"ResetPwdScreen": {
"Set password": "Nastavení hesla",
"Set new password here": "Zde si můžete nastavit Vaše nové heslo",
"Set new password": "Nastavit nové heslo",
"Set a new password here": "Zde si můžete nastavit Vaše nové heslo",
"Current Password": "Stávající heslo",
"New Password": "Nové heslo",
"Re-enter Password": "Nové heslo znovu",
"Enter new password a second time to verify it": "Pro kontrolu vložte Vaše nové heslo podruhé",
"Passwords do not match": "Hesla se neshodují!",
"Short password": "Heslo je příliš krátké!",
"Something went wrong, unable to set the password": "Něco je špatně, nepodařilo se nastavit heslo",
"Invalid password reset link, please set your password again": "Neplatný odkaz k obnovení hesla, prosím nastavte Vaše heslo znovu",
"Your password reset link has likely expired. Please request a new one.": "Váš odkaz k obnovení hesla pravděpodobně vypršel. Můžete si nechat zaslat odkaz nový.",
"CompletedResetPwdCard": {
"Password set": "Heslo bylo nastaveno",
"Continue": "Pokračovat"
Expand Down
15 changes: 12 additions & 3 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"Fill in your login credentials": "Please fill in your login credentials"
},
"ChangePwdScreen": {
"Password change": "Password change",
"Set password": "Set password",
"Set new password here": "You can set your new password here",
"Current Password": "Current password",
Expand All @@ -159,7 +160,14 @@
"Password changed": "Password has been changed",
"Go back": "Go back"
},
"Something went wrong": "Something went wrong"
"Something went wrong": "Something went wrong",
"New password must be different from your old password": "New password must be different from your old password",
"The password must meet the following criteria:": "The password must meet the following criteria:",
"It must consist of {{minLength}} or more characters": "It must consist of {{minLength}} or more characters",
"It must contain at least {{minLowercaseCount}} lowercase characters": "It must contain at least {{minLowercaseCount}} lowercase characters",
"It must contain at least {{minUppercaseCount}} uppercase characters": "It must contain at least {{minUppercaseCount}} uppercase characters",
"It must contain at least {{minDigitCount}} digits": "It must contain at least {{minDigitCount}} digits",
"It must contain at least {{minSpecialCount}} special characters": "It must contain at least {{minSpecialCount}} special characters"
},
"PhoneNumberScreen": {
"Do you want to change your number?": "Do you want to change your number?",
Expand All @@ -182,15 +190,16 @@
},
"ResetPwdScreen": {
"Set password": "Set password",
"Set new password here": "You can set your new password here",
"Set new password": "Set new password",
"Set a new password here": "You can set your new password here",
"Current Password": "Current password",
"New Password": "New password",
"Re-enter Password": "Re-enter the new password",
"Enter new password a second time to verify it": "Enter new password a second time to verify it",
"Passwords do not match": "Passwords do not match!",
"Short password": "Password is too short!",
"Something went wrong, unable to set the password": "Something went wrong, unable to set the password",
"Invalid password reset link, please set your password again": "Invalid password reset link, please set your password again",
"Your password reset link has likely expired. Please request a new one.": "Your password reset link has likely expired. Please request a new one.",
"CompletedResetPwdCard": {
"Password set": "Password has been set",
"Continue": "Continue"
Expand Down
115 changes: 82 additions & 33 deletions src/modules/auth/passwd/ChangePwdScreen.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
import { Link, useHistory, useLocation } from "react-router-dom";
import { useHistory, useLocation } from "react-router-dom";

import {
Container, Row, Col,
Expand All @@ -11,6 +11,14 @@ import {

import { factorChaining } from "../utils/factorChaining";
import generatePenrose from '../utils/generatePenrose';
import {
validatePasswordLength,
validatePasswordLowercaseCount,
validatePasswordUppercaseCount,
validatePasswordDigitCount,
validatePasswordSpecialCount,
PasswordCriteriaFeedback,
} from '../utils/passwordValidation';

function ChangePwdScreen(props) {

Expand All @@ -31,50 +39,83 @@ function ChangePwdScreen(props) {
export default ChangePwdScreen;

function ChangePwdCard(props) {
const { t, i18n } = useTranslation();
const { handleSubmit, register, getValues, formState: { errors, isSubmitting } } = useForm();
const { t } = useTranslation();
const SeaCatAuthAPI = props.app.axiosCreate('seacat-auth');
const { handleSubmit, register, getValues, watch, formState: { errors, isSubmitting } } = useForm();

let history = useHistory();

let params = new URLSearchParams(useLocation().search);
let redirect_uri = params.get("redirect_uri");

const [ completed, setCompleted ] = useState(false);
const [ passwordCriteria, setPasswordCriteria ] = useState({
minLength: 10,
});

useEffect(() => {
loadPasswordCriteria();
}, []);

const loadPasswordCriteria = async () => {
try {
const response = await SeaCatAuthAPI.get('/public/password/policy');
setPasswordCriteria({
minLength: response.data?.min_length,
minLowercaseCount: response.data?.min_lowercase_count,
minUppercaseCount: response.data?.min_uppercase_count,
minDigitCount: response.data?.min_digit_count,
minSpecialCount: response.data?.min_special_count,
});
} catch (e) {
if (e?.response?.status == 404) {
// Most likely older service version which does not have this endpoint
console.error(e);
} else {
props.app.addAlertFromException(e, t('ChangePwdScreen|Failed to load password criteria'));
}
}
};

// Password is watched for immediate feedback to the user
const watchedNewPassword = watch('newpassword', '');
const validateNewPassword = (value) => ({
minLength: validatePasswordLength(value, passwordCriteria?.minLength),
minLowercaseCount: validatePasswordLowercaseCount(value, passwordCriteria?.minLowercaseCount),
minUppercaseCount: validatePasswordUppercaseCount(value, passwordCriteria?.minUppercaseCount),
minDigitCount: validatePasswordDigitCount(value, passwordCriteria?.minDigitCount),
minSpecialCount: validatePasswordSpecialCount(value, passwordCriteria?.minSpecialCount),
});

const regOldpwd = register("oldpassword");
const regNewpwd = register("newpassword",{
const regNewpwd = register("newpassword", {
validate: {
shortInput: value => (getValues().newpassword.length >= 4)|| t("ChangePwdScreen|Short password"),
passwordCriteria: (value) => (Object.values(validateNewPassword(value)).every(Boolean)
|| t('ChangePwdScreen|Password does not meet security requirements')),
dontReuseOldPassword: (value) => (value !== getValues('oldpassword'))
|| t('ChangePwdScreen|New password must be different from your old password'),
}
});
const regNewpwd2 = register("newpassword2",{
const regNewpwd2 = register("newpassword2", {
validate: {
passEqual: value => (value === getValues().newpassword) || t("ChangePwdScreen|Passwords do not match"),
}
});

const onSubmit = async (values) => {
let SeaCatAuthAPI = props.app.axiosCreate('seacat-auth');
let response;

try {
response = await SeaCatAuthAPI.put("/public/password-change", values)
const response = await SeaCatAuthAPI.put('/public/password-change', values);

if (response.data.result !== 'OK') {
throw new Error(t('ChangePwdScreen|Unexpected server response'));
}
} catch (e) {
props.app.addAlert("danger", `${t("ChangePwdScreen|Something went wrong")}. ${e?.response?.data?.message}`, 30);
return;
}
if (e?.response?.status == 401 || e?.response?.data?.result == 'UNAUTHORIZED') {
props.app.addAlert("danger", t('ChangePwdScreen|The current password is incorrect'), 30);
} else {
props.app.addAlert("danger", `${t("ResetPwdScreen|Password change failed")}. ${e?.response?.data?.message}`, 30);
}

if (response.data.result == 'FAILED') {
props.app.addAlert(
"danger",
t("ChangePwdScreen|Something went wrong"), 30
);
return;
} else if (response.data.result == 'UNAUTHORIZED') {
props.app.addAlert(
"danger",
t("ChangePwdScreen|The current password is incorrect"), 30
);
return;
}

Expand Down Expand Up @@ -112,7 +153,7 @@ function ChangePwdCard(props) {
<Card className="shadow animated fadeIn auth-card">
<CardHeader className="border-bottom card-header-login">
<div className="card-header-title" >
<CardTitle className="text-primary" tag="h2">{t('ChangePwdScreen|Set password')}</CardTitle>
<CardTitle className="text-primary" tag="h2">{t('ChangePwdScreen|Password change')}</CardTitle>
<CardSubtitle tag="p">
{t('ChangePwdScreen|Set new password here')}
</CardSubtitle>
Expand Down Expand Up @@ -146,17 +187,25 @@ function ChangePwdCard(props) {
</Label>
</h5>
<Input
id="newpassword"
name="newpassword"
type="password"
autoComplete="new-password"
required="required"
invalid={errors.newpassword}
id='newpassword'
name='newpassword'
type='password'
autoComplete='new-password'
required='required'
invalid={Boolean(errors?.newpassword)}
onBlur={regNewpwd.onBlur}
innerRef={regNewpwd.ref}
onChange={regNewpwd.onChange}
/>
{errors.newpassword && <FormFeedback>{errors.newpassword.message}</FormFeedback>}
{errors?.newpassword?.type !== 'passwordCriteria'
&& <FormFeedback>{errors?.newpassword?.message}</FormFeedback>
}
<PasswordCriteriaFeedback
passwordCriteria={passwordCriteria}
validatePassword={validateNewPassword}
watchedPassword={watchedNewPassword}
passwordErrors={errors?.newpassword}
/>
</FormGroup>

<FormGroup tag="fieldset" disabled={isSubmitting} className="text-center">
Expand All @@ -179,7 +228,7 @@ function ChangePwdCard(props) {
{errors.newpassword2 ?
<FormFeedback>{errors.newpassword2.message}</FormFeedback>
:
<FormText>
<FormText className='text-left'>
{t('ChangePwdScreen|Enter new password a second time to verify it')}
</FormText>
}
Expand Down
Loading

0 comments on commit d36073f

Please sign in to comment.