mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 07:27:58 +00:00
Strengthen password requirement
This commit is contained in:
@@ -10,7 +10,10 @@ import {
|
|||||||
clearTokens
|
clearTokens
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { TokenService } from '../../services';
|
import { TokenService } from '../../services';
|
||||||
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
|
import {
|
||||||
|
TOKEN_EMAIL_PASSWORD_RESET,
|
||||||
|
AUTH_MODE_JWT
|
||||||
|
} from '../../variables';
|
||||||
import { BadRequestError } from '../../utils/errors';
|
import { BadRequestError } from '../../utils/errors';
|
||||||
import {
|
import {
|
||||||
getSiteURL,
|
getSiteURL,
|
||||||
@@ -231,7 +234,9 @@ export const changePassword = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await clearTokens(user._id);
|
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
|
||||||
|
await clearTokens(req.authData.tokenVersionId)
|
||||||
|
}
|
||||||
|
|
||||||
// clear httpOnly cookie
|
// clear httpOnly cookie
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';
|
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import jsrp from 'jsrp';
|
import jsrp from 'jsrp';
|
||||||
import nacl from 'tweetnacl';
|
import nacl from 'tweetnacl';
|
||||||
@@ -13,7 +13,7 @@ import getOrganizations from '@app/pages/api/organization/getOrgs';
|
|||||||
import ProjectService from '@app/services/ProjectService';
|
import ProjectService from '@app/services/ProjectService';
|
||||||
|
|
||||||
import InputField from '../basic/InputField';
|
import InputField from '../basic/InputField';
|
||||||
import passwordCheck from '../utilities/checks/PasswordCheck';
|
import checkPassword from '../utilities/checks/checkPassword';
|
||||||
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
|
||||||
import { deriveArgonKey } from '../utilities/cryptography/crypto';
|
import { deriveArgonKey } from '../utilities/cryptography/crypto';
|
||||||
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
|
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
|
||||||
@@ -37,6 +37,15 @@ interface UserInfoStepProps {
|
|||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Errors = {
|
||||||
|
length?: string,
|
||||||
|
upperCase?: string,
|
||||||
|
lowerCase?: string,
|
||||||
|
number?: string,
|
||||||
|
specialChar?: string,
|
||||||
|
repeatedChar?: string,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the step of the sign up flow where people provife their name/surname and password
|
* This is the step of the sign up flow where people provife their name/surname and password
|
||||||
* @param {object} obj
|
* @param {object} obj
|
||||||
@@ -69,6 +78,8 @@ export default function UserInfoStep({
|
|||||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -89,12 +100,10 @@ export default function UserInfoStep({
|
|||||||
} else {
|
} else {
|
||||||
setOrganizationNameError(false);
|
setOrganizationNameError(false);
|
||||||
}
|
}
|
||||||
errorCheck = passwordCheck({
|
|
||||||
|
errorCheck = checkPassword({
|
||||||
password,
|
password,
|
||||||
setPasswordErrorLength,
|
setErrors
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorLowerCase,
|
|
||||||
errorCheck
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!errorCheck) {
|
if (!errorCheck) {
|
||||||
@@ -248,12 +257,9 @@ export default function UserInfoStep({
|
|||||||
label={t('section.password.password')}
|
label={t('section.password.password')}
|
||||||
onChangeHandler={(pass: string) => {
|
onChangeHandler={(pass: string) => {
|
||||||
setPassword(pass);
|
setPassword(pass);
|
||||||
passwordCheck({
|
checkPassword({
|
||||||
password: pass,
|
password: pass,
|
||||||
setPasswordErrorLength,
|
setErrors
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorLowerCase,
|
|
||||||
errorCheck: false
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="password"
|
type="password"
|
||||||
@@ -263,44 +269,29 @@ export default function UserInfoStep({
|
|||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="new-password"
|
id="new-password"
|
||||||
/>
|
/>
|
||||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
{Object.keys(errors).length > 0 && (
|
||||||
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
<div className="mb-1 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
<div className="mb-2 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
{Object.keys(errors).map((key) => {
|
||||||
{passwordErrorLength ? (
|
if (errors[key as keyof Errors]) {
|
||||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
return (
|
||||||
) : (
|
<div className="ml-1 flex flex-row items-top justify-start">
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
<div>
|
||||||
)}
|
<FontAwesomeIcon
|
||||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
icon={faXmark}
|
||||||
{t('section.password.validate-length')}
|
className="text-md text-red ml-0.5 mr-2.5"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
<p className="text-gray-400 text-sm">
|
||||||
{passwordErrorLowerCase ? (
|
{errors[key as keyof Errors]}
|
||||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
</p>
|
||||||
) : (
|
</div>
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
);
|
||||||
)}
|
}
|
||||||
<div
|
|
||||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
return null;
|
||||||
>
|
})}
|
||||||
{t('section.password.validate-case')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
|
||||||
{passwordErrorNumber ? (
|
|
||||||
<FontAwesomeIcon icon={faXmark} className="text-md text-red ml-0.5 mr-2.5" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
|
||||||
)}
|
|
||||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
|
||||||
{t('section.password.validate-number')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="py-2" />
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
<div className="flex flex-col items-center justify-center lg:w-[19%] w-1/4 min-w-[20rem] mt-2 max-w-xs md:max-w-md mx-auto text-sm text-center md:text-left">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const passwordCheck = ({
|
|||||||
setPasswordErrorLowerCase,
|
setPasswordErrorLowerCase,
|
||||||
errorCheck
|
errorCheck
|
||||||
}: PasswordCheckProps) => {
|
}: PasswordCheckProps) => {
|
||||||
|
|
||||||
if (!password || password.length < 14) {
|
if (!password || password.length < 14) {
|
||||||
setPasswordErrorLength(true);
|
setPasswordErrorLength(true);
|
||||||
errorCheck = true;
|
errorCheck = true;
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
type Errors = {
|
||||||
|
length?: string,
|
||||||
|
upperCase?: string,
|
||||||
|
lowerCase?: string,
|
||||||
|
number?: string,
|
||||||
|
specialChar?: string,
|
||||||
|
repeatedChar?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CheckPasswordParams {
|
||||||
|
password: string;
|
||||||
|
setErrors: (value: Errors) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the password [password] is at least:
|
||||||
|
* - 8 characters long
|
||||||
|
* - Contains 1 uppercase character (A-Z)
|
||||||
|
* - Contains 1 lowercase character (a-z)
|
||||||
|
* - Contains 1 number (0-9)
|
||||||
|
* - Does not contain 3 repeat, consecutive characters
|
||||||
|
*
|
||||||
|
* The function returns whether or not the password [password]
|
||||||
|
* passes the minimum requirements above. It sets errors on
|
||||||
|
* an erorr object via [setErrors].
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.password - the password to check
|
||||||
|
* @param {Function} obj.setErrors - set state function to set error object
|
||||||
|
*/
|
||||||
|
const checkPassword = ({
|
||||||
|
password,
|
||||||
|
setErrors
|
||||||
|
}: CheckPasswordParams): boolean => {
|
||||||
|
let errors: Errors = {};
|
||||||
|
|
||||||
|
if (password.length < 8) {
|
||||||
|
errors.length = "8 characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[A-Z]/.test(password)) {
|
||||||
|
errors.upperCase = "1 uppercase character (A-Z)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[a-z]/.test(password)) {
|
||||||
|
errors.lowerCase = "1 lowercase character (a-z)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[0-9]/.test(password)) {
|
||||||
|
errors.number = "1 number (0-9)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
||||||
|
errors.specialChar = "1 special character (!@#$%^&*(),.?)";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/([A-Za-z0-9])\1\1\1/.test(password)) {
|
||||||
|
errors.repeatedChar = "No 3 repeat, consecutive characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors(errors);
|
||||||
|
return Object.keys(errors).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default checkPassword;
|
||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { faCheck, faPlus, faX, faBan } from '@fortawesome/free-solid-svg-icons';
|
import { faCheck, faPlus, faXmark, faBan } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
import Button from '@app/components/basic/buttons/Button';
|
import Button from '@app/components/basic/buttons/Button';
|
||||||
@@ -10,7 +10,7 @@ import InputField from '@app/components/basic/InputField';
|
|||||||
import ListBox from '@app/components/basic/Listbox';
|
import ListBox from '@app/components/basic/Listbox';
|
||||||
import ApiKeyTable from '@app/components/basic/table/ApiKeyTable';
|
import ApiKeyTable from '@app/components/basic/table/ApiKeyTable';
|
||||||
import NavHeader from '@app/components/navigation/NavHeader';
|
import NavHeader from '@app/components/navigation/NavHeader';
|
||||||
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
import checkPassword from '@app/components/utilities/checks/checkPassword';
|
||||||
import changePassword from '@app/components/utilities/cryptography/changePassword';
|
import changePassword from '@app/components/utilities/cryptography/changePassword';
|
||||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||||
import { SecuritySection } from '@app/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection';
|
import { SecuritySection } from '@app/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection';
|
||||||
@@ -22,12 +22,18 @@ import {
|
|||||||
useRevokeAllSessions
|
useRevokeAllSessions
|
||||||
} from '@app/hooks/api';
|
} from '@app/hooks/api';
|
||||||
|
|
||||||
|
type Errors = {
|
||||||
|
length?: string,
|
||||||
|
upperCase?: string,
|
||||||
|
lowerCase?: string,
|
||||||
|
number?: string,
|
||||||
|
specialChar?: string,
|
||||||
|
repeatedChar?: string,
|
||||||
|
};
|
||||||
|
|
||||||
export default function PersonalSettings() {
|
export default function PersonalSettings() {
|
||||||
const [personalEmail, setPersonalEmail] = useState('');
|
const [personalEmail, setPersonalEmail] = useState('');
|
||||||
const [personalName, setPersonalName] = useState('');
|
const [personalName, setPersonalName] = useState('');
|
||||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
|
||||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
|
||||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
|
||||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||||
const [currentPassword, setCurrentPassword] = useState('');
|
const [currentPassword, setCurrentPassword] = useState('');
|
||||||
const [newPassword, setNewPassword] = useState('');
|
const [newPassword, setNewPassword] = useState('');
|
||||||
@@ -37,6 +43,7 @@ export default function PersonalSettings() {
|
|||||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false);
|
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false);
|
||||||
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
||||||
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
|
||||||
const revokeAllSessions = useRevokeAllSessions();
|
const revokeAllSessions = useRevokeAllSessions();
|
||||||
|
|
||||||
@@ -159,78 +166,52 @@ export default function PersonalSettings() {
|
|||||||
label={t('section.password.new') as string}
|
label={t('section.password.new') as string}
|
||||||
onChangeHandler={(password) => {
|
onChangeHandler={(password) => {
|
||||||
setNewPassword(password);
|
setNewPassword(password);
|
||||||
passwordCheck({
|
checkPassword({
|
||||||
password,
|
password,
|
||||||
setPasswordErrorLength,
|
setErrors
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorLowerCase,
|
|
||||||
errorCheck: false
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="password"
|
type="password"
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
isRequired
|
isRequired
|
||||||
error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber}
|
error={Object.keys(errors).length > 0}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="new-password"
|
id="new-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
{Object.keys(errors).length > 0 && (
|
||||||
<div className="mt-3 mb-2 flex w-full max-w-xl flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
<div className="mb-1 text-sm text-gray-400">
|
<div className="mb-2 text-sm text-gray-400">{t('section.password.validate-base')}</div>
|
||||||
{t('section.password.validate-base')}
|
{Object.keys(errors).map((key) => {
|
||||||
</div>
|
if (errors[key as keyof Errors]) {
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
return (
|
||||||
{passwordErrorLength ? (
|
<div className="ml-1 flex flex-row items-top justify-start">
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
<div>
|
||||||
) : (
|
<FontAwesomeIcon
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
icon={faXmark}
|
||||||
)}
|
className="text-md text-red ml-0.5 mr-2.5"
|
||||||
<div
|
/>
|
||||||
className={`${
|
</div>
|
||||||
passwordErrorLength ? 'text-gray-400' : 'text-gray-600'
|
<p className="text-gray-400 text-sm">
|
||||||
} text-sm`}
|
{errors[key as keyof Errors]}
|
||||||
>
|
</p>
|
||||||
{t('section.password.validate-length')}
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
}
|
||||||
<div className="ml-1 flex flex-row items-center justify-start">
|
|
||||||
{passwordErrorLowerCase ? (
|
return null;
|
||||||
<FontAwesomeIcon icon={faX} className="text-md mr-2.5 text-red" />
|
})}
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md mr-2 text-primary" />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'
|
|
||||||
} text-sm`}
|
|
||||||
>
|
|
||||||
{t('section.password.validate-case')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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`}
|
|
||||||
>
|
|
||||||
{t('section.password.validate-number')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="py-2" />
|
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 flex w-52 flex-row items-center pr-3">
|
<div className="mt-3 flex w-52 flex-row items-center pr-3">
|
||||||
<Button
|
<Button
|
||||||
text={t('section.password.change') as string}
|
text={t('section.password.change') as string}
|
||||||
onButtonPressed={() => {
|
onButtonPressed={() => {
|
||||||
if (!passwordErrorLength && !passwordErrorLowerCase && !passwordErrorNumber) {
|
const errorCheck = checkPassword({
|
||||||
|
password: newPassword,
|
||||||
|
setErrors
|
||||||
|
});
|
||||||
|
if (!errorCheck) {
|
||||||
changePassword(
|
changePassword(
|
||||||
personalEmail,
|
personalEmail,
|
||||||
currentPassword,
|
currentPassword,
|
||||||
@@ -244,11 +225,7 @@ export default function PersonalSettings() {
|
|||||||
}}
|
}}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
size="md"
|
size="md"
|
||||||
active={
|
active={true}
|
||||||
newPassword !== '' &&
|
|
||||||
currentPassword !== '' &&
|
|
||||||
!(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber)
|
|
||||||
}
|
|
||||||
textDisabled={t('section.password.change') as string}
|
textDisabled={t('section.password.change') as string}
|
||||||
/>
|
/>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Head from 'next/head';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { faCheck, faWarning, faX } from '@fortawesome/free-solid-svg-icons';
|
import { faCheck, faWarning, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import jsrp from 'jsrp';
|
import jsrp from 'jsrp';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
@@ -18,6 +18,9 @@ import Button from '@app/components/basic/buttons/Button';
|
|||||||
import InputField from '@app/components/basic/InputField';
|
import InputField from '@app/components/basic/InputField';
|
||||||
import attemptLogin from '@app/components/utilities/attemptLogin';
|
import attemptLogin from '@app/components/utilities/attemptLogin';
|
||||||
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
import passwordCheck from '@app/components/utilities/checks/PasswordCheck';
|
||||||
|
|
||||||
|
import checkPassword from '@app/components/utilities/checks/checkPassword';
|
||||||
|
|
||||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||||
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
|
import { deriveArgonKey } from '@app/components/utilities/cryptography/crypto';
|
||||||
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
import issueBackupKey from '@app/components/utilities/cryptography/issueBackupKey';
|
||||||
@@ -32,21 +35,26 @@ import verifySignupInvite from './api/auth/VerifySignupInvite';
|
|||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
|
|
||||||
|
type Errors = {
|
||||||
|
length?: string,
|
||||||
|
upperCase?: string,
|
||||||
|
lowerCase?: string,
|
||||||
|
number?: string,
|
||||||
|
specialChar?: string,
|
||||||
|
repeatedChar?: string,
|
||||||
|
};
|
||||||
|
|
||||||
export default function SignupInvite() {
|
export default function SignupInvite() {
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [firstName, setFirstName] = useState('');
|
const [firstName, setFirstName] = useState('');
|
||||||
const [lastName, setLastName] = useState('');
|
const [lastName, setLastName] = useState('');
|
||||||
const [firstNameError, setFirstNameError] = useState(false);
|
const [firstNameError, setFirstNameError] = useState(false);
|
||||||
const [lastNameError, setLastNameError] = useState(false);
|
const [lastNameError, setLastNameError] = useState(false);
|
||||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
|
||||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
|
||||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
|
||||||
const [errorLogin, setErrorLogin] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
const [verificationToken, setVerificationToken] = useState('');
|
|
||||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||||
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
const parsedUrl = queryString.parse(router.asPath.split('?')[1]);
|
||||||
@@ -70,12 +78,10 @@ export default function SignupInvite() {
|
|||||||
} else {
|
} else {
|
||||||
setLastNameError(false);
|
setLastNameError(false);
|
||||||
}
|
}
|
||||||
errorCheck = passwordCheck({
|
|
||||||
|
errorCheck = checkPassword({
|
||||||
password,
|
password,
|
||||||
setPasswordErrorLength,
|
setErrors
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorLowerCase,
|
|
||||||
errorCheck
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!errorCheck) {
|
if (!errorCheck) {
|
||||||
@@ -252,60 +258,42 @@ export default function SignupInvite() {
|
|||||||
label="Password"
|
label="Password"
|
||||||
onChangeHandler={(pass) => {
|
onChangeHandler={(pass) => {
|
||||||
setPassword(pass);
|
setPassword(pass);
|
||||||
passwordCheck({
|
checkPassword({
|
||||||
password: pass,
|
password: pass,
|
||||||
setPasswordErrorLength,
|
setErrors
|
||||||
setPasswordErrorNumber,
|
|
||||||
setPasswordErrorLowerCase,
|
|
||||||
errorCheck: false
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
isRequired
|
isRequired
|
||||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
error={Object.keys(errors).length > 0}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
id="new-password"
|
id="new-password"
|
||||||
/>
|
/>
|
||||||
{passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber ? (
|
{Object.keys(errors).length > 0 && (
|
||||||
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
<div className="mt-4 flex w-full flex-col items-start rounded-md bg-white/5 px-2 py-2">
|
||||||
<div className="text-gray-400 text-sm mb-1">Password should contain at least:</div>
|
<div className="mb-2 text-sm text-gray-400">Password should contain at least:</div>
|
||||||
<div className="flex flex-row justify-start items-center ml-1">
|
{Object.keys(errors).map((key) => {
|
||||||
{passwordErrorLength ? (
|
if (errors[key as keyof Errors]) {
|
||||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
return (
|
||||||
) : (
|
<div className="ml-1 flex flex-row items-top justify-start">
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
<div>
|
||||||
)}
|
<FontAwesomeIcon
|
||||||
<div className={`${passwordErrorLength ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
icon={faXmark}
|
||||||
14 characters
|
className="text-md text-red ml-0.5 mr-2.5"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-400 text-sm">
|
||||||
|
{errors[key as keyof Errors]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row justify-start items-center ml-1">
|
)}
|
||||||
{passwordErrorLowerCase ? (
|
|
||||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={`${passwordErrorLowerCase ? 'text-gray-400' : 'text-gray-600'} text-sm`}
|
|
||||||
>
|
|
||||||
1 lowercase character
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row justify-start items-center ml-1">
|
|
||||||
{passwordErrorNumber ? (
|
|
||||||
<FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5" />
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2" />
|
|
||||||
)}
|
|
||||||
<div className={`${passwordErrorNumber ? 'text-gray-400' : 'text-gray-600'} text-sm`}>
|
|
||||||
1 number
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="py-2" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
<div className="flex flex-col items-center justify-center md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-max mx-auto text-lg">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user