Files
outline/app/scenes/Settings/Security.tsx
dependabot[bot] fbd16d4b9a chore(deps-dev): bump prettier from 2.1.2 to 2.8.8 (#5372)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-05-22 19:14:56 -07:00

304 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { debounce } from "lodash";
import { observer } from "mobx-react";
import { CheckboxIcon, EmailIcon, PadlockIcon } from "outline-icons";
import { useState } from "react";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useTheme } from "styled-components";
import { TeamPreference } from "@shared/types";
import ConfirmationDialog from "~/components/ConfirmationDialog";
import Flex from "~/components/Flex";
import Heading from "~/components/Heading";
import InputSelect from "~/components/InputSelect";
import PluginIcon from "~/components/PluginIcon";
import Scene from "~/components/Scene";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
import env from "~/env";
import useCurrentTeam from "~/hooks/useCurrentTeam";
import useRequest from "~/hooks/useRequest";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import isCloudHosted from "~/utils/isCloudHosted";
import DomainManagement from "./components/DomainManagement";
import SettingRow from "./components/SettingRow";
function Security() {
const { auth, authenticationProviders, dialogs } = useStores();
const team = useCurrentTeam();
const { t } = useTranslation();
const { showToast } = useToasts();
const theme = useTheme();
const [data, setData] = useState({
sharing: team.sharing,
documentEmbeds: team.documentEmbeds,
guestSignin: team.guestSignin,
defaultUserRole: team.defaultUserRole,
memberCollectionCreate: team.memberCollectionCreate,
inviteRequired: team.inviteRequired,
});
const {
data: providers,
loading,
request,
} = useRequest(() => authenticationProviders.fetchPage({}));
React.useEffect(() => {
if (!providers && !loading) {
request();
}
}, [loading, providers, request]);
const showSuccessMessage = React.useMemo(
() =>
debounce(() => {
showToast(t("Settings saved"), {
type: "success",
});
}, 250),
[showToast, t]
);
const saveData = React.useCallback(
async (newData) => {
try {
setData(newData);
await auth.updateTeam(newData);
showSuccessMessage();
} catch (err) {
showToast(err.message, {
type: "error",
});
}
},
[auth, showSuccessMessage, showToast]
);
const handleChange = React.useCallback(
async (ev: React.ChangeEvent<HTMLInputElement>) => {
await saveData({ ...data, [ev.target.id]: ev.target.checked });
},
[data, saveData]
);
const handleDefaultRoleChange = React.useCallback(
async (newDefaultRole: string) => {
await saveData({ ...data, defaultUserRole: newDefaultRole });
},
[data, saveData]
);
const handlePreferenceChange = React.useCallback(
async (ev: React.ChangeEvent<HTMLInputElement>) => {
const preferences = {
...team.preferences,
[ev.target.id]: ev.target.checked,
};
await saveData({ preferences });
},
[saveData, team.preferences]
);
const handleInviteRequiredChange = React.useCallback(
async (ev: React.ChangeEvent<HTMLInputElement>) => {
const inviteRequired = ev.target.checked;
const newData = { ...data, inviteRequired };
if (inviteRequired) {
dialogs.openModal({
isCentered: true,
title: t("Are you sure you want to require invites?"),
content: (
<ConfirmationDialog
onSubmit={async () => {
await saveData(newData);
}}
submitText={t("Im sure")}
savingText={`${t("Saving")}`}
danger
>
<Trans
defaults="New users will first need to be invited to create an account. <em>Default role</em> and <em>Allowed domains</em> will no longer apply."
values={{
authenticationMethods: team.signinMethods,
}}
components={{
em: <strong />,
}}
/>
</ConfirmationDialog>
),
});
return;
}
await saveData(newData);
},
[data, saveData, t, dialogs, team.signinMethods]
);
return (
<Scene title={t("Security")} icon={<PadlockIcon />}>
<Heading>{t("Security")}</Heading>
<Text type="secondary">
<Trans>
Settings that impact the access, security, and content of your
knowledge base.
</Trans>
</Text>
<h2>{t("Sign In")}</h2>
{authenticationProviders.orderedData
// filtering unconnected, until we have ability to connect from this screen
.filter((provider) => provider.isConnected)
.map((provider) => (
<SettingRow
key={provider.name}
label={
<Flex gap={8} align="center">
<PluginIcon id={provider.name} /> {provider.displayName}
</Flex>
}
name={provider.name}
description={t("Allow members to sign-in with {{ authProvider }}", {
authProvider: provider.displayName,
})}
>
<Flex align="center">
<CheckboxIcon
color={provider.isActive ? theme.accent : undefined}
checked={provider.isActive}
/>{" "}
<Text type="secondary">
{provider.isActive ? t("Connected") : t("Disabled")}
</Text>
</Flex>
</SettingRow>
))}
<SettingRow
label={
<Flex gap={8} align="center">
<EmailIcon /> {t("Email")}
</Flex>
}
name="guestSignin"
description={
env.EMAIL_ENABLED
? t("Allow members to sign-in using their email address")
: t("The server must have SMTP configured to enable this setting")
}
border={false}
>
<Switch
id="guestSignin"
checked={data.guestSignin}
onChange={handleChange}
disabled={!env.EMAIL_ENABLED}
/>
</SettingRow>
<h2>{t("Access")}</h2>
{isCloudHosted && (
<SettingRow
label={t("Require invites")}
name="inviteRequired"
description={t(
"Require members to be invited to the workspace before they can create an account using SSO."
)}
>
<Switch
id="inviteRequired"
checked={data.inviteRequired}
onChange={handleInviteRequiredChange}
/>
</SettingRow>
)}
{!data.inviteRequired && (
<DomainManagement onSuccess={showSuccessMessage} />
)}
{!data.inviteRequired && (
<SettingRow
label={t("Default role")}
name="defaultUserRole"
description={t(
"The default user role for new accounts. Changing this setting does not affect existing user accounts."
)}
border={false}
>
<InputSelect
id="defaultUserRole"
value={data.defaultUserRole}
options={[
{
label: t("Member"),
value: "member",
},
{
label: t("Viewer"),
value: "viewer",
},
]}
onChange={handleDefaultRoleChange}
ariaLabel={t("Default role")}
short
/>
</SettingRow>
)}
<h2>{t("Behavior")}</h2>
<SettingRow
label={t("Public document sharing")}
name="sharing"
description={t(
"When enabled, documents can be shared publicly on the internet by any member of the workspace"
)}
>
<Switch id="sharing" checked={data.sharing} onChange={handleChange} />
</SettingRow>
<SettingRow
label={t("Viewer document exports")}
name={TeamPreference.ViewersCanExport}
description={t(
"When enabled, viewers can see download options for documents"
)}
>
<Switch
id={TeamPreference.ViewersCanExport}
checked={team.getPreference(TeamPreference.ViewersCanExport)}
onChange={handlePreferenceChange}
/>
</SettingRow>
<SettingRow
label={t("Rich service embeds")}
name="documentEmbeds"
description={t(
"Links to supported services are shown as rich embeds within your documents"
)}
>
<Switch
id="documentEmbeds"
checked={data.documentEmbeds}
onChange={handleChange}
/>
</SettingRow>
<SettingRow
label={t("Collection creation")}
name="memberCollectionCreate"
description={t(
"Allow members to create new collections within the knowledge base"
)}
>
<Switch
id="memberCollectionCreate"
checked={data.memberCollectionCreate}
onChange={handleChange}
/>
</SettingRow>
</Scene>
);
}
export default observer(Security);