Merge pull request #723 from ONLYOFFICE/feature/security-settings

Feature/security settings
This commit is contained in:
Alexey Safronov 2022-07-12 14:16:53 +03:00 committed by GitHub
commit df65b26ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1780 additions and 162 deletions

View File

@ -55,18 +55,32 @@ export function setDNSSettings(dnsName, enable) {
});
}
export function getIpRestrictions() {
return request({
method: "get",
url: "/settings/iprestrictions",
});
}
export function setIpRestrictions(data) {
return request({
method: "put",
url: "/settings/iprestrictions.json",
url: "/settings/iprestrictions",
data,
});
}
export function getIpRestrictionsEnable() {
return request({
method: "get",
url: "/settings/iprestrictions/settings",
});
}
export function setIpRestrictionsEnable(data) {
return request({
method: "put",
url: "/settings/iprestrictions/settings.json",
url: "/settings/iprestrictions/settings",
data,
});
}
@ -87,6 +101,13 @@ export function setCookieSettings(lifeTime) {
});
}
export function getCookieSettings() {
return request({
method: "get",
url: "/settings/cookiesettings.json",
});
}
export function setLifetimeAuditSettings(data) {
return request({
method: "post",

View File

@ -36,6 +36,9 @@ class SettingsStore {
: Base;
trustedDomains = [];
trustedDomainsType = 0;
ipRestrictionEnable = false;
ipRestrictions = [];
sessionLifetime = "1440";
timezone = "UTC";
timezones = [];
tenantAlias = "";
@ -474,6 +477,47 @@ class SettingsStore {
this.tenantAlias = tenantAlias;
};
getIpRestrictions = async () => {
const res = await api.settings.getIpRestrictions();
this.ipRestrictions = res?.map((el) => el.ip);
};
setIpRestrictions = async (ips) => {
const data = {
ips: ips,
};
const res = await api.settings.setIpRestrictions(data);
this.ipRestrictions = res;
};
getIpRestrictionsEnable = async () => {
const res = await api.settings.getIpRestrictionsEnable();
this.ipRestrictionEnable = res.enable;
};
setIpRestrictionsEnable = async (enable) => {
const data = {
enable: enable,
};
const res = await api.settings.setIpRestrictionsEnable(data);
this.ipRestrictionEnable = res.enable;
};
setMessageSettings = async (turnOn) => {
await api.settings.setMessageSettings(turnOn);
this.enableAdmMess = turnOn;
};
getSessionLifetime = async () => {
const res = await api.settings.getCookieSettings();
this.sessionLifetime = res;
};
setSessionLifetimeSettings = async (lifeTime) => {
const res = await api.settings.setCookieSettings(lifeTime);
this.sessionLifetime = lifeTime;
};
setIsBurgerLoading = (isBurgerLoading) => {
this.isBurgerLoading = isBurgerLoading;
};

View File

@ -1926,7 +1926,7 @@ const Base = {
header: {
backgroundColor: "#0F4071",
recoveryColor: "#27537F",
linkColor: "#7a95b0",
productColor: white,
},

View File

@ -1926,7 +1926,7 @@ const Dark = {
header: {
backgroundColor: "#1f1f1f ",
recoveryColor: "#4C4C4C",
linkColor: "#606060",
productColor: "#eeeeee",
},

View File

@ -1,6 +1,6 @@
export const size = {
mobile: 375,
hugeMobile: 414,
hugeMobile: 428,
smallTablet: 600,
tablet: 1024,
desktop: 1025,

View File

@ -8,12 +8,16 @@
"AccessRightsUsersFromList": "{{users}} from the list",
"AccessSettings": "Access settings",
"AddAdmins": "Add admins",
"AddAllowedIP": "Add allowed IP address",
"AddName": "Add Name",
"AddTrustedDomain": "Add trusted domain",
"AdminInModules": "Admin in modules",
"AdministratorsAddedSuccessfully": "Administrators added successfully",
"AdministratorsRemovedSuccessfully": "Administrators removed successfully",
"Admins": "Admins",
"AdminsMessage": "Administrator Message Settings",
"AdminsMessageDescription": "Administrator Message Settings is a way to contact the portal administrator.",
"AdminsMessageHelper": "Enable this option to display the contact form on the Sign In page so that people could send the message to the portal administrator in case they have troubles accessing the portal.",
"AllDomains": "Any domains",
"AutoBackup": "Automatic backup",
"AutoBackupDescription": "Use this option for automatic backup of the portal data.",
@ -67,11 +71,16 @@
"GroupLead": "Group Lead",
"Groups": "Groups",
"Guests": "Guests",
"IPSecurity": "IP Security",
"IPSecurityDescription": "IP Security is used to restrict login to the portal from all IP addresses except certain addresses.",
"IPSecurityHelper": "You can set the allowed IP addresses using either exact IP addresses in the IPv4 format (#.#.#.#, where # is a numeric value from 0 to 255) or IP range (in the #.#.#.#-#.#.#.# format).",
"IPSecurityWarningHelper": "First you need to specify your current IP or the IP range your current IP address belongs to, otherwise your portal access will be blocked right after you save the settings. The portal owner will have the portal access from any IP address.",
"Job/Title": "Job/Title",
"LanguageAndTimeZoneSettingsDescription": "Language and Time Zone Settings is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the ONLYOFFICE portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltipDescription": "To make the parameters you set take effect click the <1>{{save}}</1> button at the bottom of the section.<3>{{learnMore}}</3>",
"LocalFile": "Local file",
"Lifetime": "Lifetime (min)",
"LogoDark": "Logo for the About/Login page",
"LogoDocsEditor": "Logo for the editors header",
"LogoFavicon": "Favicon",
@ -122,6 +131,11 @@
"SendNotificationAboutRestoring": "Send notification about portal restoring to users",
"ServerSideEncryptionMethod": "Server Side Encryption Method",
"ServiceUrl": "Service Url",
"RecoveryFileNotSelected": "Recovery error. Recovery file not selected",
"SessionLifetime": "Session Lifetime",
"SessionLifetimeDescription": "Session Lifetime allows to set time (in minutes) before the portal users will need to enter the portal credentials again in order to access the portal.",
"SessionLifetimeHelper": "After save all the users will be logged out from portal.",
"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.",

View File

@ -8,12 +8,16 @@
"AccessRightsUsersFromList": "Участников со статусом {{users}} из списка",
"AccessSettings": "Настройки доступа",
"AddAdmins": "Добавить администраторов",
"AddAllowedIP": "Добавить разрешенный IP-адрес",
"AddName": "Добавьте наименование",
"AddTrustedDomain": "Добавить доверенный домен",
"AdminInModules": "Администратор в модулях",
"AdministratorsAddedSuccessfully": "Администраторы успешно добавлены",
"AdministratorsRemovedSuccessfully": "Администраторы успешно удалены",
"Admins": "Администраторы",
"AdminsMessage": "Настройки сообщений администратору",
"AdminsMessageDescription": "Настройки сообщений администратору это способ связаться с администратором портала.",
"AdminsMessageHelper": "Включите эту опцию для отображения формы связи на странице Входа, чтобы пользователи могли отправить сообщение администратору портала в случае, если у них возникают проблемы со входом на портал.",
"AllDomains": "Любые домены",
"AutoBackup": "Автоматическое резервное копирование",
"AutoBackupDescription": "Используйте эту опцию для автоматического выполнения резервного копирования данных портала.",
@ -67,6 +71,10 @@
"GroupLead": "Руководитель группы",
"Groups": "Группы",
"Guests": "Гости",
"IPSecurity": "IP-безопасность",
"IPSecurityDescription": "Настройки IP-безопасности используются для ограничения возможности входа на портал со всех IP-адресов, кроме указанных.",
"IPSecurityHelper": "Вы можете задать разрешенные IP-адреса, указав конкретные значения IP-адресов в формате IPv4 (#.#.#.#, где # - это число от 0 до 255) или диапазон IP-адресов (в формате #.#.#.#-#.#.#.#).",
"IPSecurityWarningHelper": "Первым необходимо указать ваш текущий IP-адрес или диапазон IP-адресов, в который входит ваш текущий IP, иначе после сохранения настроек Вам будет заблокирован доступ к порталу. Владелец портала будет иметь доступ с любого IP-адреса.",
"Job/Title": "Должность/Позиция",
"LanguageAndTimeZoneSettingsDescription": "Настройки языка и часового пояса позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале отображались с корректной датой и временем.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале ONLYOFFICE отображались с корректной датой и временем.",

View File

@ -13,10 +13,9 @@ const Header = styled.header`
align-items: left;
background-color: ${(props) => props.theme.header.backgroundColor};
display: flex;
width: calc(100vw - 64px);
width: 100vw;
height: 48px;
justify-content: left;
padding: 0 32px;
justify-content: center;
.header-items-wrapper {
width: 960px;
@ -25,7 +24,7 @@ const Header = styled.header`
width: 475px;
}
@media (max-width: 375px) {
width: 311px;
padding: 0 16px;
}
}
@ -57,7 +56,7 @@ const HeaderUnAuth = ({
isAuthenticated,
isLoaded,
}) => {
const { t } = useTranslation();
const { t } = useTranslation("NavMenu");
return (
<Header isLoaded={isLoaded} className="navMenuHeaderUnAuth">
@ -110,7 +109,7 @@ export default inject(({ auth }) => {
const { enableAdmMess, wizardToken } = settingsStore;
return {
enableAdmMess,
wizardToken: wizardToken || "/",
wizardToken,
isAuthenticated,
isLoaded,
};

View File

@ -9,6 +9,7 @@ import UnionIcon from "../svg/union.react.svg";
import RecoverAccessModalDialog from "./recover-access-modal-dialog";
import { sendRecoverRequest } from "@appserver/common/api/settings/index";
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
import { Base } from "@appserver/components/themes";
const StyledUnionIcon = styled(UnionIcon)`
${commonIconsStyles}
@ -16,6 +17,7 @@ const StyledUnionIcon = styled(UnionIcon)`
const RecoverContainer = styled(Box)`
cursor: pointer;
background-color: ${(props) => props.theme.header.recoveryColor};
.recover-icon {
@media (max-width: 450px) {
@ -29,6 +31,8 @@ const RecoverContainer = styled(Box)`
}
`;
RecoverContainer.defaultProps = { theme: Base };
const RecoverAccess = ({ t }) => {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
@ -88,7 +92,6 @@ const RecoverAccess = ({ t }) => {
alignItems="center"
>
<RecoverContainer
backgroundProp="#27537F"
heightProp="100%"
displayProp="flex"
onClick={onRecoverClick}

View File

@ -12,12 +12,11 @@ import Box from "@appserver/components/box";
import withLoader from "../withLoader";
import toastr from "studio/toastr";
import ErrorContainer from "@appserver/common/components/ErrorContainer";
import { mobile, tablet } from "@appserver/components/utils/device";
import { hugeMobile, tablet } from "@appserver/components/utils/device";
import Link from "@appserver/components/link";
const StyledForm = styled(Box)`
margin: 63px auto auto 216px;
width: 960px;
display: flex;
flex: 1fr 1fr;
gap: 80px;
@ -31,9 +30,9 @@ const StyledForm = styled(Box)`
gap: 32px;
}
@media ${mobile} {
margin: 72px 16px auto 8px;
width: 311px;
@media ${hugeMobile} {
margin-top: 72px;
width: 100%;
flex: 1fr;
flex-direction: column;
gap: 0px;
@ -45,6 +44,11 @@ const StyledForm = styled(Box)`
}
}
.set-app-description {
width: 100%;
max-width: 500px;
}
.set-app-title {
margin-bottom: 14px;
}
@ -62,7 +66,7 @@ const StyledForm = styled(Box)`
border-radius: 6px;
margin-bottom: 32px;
@media ${mobile} {
@media ${hugeMobile} {
display: none;
}
}
@ -106,23 +110,17 @@ const TfaActivationForm = withLoader((props) => {
if (target.code === "Enter" || target.code === "NumpadEnter") onSubmit();
};
const width = window.innerWidth;
return (
<Section>
<Section.SectionBody>
<StyledForm className="set-app-container">
<div>
<Box className="set-app-description" marginProp="0 0 32px 0">
<Text isBold fontSize="14px" className="set-app-title">
{t("SetAppTitle")}
</Text>
<Trans t={t} i18nKey="SetAppDescription" ns="Confirm">
The two-factor authentication is enabled to provide additional
portal security. Configure your authenticator application to
continue work on the portal. For example you could use Google
Authenticator for
The two-factor authentication is enabled to provide additional portal
security. Configure your authenticator application to continue work on
the portal. For example you could use Google Authenticator for
<Link isHovered href={props.tfaAndroidAppUrl} target="_blank">
Android
</Link>
@ -144,26 +142,19 @@ const TfaActivationForm = withLoader((props) => {
ns="Confirm"
key={secretKey}
>
To connect your apllication scan the QR code or manually enter
your secret key <strong>{{ secretKey }}</strong> then enter
6-digit code from your application in the field below.
To connect your apllication scan the QR code or manually enter your
secret key <strong>{{ secretKey }}</strong> then enter 6-digit code
from your application in the field below.
</Trans>
</Text>
</Box>
</div>
<div>
<Box
displayProp="flex"
flexDirection="column"
className="app-code-wrapper"
>
<div className="qrcode-wrapper">
<img
src={qrCode}
height="180px"
width="180px"
alt="QR-code"
></img>
<img src={qrCode} height="180px" width="180px" alt="QR-code"></img>
</div>
<Box className="app-code-input">
<FieldContainer
@ -199,9 +190,7 @@ const TfaActivationForm = withLoader((props) => {
size="medium"
tabIndex={3}
label={
isLoading
? t("Common:LoadingProcessing")
: t("SetAppButton")
isLoading ? t("Common:LoadingProcessing") : t("SetAppButton")
}
isDisabled={!code.length || isLoading}
isLoading={isLoading}
@ -209,10 +198,7 @@ const TfaActivationForm = withLoader((props) => {
/>
</Box>
</Box>
</div>
</StyledForm>
</Section.SectionBody>
</Section>
);
});
@ -233,7 +219,7 @@ const TfaActivationWrapper = (props) => {
setSecretKey(manualEntryKey);
setQrCode(qrCodeSetupImageUrl);
} catch (e) {
setError(e);
setError(e.error);
toastr.error(e);
}
@ -244,7 +230,11 @@ const TfaActivationWrapper = (props) => {
return error ? (
<ErrorContainer bodyText={error} />
) : (
<Section>
<Section.SectionBody>
<TfaActivationForm secretKey={secretKey} qrCode={qrCode} {...props} />
</Section.SectionBody>
</Section>
);
};

View File

@ -11,7 +11,11 @@ import { inject, observer } from "mobx-react";
import Box from "@appserver/components/box";
import toastr from "studio/toastr";
import withLoader from "../withLoader";
import { mobile, tablet } from "@appserver/components/utils/device";
import {
hugeMobile,
smallTablet,
tablet,
} from "@appserver/components/utils/device";
const StyledForm = styled(Box)`
margin: 63px auto;
@ -24,9 +28,15 @@ const StyledForm = styled(Box)`
margin: 120px auto;
width: 480px;
}
@media ${mobile} {
margin: 72px 16px auto 8px;
width: 311px;
@media ${smallTablet} {
width: 400px;
}
@media ${hugeMobile} {
margin: 72px 8px auto 8px;
padding: 16px;
width: 100%;
}
.app-code-wrapper {

View File

@ -0,0 +1,183 @@
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 RadioButtonGroup from "@appserver/components/radio-button-group";
import Text from "@appserver/components/text";
import Link from "@appserver/components/link";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import isEqual from "lodash/isEqual";
import { isMobile } from "react-device-detect";
import AdmMsgLoader from "../sub-components/loaders/admmsg-loader";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.box {
margin-bottom: 24px;
}
`;
const AdminMessage = (props) => {
const {
t,
history,
enableAdmMess,
setMessageSettings,
initSettings,
isInit,
helpLink,
} = props;
const [type, setType] = useState("");
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage(
"currentAdminMessageSettings"
);
const enable = enableAdmMess ? "enable" : "disabled";
saveToSessionStorage("defaultAdminMessageSettings", enable);
if (currentSettings) {
setType(currentSettings);
} else {
setType(enable);
}
};
useEffect(() => {
checkWidth();
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage(
"defaultAdminMessageSettings"
);
saveToSessionStorage("currentAdminMessageSettings", type);
if (isEqual(defaultSettings, type)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [type]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("admin-message") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
if (type !== e.target.value) {
setType(e.target.value);
}
};
const onSaveClick = () => {
const turnOn = type === "enable" ? true : false;
setMessageSettings(turnOn);
toastr.success(t("SuccessfullySaveSettingsMessage"));
saveToSessionStorage("defaultAdminMessageSettings", type);
setShowReminder(false);
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage(
"defaultAdminMessageSettings"
);
setType(defaultSettings);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <AdmMsgLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">{t("AdminsMessageHelper")}</Text>
<Link
color="#316DAA"
target="_blank"
isHovered
href={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
>
{t("Common:LearnMore")}
</Link>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={type}
onClick={onSelectType}
/>
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const { enableAdmMess, setMessageSettings, helpLink } = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
enableAdmMess,
setMessageSettings,
initSettings,
isInit,
helpLink,
};
})(withTranslation(["Settings", "Common"])(withRouter(observer(AdminMessage))));

View File

@ -7,6 +7,9 @@ import { MainContainer } from "../StyledSecurity";
import TfaSection from "./tfa";
import PasswordStrengthSection from "./passwordStrength";
import TrustedMailSection from "./trustedMail";
import IpSecuritySection from "./ipSecurity";
import AdminMessageSection from "./adminMessage";
import SessionLifetimeSection from "./sessionLifetime";
import MobileView from "./mobileView";
import CategoryWrapper from "../sub-components/category-wrapper";
import { size } from "@appserver/components/utils/device";
@ -56,6 +59,30 @@ const AccessPortal = (props) => {
tooltipUrl={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
/>
<TrustedMailSection />
<hr />
<CategoryWrapper
t={t}
title={t("IPSecurity")}
tooltipContent={t("IPSecurityDescription")}
tooltipTitle={t("IPSecurityDescription")}
/>
<IpSecuritySection />
<hr />
<CategoryWrapper
t={t}
title={t("AdminsMessage")}
tooltipTitle={t("AdminsMessageDescription")}
tooltipUrl={`${helpLink}/administration/configuration.aspx#ChangingSecuritySettings_block`}
/>
<AdminMessageSection />
<hr />
<CategoryWrapper
t={t}
title={t("SessionLifetime")}
tooltipTitle={t("SessionLifetimeDescription")}
/>
<SessionLifetimeSection />
</MainContainer>
);
};

View File

@ -0,0 +1,260 @@
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 Text from "@appserver/components/text";
import RadioButtonGroup from "@appserver/components/radio-button-group";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import UserFields from "../sub-components/user-fields";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import IpSecurityLoader from "../sub-components/loaders/ip-security-loader";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.user-fields {
margin-bottom: 18px;
}
.box {
margin-bottom: 11px;
}
.warning-text {
margin-bottom: 9px;
}
.save-cancel-buttons {
margin-top: 24px;
}
`;
const IpSecurity = (props) => {
const {
t,
history,
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
} = props;
const regexp = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/; //check ip valid
const [enable, setEnable] = useState(false);
const [ips, setIps] = useState();
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage("currentIPSettings");
const defaultData = {
enable: ipRestrictionEnable,
ips: ipRestrictions,
};
saveToSessionStorage("defaultIPSettings", defaultData);
if (currentSettings) {
setEnable(currentSettings.enable);
setIps(currentSettings.ips);
} else {
setEnable(ipRestrictionEnable);
setIps(ipRestrictions);
}
};
useEffect(() => {
checkWidth();
window.addEventListener("resize", checkWidth);
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage("defaultIPSettings");
const newSettings = {
enable: enable,
ips: ips,
};
saveToSessionStorage("currentIPSettings", newSettings);
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [enable, ips]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("ip") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
setEnable(e.target.value === "enable" ? true : false);
};
const onChangeInput = (e, index) => {
let newInputs = Array.from(ips);
newInputs[index] = e.target.value;
setIps(newInputs);
};
const onDeleteInput = (index) => {
let newInputs = Array.from(ips);
newInputs.splice(index, 1);
setIps(newInputs);
};
const onClickAdd = () => {
setIps([...ips, ""]);
};
const onSaveClick = async () => {
setIsSaving(true);
const valid = ips.map((ip) => regexp.test(ip));
if (valid.includes(false)) {
setIsSaving(false);
return;
}
try {
await setIpRestrictions(ips);
await setIpRestrictionsEnable(enable);
saveToSessionStorage("defaultIPSettings", {
enable: enable,
ips: ips,
});
setShowReminder(false);
toastr.success(t("SuccessfullySaveSettingsMessage"));
} catch (error) {
toastr.error(error);
}
setIsSaving(false);
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage("defaultIPSettings");
setEnable(defaultSettings.enable);
setIps(defaultSettings.ips);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <IpSecurityLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">{t("IPSecurityHelper")}</Text>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={enable ? "enable" : "disabled"}
onClick={onSelectType}
/>
{enable && (
<UserFields
className="user-fields"
inputs={ips}
buttonLabel={t("AddAllowedIP")}
onChangeInput={onChangeInput}
onDeleteInput={onDeleteInput}
onClickAdd={onClickAdd}
regexp={regexp}
/>
)}
{enable && (
<>
<Text
color="#F21C0E"
fontSize="16px"
fontWeight="700"
className="warning-text"
>
{t("Common:Warning")}!
</Text>
<Text>{t("IPSecurityWarningHelper")}</Text>
</>
)}
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
isSaving={isSaving}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
} = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
};
})(withTranslation(["Settings", "Common"])(withRouter(observer(IpSecurity))));

View File

@ -37,6 +37,24 @@ const MobileView = (props) => {
url="/settings/security/access-portal/trusted-mail"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("IPSecurity")}
subtitle={t("IPSecurityDescription")}
url="/settings/security/access-portal/ip"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("AdminsMessage")}
subtitle={t("AdminsMessageDescription")}
url="/settings/security/access-portal/admin-message"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("SessionLifetime")}
subtitle={t("SessionLifetimeDescription")}
url="/settings/security/access-portal/lifetime"
onClickLink={onClickLink}
/>
</MainContainer>
);
};

View File

@ -14,6 +14,8 @@ import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import PasswordLoader from "../sub-components/loaders/password-loader";
const MainContainer = styled.div`
width: 100%;
@ -175,6 +177,10 @@ const PasswordStrength = (props) => {
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <PasswordLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -0,0 +1,230 @@
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 RadioButtonGroup from "@appserver/components/radio-button-group";
import Text from "@appserver/components/text";
import TextInput from "@appserver/components/text-input";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import isEqual from "lodash/isEqual";
import { isMobile } from "react-device-detect";
import SessionLifetimeLoader from "../sub-components/loaders/session-lifetime-loader";
const MainContainer = styled.div`
width: 100%;
.lifetime {
margin-top: 16px;
margin-bottom: 8px;
}
.save-cancel-buttons {
margin-top: 24px;
}
`;
const SessionLifetime = (props) => {
const {
t,
history,
lifetime,
setSessionLifetimeSettings,
initSettings,
isInit,
} = props;
const [type, setType] = useState(false);
const [sessionLifetime, setSessionLifetime] = useState("0");
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage(
"currentSessionLifetimeSettings"
);
const defaultData = {
lifetime: lifetime.toString(),
type: lifetime > 0 ? true : false,
};
saveToSessionStorage("defaultSessionLifetimeSettings", defaultData);
if (currentSettings) {
setSessionLifetime(currentSettings.lifetime);
setType(currentSettings.type);
} else {
setSessionLifetime(lifetime.toString());
setType(lifetime > 0 ? true : false);
}
if (currentSettings) {
setType(currentSettings.type);
setSessionLifetime(currentSettings.lifetime);
} else {
setType(lifetime > 0 ? true : false);
setSessionLifetime(lifetime.toString());
}
setIsLoading(true);
};
useEffect(() => {
checkWidth();
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
window.addEventListener("resize", checkWidth);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage(
"defaultSessionLifetimeSettings"
);
const newSettings = {
lifetime: type === false ? "0" : sessionLifetime,
type: type,
};
saveToSessionStorage("currentSessionLifetimeSettings", newSettings);
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [type, sessionLifetime]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("lifetime") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
setType(e.target.value === "enable" ? true : false);
};
const onChangeInput = (e) => {
setSessionLifetime(e.target.value);
};
const onBlurInput = () => {
!sessionLifetime ? setError(true) : setError(false);
};
const onFocusInput = () => {
setError(false);
};
const onSaveClick = async () => {
if (error) return;
try {
const lft = type === false ? "0" : sessionLifetime;
setSessionLifetimeSettings(lft);
toastr.success(t("SuccessfullySaveSettingsMessage"));
saveToSessionStorage("defaultSessionLifetimeSettings", {
lifetime: lft,
type: type,
});
setShowReminder(false);
} catch (error) {
toastr.error(error);
}
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage(
"defaultSessionLifetimeSettings"
);
setType(defaultSettings.type);
setSessionLifetime(defaultSettings.lifetime);
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <SessionLifetimeLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>
<Text>{t("SessionLifetimeHelper")}</Text>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={type ? "enable" : "disabled"}
onClick={onSelectType}
/>
{type && (
<>
<Text className="lifetime" fontSize="15px" fontWeight="600">
{t("Lifetime")}
</Text>
<TextInput
isAutoFocussed={false}
value={sessionLifetime}
onChange={onChangeInput}
onBlur={onBlurInput}
onFocus={onFocusInput}
hasError={error}
/>
</>
)}
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const { sessionLifetime, setSessionLifetimeSettings } = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
lifetime: sessionLifetime,
setSessionLifetimeSettings,
initSettings,
isInit,
};
})(
withTranslation(["Settings", "Common"])(withRouter(observer(SessionLifetime)))
);

View File

@ -11,6 +11,8 @@ import { LearnMoreWrapper } from "../StyledSecurity";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import TfaLoader from "../sub-components/loaders/tfa-loader";
const MainContainer = styled.div`
width: 100%;
@ -115,6 +117,11 @@ const TwoFactorAuth = (props) => {
setShowReminder(false);
};
if (isMobile && !isInit && !isLoading) {
return <TfaLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -13,6 +13,8 @@ import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
import { isMobile } from "react-device-detect";
import TrustedMailLoader from "../sub-components/loaders/trusted-mail-loader";
const MainContainer = styled.div`
width: 100%;
@ -150,6 +152,11 @@ const TrustedMail = (props) => {
setShowReminder(false);
};
if (isMobile && !isLoading) {
return <TrustedMailLoader />;
}
return (
<MainContainer>
<LearnMoreWrapper>

View File

@ -9,7 +9,11 @@ import config from "../../../../../../package.json";
import AccessRights from "./access-rights/index.js";
import AccessPortal from "./access-portal/index.js";
import AppLoader from "@appserver/common/components/AppLoader";
import SecurityLoader from "./sub-components/loaders/security-loader";
import MobileSecurityLoader from "./sub-components/loaders/mobile-security-loader";
import AccessLoader from "./sub-components/loaders/access-loader";
import { isMobile } from "react-device-detect";
const SecurityWrapper = (props) => {
const { t, history, loadBaseInfo } = props;
@ -31,13 +35,14 @@ const SecurityWrapper = (props) => {
const load = async () => {
await loadBaseInfo();
const path = location.pathname;
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
setIsLoading(true);
};
useEffect(() => {
const path = location.pathname;
const currentTab = data.findIndex((item) => path.includes(item.id));
if (currentTab !== -1) setCurrentTab(currentTab);
load();
}, []);
@ -51,7 +56,16 @@ const SecurityWrapper = (props) => {
);
};
if (!isLoading) return <AppLoader />;
if (!isLoading)
return currentTab === 0 ? (
isMobile ? (
<MobileSecurityLoader />
) : (
<SecurityLoader />
)
) : (
<AccessLoader />
);
return (
<Submenu
data={data}

View File

@ -10,10 +10,12 @@ const CategoryWrapper = (props) => {
const tooltip = () => (
<StyledTooltip>
<Text className="subtitle">{tooltipTitle}</Text>
<Text className={tooltipUrl ? "subtitle" : ""}>{tooltipTitle}</Text>
{tooltipUrl && (
<Link target="_blank" isHovered href={tooltipUrl}>
{t("Common:LearnMore")}
</Link>
)}
</StyledTooltip>
);

View File

@ -0,0 +1,86 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
import { tablet } from "@appserver/components/utils/device";
const StyledLoader = styled.div`
.header {
width: 296px;
height: 29px;
margin-bottom: 14px;
@media (${tablet}) {
width: 184px;
height: 37px;
}
@media (max-width: 428px) {
width: 273px;
height: 37px;
margin-bottom: 18px;
}
}
.submenu {
display: flex;
gap: 20px;
margin-bottom: 22px;
}
.owner {
width: 700px;
display: flex;
flex-direction: column;
gap: 20px;
margin-bottom: 40px;
@media (max-width: 428px) {
width: 100%;
}
.header {
height: 40px;
@media (${tablet}) {
height: 60px;
}
}
}
.admins {
display: flex;
flex-direction: column;
gap: 8px;
.description {
width: 700px;
@media (${tablet}) {
width: 100%;
}
}
}
`;
const AccessLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="100%" />
<div className="submenu">
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
</div>
<div className="owner">
<Loaders.Rectangle className="header" height="100%" />
<Loaders.Rectangle height="82px" />
</div>
<div className="admins">
<Loaders.Rectangle height="22px" width="77px" />
<Loaders.Rectangle height="20px" width="56px" />
<Loaders.Rectangle className="description" height="40px" />
</div>
</StyledLoader>
);
};
export default AccessLoader;

View File

@ -0,0 +1,55 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 11px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const AdmMsgLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default AdmMsgLoader;

View File

@ -0,0 +1,66 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 12px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 15px;
}
.add-button {
width: 85px;
margin-bottom: 16px;
}
.block {
display: flex;
flex-direction: column;
gap: 8px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const IpSecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="add-button" height="20px" />
<div className="block">
<Loaders.Rectangle height="22px" width="72px" />
<Loaders.Rectangle height="64px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default IpSecurityLoader;

View File

@ -0,0 +1,59 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
.header {
width: 273px;
margin-bottom: 18px;
}
.submenu {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.category {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 30px;
}
`;
const MobileSecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<div className="submenu">
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
<Loaders.Rectangle height="28px" width="72px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="236px" />
<Loaders.Rectangle height="60px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="227px" />
<Loaders.Rectangle height="120px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" />
<Loaders.Rectangle height="40px" />
</div>
<div className="category">
<Loaders.Rectangle height="22px" width="101px" />
<Loaders.Rectangle height="40px" />
</div>
</StyledLoader>
);
};
export default MobileSecurityLoader;

View File

@ -0,0 +1,70 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.subheader {
margin-bottom: 16px;
}
.slider {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const PasswordLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="80px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<Loaders.Rectangle className="subheader" height="16px" width="171px" />
<div className="slider">
<Loaders.Rectangle height="24px" width="160px" />
<Loaders.Rectangle height="20px" width="75px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default PasswordLoader;

View File

@ -0,0 +1,188 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
import { tablet } from "@appserver/components/utils/device";
const StyledLoader = styled.div`
hr {
margin: 24px 0;
border: none;
border-top: 1px solid #eceef1;
}
.submenu {
width: 296px;
height: 29px;
margin-bottom: 14px;
@media (${tablet}) {
width: 184px;
height: 37px;
}
}
.header {
display: flex;
margin-bottom: 22px;
.header-item {
width: 72px;
margin-right: 20px;
}
}
.description {
width: 591px;
margin-bottom: 20px;
@media (${tablet}) {
width: 100%;
}
}
.buttons {
width: 192px;
height: 32px;
@media (${tablet}) {
height: 40px;
}
}
.password-settings {
.header {
width: 132px;
margin-bottom: 16px;
}
.subheader {
width: 171px;
margin-bottom: 16px;
}
.slider {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 16px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
}
.tfa-settings {
.header {
width: 227px;
margin-bottom: 16px;
}
.radio-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 24px;
}
}
.domain-settings {
.header {
width: 132px;
margin-bottom: 16px;
}
.radio-buttons {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 11px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
.input {
display: flex;
gap: 8px;
align-items: center;
}
}
.button {
width: 85px;
}
}
`;
const SecurityLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="submenu" height="100%" />
<div className="header">
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
<Loaders.Rectangle className="header-item" height="28px" />
</div>
<Loaders.Rectangle className="description" height="20px" />
<div className="password-settings">
<Loaders.Rectangle className="header" height="22px" />
<Loaders.Rectangle className="subheader" height="16px" />
<div className="slider">
<Loaders.Rectangle height="24px" width="160px" />
<Loaders.Rectangle height="20px" width="75px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" width="133px" />
<Loaders.Rectangle height="20px" width="83px" />
<Loaders.Rectangle height="20px" width="159px" />
</div>
<Loaders.Rectangle className="buttons" height="100%" />
</div>
<hr />
<div className="tfa-settings">
<Loaders.Rectangle className="header" height="22px" />
<div className="radio-buttons">
<Loaders.Rectangle height="20px" width="69px" />
<Loaders.Rectangle height="20px" width="69px" />
<Loaders.Rectangle height="20px" width="152px" />
</div>
<Loaders.Rectangle className="buttons" height="100%" />
</div>
<hr />
<div className="domain-settings">
<Loaders.Rectangle className="header" height="22px" />
<div className="radio-buttons">
<Loaders.Rectangle height="20px" width="77px" />
<Loaders.Rectangle height="20px" width="103px" />
<Loaders.Rectangle height="20px" width="127px" />
</div>
<div className="inputs">
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" width="350px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<Loaders.Rectangle className="button" height="20px" />
</div>
</div>
</StyledLoader>
);
};
export default SecurityLoader;

View File

@ -0,0 +1,59 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 12px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 16px;
}
.input {
display: flex;
flex-direction: column;
gap: 4px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const SessionLifetimeLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="20px" />
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<div className="input">
<Loaders.Rectangle height="20px" width="95px" />
<Loaders.Rectangle height="32px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default SessionLifetimeLoader;

View File

@ -0,0 +1,55 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const TfaLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="40px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default TfaLoader;

View File

@ -0,0 +1,90 @@
import React from "react";
import styled from "styled-components";
import Loaders from "@appserver/common/components/Loaders";
const StyledLoader = styled.div`
padding-right: 8px;
.header {
width: 273px;
margin-bottom: 16px;
}
.description {
margin-bottom: 8px;
}
.link {
margin-bottom: 20px;
}
.checkboxs {
display: flex;
flex-direction: column;
gap: 8px;
width: 50px;
margin-bottom: 11px;
}
.inputs {
display: flex;
flex-direction: column;
gap: 8px;
.input {
display: flex;
gap: 8px;
align-items: center;
}
.add {
width: 85px;
margin-top: 16px;
}
}
.buttons {
width: calc(100% - 32px);
position: absolute;
bottom: 16px;
}
`;
const TrustedMailLoader = () => {
return (
<StyledLoader>
<Loaders.Rectangle className="header" height="37px" />
<Loaders.Rectangle className="description" height="100px" />
<div className="link">
<Loaders.Rectangle height="20px" width="57px" />
</div>
<div className="checkboxs">
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
<Loaders.Rectangle height="20px" />
</div>
<div className="inputs">
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<div className="input">
<Loaders.Rectangle height="32px" />
<Loaders.Rectangle height="16px" width="16px" />
</div>
<Loaders.Rectangle className="add" height="20px" />
</div>
<Loaders.Rectangle className="buttons" height="40px" />
</StyledLoader>
);
};
export default TrustedMailLoader;

View File

@ -92,13 +92,25 @@ const UserFields = (props) => {
<div className={className}>
{inputs ? (
inputs.map((input, index) => {
const error = !regexp.test(input);
let newInput1;
let newInput2;
if (input.includes("-")) {
newInput1 = input.split("-")[0];
newInput2 = input.split("-")[1];
}
const error = newInput2
? input.split("-").length - 1 > 1 ||
!regexp.test(newInput1) ||
!regexp.test(newInput2)
: !regexp.test(input);
return (
<StyledInputWrapper key={`domain-input-${index}`}>
<StyledInputWrapper key={`user-input-${index}`}>
<TextInput
id={`domain-input-${index}`}
isAutoFocussed={true}
id={`user-input-${index}`}
isAutoFocussed={false}
value={input}
onChange={(e) => onChangeInput(e, index)}
onBlur={() => onBlur(index)}

View File

@ -14,6 +14,15 @@ const PasswordStrengthPage = lazy(() =>
const TrustedMailPage = lazy(() =>
import("./categories/security/access-portal/trustedMail")
);
const IpSecurityPage = lazy(() =>
import("./categories/security/access-portal/ipSecurity")
);
const AdminMessagePage = lazy(() =>
import("./categories/security/access-portal/adminMessage")
);
const SessionLifetimePage = lazy(() =>
import("./categories/security/access-portal/sessionLifetime")
);
const CommonSettings = lazy(() => import("./categories/common/index.js"));
@ -97,6 +106,18 @@ const TRUSTED_MAIL_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/trusted-mail"
);
const IP_SECURITY_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/ip"
);
const ADMIN_MESSAGE_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/admin-message"
);
const SESSION_LIFETIME_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/lifetime"
);
const ADMINS_URL = combineUrl(PROXY_BASE_URL, "/security/access-rights/admins");
const THIRD_PARTY_URL = combineUrl(
@ -151,6 +172,17 @@ const Settings = (props) => {
path={TRUSTED_MAIL_PAGE_URL}
component={TrustedMailPage}
/>
<Route exact path={IP_SECURITY_PAGE_URL} component={IpSecurityPage} />
<Route
exact
path={ADMIN_MESSAGE_PAGE_URL}
component={AdminMessagePage}
/>
<Route
exact
path={SESSION_LIFETIME_PAGE_URL}
component={SessionLifetimePage}
/>
<Route exact path={THIRD_PARTY_URL} component={ThirdPartyServices} />
<Route

View File

@ -77,6 +77,24 @@ export const settingsTree = [
link: "trusted-mail",
tKey: "TrustedMail",
},
{
key: "1-0-3",
icon: "",
link: "ip",
tKey: "IPSecurity",
},
{
key: "1-0-4",
icon: "",
link: "admin-message",
tKey: "AdminsMessage",
},
{
key: "1-0-5",
icon: "",
link: "lifetime",
tKey: "SessionLifetime",
},
],
},
{

View File

@ -43,9 +43,7 @@ class ConfirmRoute extends React.Component {
.then((validationResult) => {
switch (validationResult) {
case ValidationResult.Ok:
const confirmHeader = `type=${confirmLinkData.type}&${search.slice(
1
)}`;
const confirmHeader = `${confirmLinkData}&${search.slice(1)}`;
const linkData = {
...confirmLinkData,
confirmHeader,

View File

@ -53,6 +53,9 @@ class SettingsSetupStore {
if (authStore.isAuthenticated) {
await authStore.settingsStore.getPortalPasswordSettings();
await authStore.tfaStore.getTfaType();
await authStore.settingsStore.getIpRestrictionsEnable();
await authStore.settingsStore.getIpRestrictions();
await authStore.settingsStore.getSessionLifetime();
}
};
@ -216,22 +219,6 @@ class SettingsSetupStore {
const res = await api.settings.setMailDomainSettings(dnsName, enable);
};
setIpRestrictions = async (data) => {
const res = await api.settings.setIpRestrictions(data);
};
setIpRestrictionsEnable = async (data) => {
const res = await api.settings.setIpRestrictionsEnable(data);
};
setMessageSettings = async (turnOn) => {
const res = await api.settings.setMessageSettings(turnOn);
};
setCookieSettings = async (lifeTime) => {
const res = await api.settings.setCookieSettings(lifeTime);
};
setLifetimeAuditSettings = async (data) => {
const res = await api.settings.setLifetimeAuditSettings(data);
};

View File

@ -1052,7 +1052,7 @@ Scenario("Trusted mail settings change test success", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test.com");
I.fillField("#user-input-0", "test.com");
I.click("Save");
@ -1089,7 +1089,7 @@ Scenario("Trusted mail settings change test error", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test");
I.fillField("#user-input-0", "test");
I.click("Save");