Merge pull request #604 from ONLYOFFICE/feature/password-strength

Feature/password strength
This commit is contained in:
Alexey Safronov 2022-04-05 18:04:23 +03:00 committed by GitHub
commit 3220199473
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 553 additions and 112 deletions

View File

@ -52,6 +52,10 @@ class AuthStore {
this.userStore.user && requests.push(this.moduleStore.init());
}
if (this.isAuthenticated) {
this.settingsStore.getPortalPasswordSettings();
}
return Promise.all(requests);
};
setLanguage() {

View File

@ -364,6 +364,7 @@ class SettingsStore {
digits,
specSymbols
);
this.setPasswordSettings(settings);
};
setTimezones = (timezones) => {

View File

@ -63,3 +63,4 @@ export { default as ViewSelector } from "./view-selector";
export * as Themes from "./themes";
export { default as Portal } from "./portal";
export { default as TableContainer } from "./TableContainer";
export { default as Slider } from "./slider";

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { StyledSlider } from "./styled-slider";
@ -16,16 +16,9 @@ const Slider = ({
}) => {
const [size, setSize] = useState("0%");
const handleChange = (e) => {
let target = e.target;
const max = target.max;
const min = target.min;
const val = target.value;
setSize(((val - min) * 100) / (max - min) + "%");
onChange && onChange(e);
};
useEffect(() => {
setSize(((value - min) * 100) / (max - min) + "%");
}, [value]);
return (
<StyledSlider
@ -39,7 +32,7 @@ const Slider = ({
value={value}
size={value && withPouring ? size : "0%"}
withPouring={withPouring}
onChange={handleChange}
onChange={onChange}
/>
);
};

View File

@ -30,6 +30,7 @@
"ClearBackupList": "Delete all backups",
"ChangeLogoButton": "Change Logo",
"ChangeOwner": "Change portal owner",
"Characters": "{{length}} characters",
"ChooseOwner": "Choose owner",
"CompanyNameForCanvasLogo": "Company name",
"ConfirmEmailSended": "Confirmation e-mail has been sent to {{ownerName}}",
@ -86,6 +87,7 @@
"NoAdminsDescription": "You can add new administrator manually",
"NotFoundDescription": "Change filter settings or add people to the section.",
"NotFoundTitle": "Nothing found",
"PasswordMinLenght": "Minimal password length",
"Path": "Path",
"PeopleAdministratorsCan": "People module admins can create profiles and groups, import people, and invite users.",
"PortalAccess": "Portal access",
@ -104,6 +106,9 @@
"RestoreDefaultButton": "Restore to Default",
"RecoveryFileNotSelected": "Recovery error. Recovery file not selected",
"SetDefaultTitle": "Set default title",
"SettingPasswordStrength": "Setting password strength",
"SettingPasswordStrengthDescription": "Password Strength Settings is a way to determine the effectiveness of a password in resisting guessing and brute-force attacks.",
"SettingPasswordStrengthHelper": "Use the Minimum Password Length bar to determine how long the password should be. Check the appropriate boxes below to determine the character set that must be used in the password.",
"SendNotificationAboutRestoring": "Send notification about portal restoring to users",
"ServerSideEncryptionMethod": "Server Side Encryption Method",
"ServiceUrl": "Service Url",
@ -131,9 +136,12 @@
"TwoFactorAuthDescription": "Two-factor authentication provides a more secure way to log in. After entering the credentials, the user will have to enter a code from an SMS or the authentication app. ",
"TwoFactorAuthHelper": "Note: SMS messages can be sent if you have a positive balance only. You can always check your current balance in your SMS provider account. Do not forget to replenish your balance in good time. ",
"UseAsLogoButton": "Use as logo",
"UseDigits": "Use digits",
"Users": "Users",
"UserAgreement": "I confirm and want to proceed",
"UseHttp": "Use Http",
"UseSpecialChar": "Use special characters",
"UseUpperCase": "Use capital letters",
"WelcomePageTitle": "Welcome page title",
"YouHaveUnsavedChanges": "You have unsaved changes"
}

View File

@ -31,6 +31,7 @@
"ClearBackupList": "Удалить все резервные копии",
"ChangeLogoButton": "Сменить логотип",
"ChangeOwner": "Сменить владельца портала",
"Characters": "{{length}} символов",
"ChooseOwner": "Выбрать владельца",
"CompanyNameForCanvasLogo": "Название компании",
"ConfirmEmailSended": "Письмо с подтверждением отправлено {{ownerName}}",
@ -87,6 +88,7 @@
"NoAdminsDescription": "Вы можете добавить нового администратора вручную",
"NotFoundDescription": "Измените настройки фильтра или добавьте людей в раздел.",
"NotFoundTitle": "Ничего не найдено",
"PasswordMinLenght": "Минимальная длина пароля",
"Path": "Путь",
"PeopleAdministratorsCan": "Администраторы модуля Люди могут создавать профили и группы, импортировать людей и приглашать пользователей.",
"PortalAccess": "Доступ к порталу",
@ -105,6 +107,9 @@
"RestoreDefaultButton": "Настройки по умолчанию",
"RecoveryFileNotSelected": "Ошибка восстановления. Файл восстановления не выбран",
"SetDefaultTitle": "Установить заголовок по умолчанию",
"SettingPasswordStrength": "Настройка надежности пароля",
"SettingPasswordStrengthDescription": "Настройки надежности пароля позволяют определить надежность пароля (эффективность пароля при сопротивлении угадыванию и прямому подбору).",
"SettingPasswordStrengthHelper": "Используйте ползунок Минимальная длина пароля, чтобы определить, насколько длинным должен быть пароль, чтобы считаться надежным. Отметьте подходящие опции ниже, чтобы определить, какой набор символов должен использоваться в пароле.",
"SendNotificationAboutRestoring": "Оповестить пользователей о восстановлении портала",
"ServerSideEncryptionMethod": "Метод шифрования на стороне сервера",
"ServiceUrl": "Url-адрес сервиса",
@ -132,9 +137,12 @@
"TwoFactorAuthDescription": "Двухфакторная аутентификация обеспечивает более безопасный способ входа на портал. После ввода учетных данных пользователь должен будет ввести код из SMS или приложения для аутентификации.",
"TwoFactorAuthHelper": "Обратите внимание: отправка SMS-сообщений осуществляется только при положительном балансе. Вы всегда можете проверить текущий баланс в учетной записи вашего SMS-провайдера. Не забывайте своевременно пополнять баланс. ",
"UseAsLogoButton": "Использовать как логотип",
"UseDigits": "Использовать цифры",
"Users": "Пользователи",
"UserAgreement": "Я подтверждаю и хочу продолжить",
"UseHttp": "Использовать Http",
"UseSpecialChar": "Использовать специальные символы",
"UseUpperCase": "Использовать заглавные буквы",
"WelcomePageTitle": "Заголовок страницы приветствия",
"YouHaveUnsavedChanges": "Имеются несохраненные изменения"
}

View File

@ -15,6 +15,12 @@ StyledArrowRightIcon.defaultProps = { theme: Base };
export const MainContainer = styled.div`
width: 100%;
hr {
margin: 24px 0;
border: none;
border-top: 1px solid #eceef1;
}
.subtitle {
margin-bottom: 20px;
}
@ -27,39 +33,6 @@ export const MainContainer = styled.div`
position: fixed;
left: 50%;
}
.category-item-wrapper {
margin-bottom: 40px;
.category-item-heading {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.category-item-subheader {
font-size: 13px;
font-weight: 600;
margin-bottom: 5px;
}
.category-item-description {
color: ${(props) =>
props.theme.studio.settings.security.descriptionColor};
font-size: 12px;
max-width: 1024px;
}
.inherit-title-link {
margin-right: 7px;
font-size: 19px;
font-weight: 600;
}
.link-text {
margin: 0;
}
}
`;
MainContainer.defaultProps = { theme: Base };
@ -71,3 +44,74 @@ export const StyledCategoryWrapper = styled.div`
margin-bottom: 16px;
align-items: center;
`;
export const StyledMobileCategoryWrapper = styled.div`
margin-bottom: 20px;
.category-item-heading {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.category-item-subheader {
font-size: 13px;
font-weight: 600;
margin-bottom: 5px;
}
.category-item-description {
color: ${(props) => props.theme.studio.settings.security.descriptionColor};
font-size: 12px;
max-width: 1024px;
}
.inherit-title-link {
margin-right: 7px;
font-size: 19px;
font-weight: 600;
}
.link-text {
margin: 0;
}
`;
StyledMobileCategoryWrapper.defaultProps = { theme: Base };
export const ButtonsWrapper = styled.div`
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
@media (max-width: 600px) {
position: absolute;
bottom: 16px;
width: calc(100vw - 84px);
.button {
height: 40px;
width: 100%;
}
.reminder {
position: absolute;
bottom: 48px;
}
}
@media (max-width: 430px) {
width: calc(100vw - 32px);
}
`;
export const LearnMoreWrapper = styled.div`
display: none;
@media (max-width: 600px) {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}
`;

View File

@ -1,25 +1,42 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import Text from "@appserver/components/text";
import { setDocumentTitle } from "../../../../../../helpers/utils";
import { MainContainer } from "../StyledSecurity";
import TfaSection from "./tfa";
import PasswordStrengthSection from "./passwordStrength";
import MobileView from "./mobileView";
import { isMobile } from "react-device-detect";
import CategoryWrapper from "../sub-components/category-wrapper";
import { size } from "@appserver/components/utils/device";
const AccessPortal = (props) => {
const { t } = props;
const [isMobileView, setIsMobileView] = useState(false);
useEffect(() => {
setDocumentTitle(t("PortalAccess"));
checkWidth();
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
if (isMobile) return <MobileView />;
const checkWidth = () => {
window.innerWidth <= size.smallTablet
? setIsMobileView(true)
: setIsMobileView(false);
};
if (isMobileView) return <MobileView />;
return (
<MainContainer className="desktop-view">
<Text className="subtitle">{t("PortalAccessSubTitle")}</Text>
<CategoryWrapper
title={t("SettingPasswordStrength")}
tooltipContent={t("SettingPasswordStrengthDescription")}
/>
<PasswordStrengthSection />
<hr />
<CategoryWrapper
title={t("TwoFactorAuth")}
tooltipContent={t("TwoFactorAuthDescription")}

View File

@ -1,12 +1,9 @@
import React, { useEffect } from "react";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import Link from "@appserver/components/link";
import Text from "@appserver/components/text";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
import { setDocumentTitle } from "../../../../../../helpers/utils";
import { MainContainer, StyledArrowRightIcon } from "../StyledSecurity";
import { MainContainer } from "../StyledSecurity";
import MobileCategoryWrapper from "../sub-components/mobile-category-wrapper";
const MobileView = (props) => {
const { t, history } = props;
@ -22,25 +19,18 @@ const MobileView = (props) => {
return (
<MainContainer>
<div className="category-item-wrapper">
<div className="category-item-heading">
<Link
className="inherit-title-link header"
onClick={onClickLink}
truncate={true}
href={combineUrl(
AppServerConfig.proxyURL,
"/settings/security/access-portal/tfa"
)}
>
{t("TwoFactorAuth")}
</Link>
<StyledArrowRightIcon size="small" />
</div>
<Text className="category-item-description">
{t("TwoFactorAuthDescription")}
</Text>
</div>
<MobileCategoryWrapper
title={t("SettingPasswordStrength")}
subtitle={t("SettingPasswordStrengthDescription")}
url="/settings/security/access-portal/password"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("TwoFactorAuth")}
subtitle={t("TwoFactorAuthDescription")}
url="/settings/security/access-portal/tfa"
onClickLink={onClickLink}
/>
</MainContainer>
);
};

View File

@ -0,0 +1,267 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Box from "@appserver/components/box";
import Button from "@appserver/components/button";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import Slider from "@appserver/components/slider";
import Checkbox from "@appserver/components/checkbox";
import SectionLoader from "../sub-components/section-loader";
import { getLanguage } from "@appserver/common/utils";
import { ButtonsWrapper, LearnMoreWrapper } from "../StyledSecurity";
import toastr from "@appserver/components/toast/toastr";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.password-slider {
width: 160px;
height: 8px;
margin: 24px 16px 24px 0px;
}
.checkboxes {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 18px;
margin-bottom: 24px;
}
`;
const PasswordStrength = (props) => {
const { t, history, setPortalPasswordSettings, passwordSettings } = props;
const [passwordLen, setPasswordLen] = useState(8);
const [useUpperCase, setUseUpperCase] = useState(false);
const [useDigits, setUseDigits] = useState(false);
const [useSpecialSymbols, setUseSpecialSymbols] = useState(false);
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage("currentPasswordSettings");
const defaultSettings = getFromSessionStorage("defaultPasswordSettings");
if (defaultSettings) {
saveToSessionStorage("defaultPasswordSettings", defaultSettings);
} else {
const defaultData = {
minLength: passwordSettings.minLength,
upperCase: passwordSettings.upperCase,
digits: passwordSettings.digits,
specSymbols: passwordSettings.specSymbols,
};
saveToSessionStorage("defaultPasswordSettings", defaultData);
}
if (currentSettings) {
setPasswordLen(currentSettings.minLength);
setUseUpperCase(currentSettings.upperCase);
setUseDigits(currentSettings.digits);
setUseSpecialSymbols(currentSettings.specSymbols);
} else {
setPasswordLen(passwordSettings.minLength);
setUseUpperCase(passwordSettings.upperCase);
setUseDigits(passwordSettings.digits);
setUseSpecialSymbols(passwordSettings.specSymbols);
}
setIsLoading(true);
};
useEffect(() => {
checkWidth();
getSettings();
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage("defaultPasswordSettings");
const newSettings = {
minLength: passwordLen,
upperCase: useUpperCase,
digits: useDigits,
specSymbols: useSpecialSymbols,
};
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
saveToSessionStorage("currentPasswordSettings", newSettings);
setShowReminder(true);
}
}, [passwordLen, useUpperCase, useDigits, useSpecialSymbols]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("password") &&
history.push("/settings/security/access-portal");
};
const onSliderChange = (e) => {
setPasswordLen(Number(e.target.value));
};
const onClickCheckbox = (e) => {
switch (e.target.value) {
case "upperCase":
setUseUpperCase(e.target.checked);
break;
case "digits":
setUseDigits(e.target.checked);
break;
case "special":
setUseSpecialSymbols(e.target.checked);
break;
}
};
const onSaveClick = () => {
setPortalPasswordSettings(
passwordLen,
useUpperCase,
useDigits,
useSpecialSymbols
)
.then(() => {
setShowReminder(false);
const data = {
minLength: passwordLen,
upperCase: useUpperCase,
digits: useDigits,
specSymbols: useSpecialSymbols,
};
saveToSessionStorage("currentPasswordSettings", data);
saveToSessionStorage("defaultPasswordSettings", data);
toastr.success(t("SuccessfullySaveSettingsMessage"));
})
.catch((e) => toastr.error(e));
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage("defaultPasswordSettings");
saveToSessionStorage("currentPasswordSettings", defaultSettings);
setPasswordLen(defaultSettings.minLength);
setUseUpperCase(defaultSettings.upperCase);
setUseDigits(defaultSettings.digits);
setUseSpecialSymbols(defaultSettings.specSymbols);
setShowReminder(false);
};
const lng = getLanguage(localStorage.getItem("language") || "en");
if (!isLoading) return <SectionLoader />;
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">
{t("SettingPasswordStrengthHelper")}
</Text>
<Link
color="#316DAA"
target="_blank"
isHovered
href={`https://helpcenter.onlyoffice.com/${lng}/administration/configuration.aspx#ChangingSecuritySettings_block`}
>
{t("Common:LearnMore")}
</Link>
</LearnMoreWrapper>
<Text fontSize="14px" fontWeight="600" className="length-subtitle">
{t("PasswordMinLenght")}
</Text>
<Box displayProp="flex" flexDirection="row" alignItems="center">
<Slider
className="password-slider"
min="8"
max="30"
step="1"
withPouring={true}
value={passwordLen}
onChange={onSliderChange}
/>
<Text>
{t("Characters", {
length: passwordLen,
})}
</Text>
</Box>
<Box className="checkboxes">
<Checkbox
onChange={onClickCheckbox}
label={t("UseUpperCase")}
isChecked={useUpperCase}
value="upperCase"
/>
<Checkbox
onChange={onClickCheckbox}
label={t("UseDigits")}
isChecked={useDigits}
value="digits"
/>
<Checkbox
onChange={onClickCheckbox}
label={t("UseSpecialChar")}
isChecked={useSpecialSymbols}
value="special"
/>
</Box>
<ButtonsWrapper>
<Button
label={t("Common:SaveButton")}
size="small"
primary={true}
className="button"
onClick={onSaveClick}
isDisabled={!showReminder}
/>
<Button
label={t("Common:CancelButton")}
size="small"
className="button"
onClick={onCancelClick}
isDisabled={!showReminder}
/>
{showReminder && (
<Text
color="#A3A9AE"
fontSize="12px"
fontWeight="600"
className="reminder"
>
{t("YouHaveUnsavedChanges")}
</Text>
)}
</ButtonsWrapper>
</MainContainer>
);
};
export default inject(({ auth }) => {
const { setPortalPasswordSettings, passwordSettings } = auth.settingsStore;
return {
setPortalPasswordSettings,
passwordSettings,
};
})(
withTranslation(["Settings", "Common"])(
withRouter(observer(PasswordStrength))
)
);

View File

@ -10,7 +10,8 @@ import Link from "@appserver/components/link";
import toastr from "@appserver/components/toast/toastr";
import SectionLoader from "../sub-components/section-loader";
import { getLanguage } from "@appserver/common/utils";
import { isMobile } from "react-device-detect";
import { ButtonsWrapper, LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
const MainContainer = styled.div`
width: 100%;
@ -20,36 +21,12 @@ const MainContainer = styled.div`
}
.box {
margin-top: 20px;
margin-bottom: 24px;
}
`;
const ButtonsWrapper = styled.div`
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
@media (max-width: 375px) {
position: absolute;
bottom: 16px;
width: calc(100vw - 32px);
.button {
height: 40px;
width: 100%;
}
.reminder {
position: absolute;
bottom: 48px;
}
}
`;
const TwoFactorAuth = (props) => {
const { t } = props;
const { t, history } = props;
const [type, setType] = useState("none");
const [currentState, setCurrentState] = useState("");
const [smsDisabled, setSmsDisabled] = useState(false);
@ -66,13 +43,22 @@ const TwoFactorAuth = (props) => {
const settings = await getTfaSettings();
setSmsDisabled(settings[0].avaliable);
setAppDisabled(settings[1].avaliable);
setIsLoading(true);
};
useEffect(() => {
checkWidth();
getSettings();
setIsLoading(true);
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("tfa") &&
history.push("/settings/security/access-portal");
};
const onSelectTfaType = (e) => {
if (type !== e.target.value) {
setType(e.target.value);
@ -107,18 +93,17 @@ const TwoFactorAuth = (props) => {
if (!isLoading) return <SectionLoader />;
return (
<MainContainer>
{isMobile && (
<>
<Text className="page-subtitle">{t("TwoFactorAuthHelper")}</Text>
<Link
className="learn-more"
target="_blank"
href={`https://helpcenter.onlyoffice.com/${lng}/administration/two-factor-authentication.aspx`}
>
{t("Common:LearnMore")}
</Link>
</>
)}
<LearnMoreWrapper>
<Text className="page-subtitle">{t("TwoFactorAuthHelper")}</Text>
<Link
color="#316DAA"
target="_blank"
isHovered
href={`https://helpcenter.onlyoffice.com/${lng}/administration/two-factor-authentication.aspx`}
>
{t("Common:LearnMore")}
</Link>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"

View File

@ -0,0 +1,37 @@
import React from "react";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import { Base } from "@appserver/components/themes";
import {
StyledMobileCategoryWrapper,
StyledArrowRightIcon,
} from "../StyledSecurity";
import { combineUrl } from "@appserver/common/utils";
import { AppServerConfig } from "@appserver/common/constants";
const MobileCategoryWrapper = (props) => {
const { title, url, subtitle, onClickLink } = props;
return (
<StyledMobileCategoryWrapper>
<div className="category-item-heading">
<Link
className="inherit-title-link header"
onClick={onClickLink}
truncate={true}
href={combineUrl(AppServerConfig.proxyURL, url)}
>
{title}
</Link>
<StyledArrowRightIcon size="small" />
</div>
<Text className="category-item-description">{subtitle}</Text>
</StyledMobileCategoryWrapper>
);
};
MobileCategoryWrapper.defaultProps = {
theme: Base,
};
export default MobileCategoryWrapper;

View File

@ -8,6 +8,9 @@ import AppServerConfig from "@appserver/common/constants/AppServerConfig";
const SecuritySettings = lazy(() => import("./categories/security/index.js"));
const Admins = lazy(() => import("./categories/security/access-rights/admins"));
const TfaPage = lazy(() => import("./categories/security/access-portal/tfa"));
const PasswordStrengthPage = lazy(() =>
import("./categories/security/access-portal/passwordStrength")
);
const CommonSettings = lazy(() => import("./categories/common/index.js"));
@ -42,6 +45,8 @@ const WhiteLabel = lazy(() => import("./categories/common/whitelabel"));
const PROXY_BASE_URL = combineUrl(AppServerConfig.proxyURL, "/settings");
const COMMON_URLS = [
PROXY_BASE_URL,
combineUrl(PROXY_BASE_URL, "/common"),
combineUrl(PROXY_BASE_URL, "/common/customization"),
combineUrl(PROXY_BASE_URL, "/common/whitelabel"),
];
@ -69,6 +74,10 @@ const SECURITY_URLS = [
combineUrl(PROXY_BASE_URL, "/security/access-portal"),
];
const TFA_PAGE_URL = combineUrl(PROXY_BASE_URL, "/security/access-portal/tfa");
const PASSWORD_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/password"
);
const ADMINS_URL = combineUrl(PROXY_BASE_URL, "/security/access-rights/admins");
const THIRD_PARTY_URL = combineUrl(
@ -106,6 +115,11 @@ const Settings = () => {
<Route exact path={SECURITY_URLS} component={SecuritySettings} />
<Route path={ADMINS_URL} component={Admins} />
<Route exact path={TFA_PAGE_URL} component={TfaPage} />
<Route
exact
path={PASSWORD_PAGE_URL}
component={PasswordStrengthPage}
/>
<Route exact path={THIRD_PARTY_URL} component={ThirdPartyServices} />
<Route

View File

@ -62,6 +62,12 @@ export const settingsTree = [
{
key: "1-0-0",
icon: "",
link: "password",
tKey: "SettingPasswordStrength",
},
{
key: "1-0-1",
icon: "",
link: "tfa",
tKey: "TwoFactorAuth",
},

View File

@ -740,3 +740,58 @@ Scenario(
});
}
);
Scenario("Setting password strength change test success", async ({ I }) => {
I.mockEndpoint(Endpoints.settings, "settings");
I.mockEndpoint(Endpoints.password, "password");
I.mockEndpoint(Endpoints.build, "build");
I.mockEndpoint(Endpoints.info, "infoSettings");
I.mockEndpoint(Endpoints.self, "selfSettings");
if (deviceType === "mobile") {
I.amOnPage("/settings/security/access-portal/password");
I.see("Minimal password length");
I.click({
react: "Checkbox",
props: {
value: "digits",
},
});
I.see("You have unsaved changes");
I.click("Save");
I.see("Settings have been successfully updated");
}
});
Scenario("Setting password strength change test error", async ({ I }) => {
I.mockEndpoint(Endpoints.settings, "settings");
I.mockEndpoint(Endpoints.password, "password");
I.mockEndpoint(Endpoints.build, "build");
I.mockEndpoint(Endpoints.info, "infoSettings");
I.mockEndpoint(Endpoints.self, "selfSettings");
if (deviceType === "mobile") {
I.amOnPage("/settings/security/access-portal/password");
I.see("Minimal password length");
I.click({
react: "Checkbox",
props: {
value: "digits",
},
});
I.see("You have unsaved changes");
I.mockEndpoint(Endpoints.password, "passwordError");
I.click("Save");
I.see("Error");
}
});

View File

@ -45,7 +45,7 @@ module.exports = class Endpoints {
static password = {
url: ["http://localhost:8092/api/2.0/settings/security/password"],
method: "GET",
method: ["GET", "PUT"],
baseDir: "settings",
};
@ -129,4 +129,10 @@ module.exports = class Endpoints {
method: "GET",
baseDir: "settings",
};
static passwordError = {
url: ["http://localhost:8092/api/2.0/settings/password"],
method: "PUT",
baseDir: "settings",
};
};

View File

@ -0,0 +1,5 @@
{
"error": { "message": "Error", "hresult": 0 },
"status": 1,
"statusCode": 500
}