feat: Migrate allowedDomains to a Team Level Settings (#3489)
Fixes #3412 Previously the only way to restrict the domains for a Team were with the ALLOWED_DOMAINS environment variable for self hosted instances. This PR migrates this to be a database backed setting on the Team object. This is done through the creation of a TeamDomain model that is associated with the Team and contains the domain name This settings is updated on the Security Tab. Here domains can be added or removed from the Team. On the server side, we take the code paths that previously were using ALLOWED_DOMAINS and switched them to use the Team allowed domains instead
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
import { debounce } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { PadlockIcon } from "outline-icons";
|
||||
import { CloseIcon, PadlockIcon } from "outline-icons";
|
||||
import { useState } from "react";
|
||||
import * as React from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Button from "~/components/Button";
|
||||
import ConfirmationDialog from "~/components/ConfirmationDialog";
|
||||
import Flex from "~/components/Flex";
|
||||
import Heading from "~/components/Heading";
|
||||
import Input from "~/components/Input";
|
||||
import InputSelect from "~/components/InputSelect";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Scene from "~/components/Scene";
|
||||
import Switch from "~/components/Switch";
|
||||
import Text from "~/components/Text";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import env from "~/env";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
@@ -29,6 +35,7 @@ function Security() {
|
||||
defaultUserRole: team.defaultUserRole,
|
||||
memberCollectionCreate: team.memberCollectionCreate,
|
||||
inviteRequired: team.inviteRequired,
|
||||
allowedDomains: team.allowedDomains,
|
||||
});
|
||||
|
||||
const authenticationMethods = team.signinMethods;
|
||||
@@ -43,6 +50,8 @@ function Security() {
|
||||
[showToast, t]
|
||||
);
|
||||
|
||||
const [domainsChanged, setDomainsChanged] = useState(false);
|
||||
|
||||
const saveData = React.useCallback(
|
||||
async (newData) => {
|
||||
try {
|
||||
@@ -53,6 +62,8 @@ function Security() {
|
||||
showToast(err.message, {
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setDomainsChanged(false);
|
||||
}
|
||||
},
|
||||
[auth, showSuccessMessage, showToast]
|
||||
@@ -110,6 +121,36 @@ function Security() {
|
||||
[data, saveData, t, dialogs, authenticationMethods]
|
||||
);
|
||||
|
||||
const handleRemoveDomain = async (index: number) => {
|
||||
const newData = {
|
||||
...data,
|
||||
};
|
||||
newData.allowedDomains && newData.allowedDomains.splice(index, 1);
|
||||
|
||||
setData(newData);
|
||||
setDomainsChanged(true);
|
||||
};
|
||||
|
||||
const handleAddDomain = () => {
|
||||
const newData = {
|
||||
...data,
|
||||
allowedDomains: [...(data.allowedDomains || []), ""],
|
||||
};
|
||||
|
||||
setData(newData);
|
||||
setDomainsChanged(true);
|
||||
};
|
||||
|
||||
const createOnDomainChangedHandler = (index: number) => (
|
||||
ev: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const newData = { ...data };
|
||||
|
||||
newData.allowedDomains![index] = ev.currentTarget.value;
|
||||
setData(newData);
|
||||
setDomainsChanged(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Scene title={t("Security")} icon={<PadlockIcon color="currentColor" />}>
|
||||
<Heading>{t("Security")}</Heading>
|
||||
@@ -220,8 +261,62 @@ function Security() {
|
||||
short
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
label={t("Allowed Domains")}
|
||||
name="allowedDomains"
|
||||
description={t(
|
||||
"The domains which should be allowed to create accounts. This applies to both SSO and Email logins. Changing this setting does not affect existing user accounts."
|
||||
)}
|
||||
>
|
||||
{data.allowedDomains &&
|
||||
data.allowedDomains.map((domain, index) => (
|
||||
<Flex key={index} gap={4}>
|
||||
<Input
|
||||
key={index}
|
||||
id={`allowedDomains${index}`}
|
||||
value={domain}
|
||||
placeholder="example.com"
|
||||
flex={true}
|
||||
onChange={createOnDomainChangedHandler(index)}
|
||||
/>
|
||||
<Remove>
|
||||
<Tooltip tooltip={t("Remove domain")} placement="top">
|
||||
<NudeButton onClick={() => handleRemoveDomain(index)}>
|
||||
<CloseIcon />
|
||||
</NudeButton>
|
||||
</Tooltip>
|
||||
</Remove>
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
<Flex justify="space-between" gap={4} style={{ flexWrap: "wrap" }}>
|
||||
{!data.allowedDomains?.length ||
|
||||
data.allowedDomains[data.allowedDomains.length - 1] !== "" ? (
|
||||
<Button type="button" onClick={handleAddDomain} neutral>
|
||||
<Trans>Add another</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
|
||||
{domainsChanged && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleChange}
|
||||
disabled={auth.isSaving}
|
||||
>
|
||||
<Trans>Save changes</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</SettingRow>
|
||||
</Scene>
|
||||
);
|
||||
}
|
||||
|
||||
const Remove = styled("div")`
|
||||
margin-top: 6px;
|
||||
`;
|
||||
|
||||
export default observer(Security);
|
||||
|
||||
Reference in New Issue
Block a user