mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 343684d1b5 |
@@ -5,14 +5,15 @@ import GoogleLogo from "./GoogleLogo";
|
||||
|
||||
type Props = {|
|
||||
providerName: string,
|
||||
fill?: string,
|
||||
|};
|
||||
|
||||
export default function AuthLogo({ providerName }: Props) {
|
||||
export default function AuthLogo({ providerName, fill }: Props) {
|
||||
switch (providerName) {
|
||||
case "slack":
|
||||
return <SlackLogo size={16} />;
|
||||
return <SlackLogo size={16} fill={fill} />;
|
||||
case "google":
|
||||
return <GoogleLogo size={16} />;
|
||||
return <GoogleLogo size={16} fill={fill} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
import BaseModel from "./BaseModel";
|
||||
|
||||
class AuthenticationProvider extends BaseModel {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
providerId: string;
|
||||
isEnabled: boolean;
|
||||
isConnected: boolean;
|
||||
}
|
||||
|
||||
export default AuthenticationProvider;
|
||||
+102
-79
@@ -1,98 +1,121 @@
|
||||
// @flow
|
||||
import { debounce } from "lodash";
|
||||
import { observable } from "mobx";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EmailIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
|
||||
import AuthStore from "stores/AuthStore";
|
||||
import UiStore from "stores/UiStore";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import AuthLogo from "components/AuthLogo";
|
||||
import CenteredContent from "components/CenteredContent";
|
||||
import Checkbox from "components/Checkbox";
|
||||
import HelpText from "components/HelpText";
|
||||
import List from "components/List";
|
||||
import ListItem from "components/List/Item";
|
||||
import PageTitle from "components/PageTitle";
|
||||
import Subheading from "components/Subheading";
|
||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||
import useStores from "hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
auth: AuthStore,
|
||||
ui: UiStore,
|
||||
};
|
||||
function Security() {
|
||||
const team = useCurrentTeam();
|
||||
const { ui, auth, authenticationProviders } = useStores();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@observer
|
||||
class Security extends React.Component<Props> {
|
||||
form: ?HTMLFormElement;
|
||||
React.useEffect(() => {
|
||||
authenticationProviders.fetchPage();
|
||||
}, [authenticationProviders]);
|
||||
|
||||
@observable sharing: boolean;
|
||||
@observable documentEmbeds: boolean;
|
||||
@observable guestSignin: boolean;
|
||||
const showSuccessMessage = React.useCallback(
|
||||
debounce(() => {
|
||||
ui.showToast(t("Settings saved"), { type: "success" });
|
||||
}, 500),
|
||||
[ui, t]
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
const { auth } = this.props;
|
||||
if (auth.team) {
|
||||
this.documentEmbeds = auth.team.documentEmbeds;
|
||||
this.guestSignin = auth.team.guestSignin;
|
||||
this.sharing = auth.team.sharing;
|
||||
}
|
||||
}
|
||||
const handleChange = React.useCallback(
|
||||
async (ev: SyntheticInputEvent<>) => {
|
||||
switch (ev.target.name) {
|
||||
case "sharing":
|
||||
team.sharing = ev.target.checked;
|
||||
break;
|
||||
case "documentEmbeds":
|
||||
team.documentEmbeds = ev.target.checked;
|
||||
break;
|
||||
case "guestSignin":
|
||||
team.guestSignin = ev.target.checked;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
handleChange = async (ev: SyntheticInputEvent<*>) => {
|
||||
switch (ev.target.name) {
|
||||
case "sharing":
|
||||
this.sharing = ev.target.checked;
|
||||
break;
|
||||
case "documentEmbeds":
|
||||
this.documentEmbeds = ev.target.checked;
|
||||
break;
|
||||
case "guestSignin":
|
||||
this.guestSignin = ev.target.checked;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
await auth.updateTeam({
|
||||
sharing: team.sharing,
|
||||
documentEmbeds: team.documentEmbeds,
|
||||
guestSignin: team.guestSignin,
|
||||
});
|
||||
showSuccessMessage();
|
||||
},
|
||||
[auth, showSuccessMessage, team]
|
||||
);
|
||||
|
||||
await this.props.auth.updateTeam({
|
||||
sharing: this.sharing,
|
||||
documentEmbeds: this.documentEmbeds,
|
||||
guestSignin: this.guestSignin,
|
||||
});
|
||||
this.showSuccessMessage();
|
||||
};
|
||||
|
||||
showSuccessMessage = debounce(() => {
|
||||
this.props.ui.showToast("Settings saved", { type: "success" });
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
return (
|
||||
<CenteredContent>
|
||||
<PageTitle title="Security" />
|
||||
<h1>Security</h1>
|
||||
<HelpText>
|
||||
return (
|
||||
<CenteredContent>
|
||||
<PageTitle title={t("Security")} />
|
||||
<h1>{t("Security")}</h1>
|
||||
<HelpText>
|
||||
<Trans>
|
||||
Settings that impact the access, security, and content of your
|
||||
knowledge base.
|
||||
</HelpText>
|
||||
</Trans>
|
||||
</HelpText>
|
||||
|
||||
<Checkbox
|
||||
label="Allow email authentication"
|
||||
name="guestSignin"
|
||||
checked={this.guestSignin}
|
||||
onChange={this.handleChange}
|
||||
note="When enabled, users can sign-in using their email address"
|
||||
<Checkbox
|
||||
label={t("Allow email authentication")}
|
||||
name="guestSignin"
|
||||
checked={team.guestSignin}
|
||||
onChange={handleChange}
|
||||
note={t("When enabled, users can sign-in using their email address")}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("Public document sharing")}
|
||||
name="sharing"
|
||||
checked={team.sharing}
|
||||
onChange={handleChange}
|
||||
note={t(
|
||||
"When enabled, documents can be shared publicly on the internet by any team member"
|
||||
)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t("Rich service embeds")}
|
||||
name="documentEmbeds"
|
||||
checked={team.documentEmbeds}
|
||||
onChange={handleChange}
|
||||
note={t(
|
||||
"Links to supported services are shown as rich embeds within your documents"
|
||||
)}
|
||||
/>
|
||||
|
||||
<Subheading>{t("Authentication providers")}</Subheading>
|
||||
<List>
|
||||
<ListItem
|
||||
title={t("Email")}
|
||||
subtitle={t("Users can sign-in using their email address")}
|
||||
image={<EmailIcon fill="currentColor" />}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Public document sharing"
|
||||
name="sharing"
|
||||
checked={this.sharing}
|
||||
onChange={this.handleChange}
|
||||
note="When enabled, documents can be shared publicly on the internet by any team member"
|
||||
/>
|
||||
<Checkbox
|
||||
label="Rich service embeds"
|
||||
name="documentEmbeds"
|
||||
checked={this.documentEmbeds}
|
||||
onChange={this.handleChange}
|
||||
note="Links to supported services are shown as rich embeds within your documents"
|
||||
/>
|
||||
</CenteredContent>
|
||||
);
|
||||
}
|
||||
|
||||
{authenticationProviders.orderedData.map((authenticationProvider) => (
|
||||
<ListItem
|
||||
title={authenticationProvider.name}
|
||||
subtitle={authenticationProvider.providerId}
|
||||
image={
|
||||
<AuthLogo
|
||||
providerName={authenticationProvider.name}
|
||||
fill="currentColor"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</CenteredContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default inject("auth", "ui")(Security);
|
||||
export default observer(Security);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// @flow
|
||||
import AuthenticationProvider from "models/AuthenticationProvider";
|
||||
import BaseStore from "./BaseStore";
|
||||
import RootStore from "./RootStore";
|
||||
|
||||
export default class AuthenticationProvidersStore extends BaseStore<AuthenticationProvider> {
|
||||
actions = ["list", "create", "update"];
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore, AuthenticationProvider);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import ApiKeysStore from "./ApiKeysStore";
|
||||
import AuthStore from "./AuthStore";
|
||||
import AuthenticationProvidersStore from "./AuthenticationProvidersStore";
|
||||
import CollectionGroupMembershipsStore from "./CollectionGroupMembershipsStore";
|
||||
import CollectionsStore from "./CollectionsStore";
|
||||
import DocumentPresenceStore from "./DocumentPresenceStore";
|
||||
@@ -18,6 +19,7 @@ import UsersStore from "./UsersStore";
|
||||
import ViewsStore from "./ViewsStore";
|
||||
|
||||
export default class RootStore {
|
||||
authenticationProviders: AuthenticationProvidersStore;
|
||||
apiKeys: ApiKeysStore;
|
||||
auth: AuthStore;
|
||||
collections: CollectionsStore;
|
||||
@@ -37,6 +39,7 @@ export default class RootStore {
|
||||
views: ViewsStore;
|
||||
|
||||
constructor() {
|
||||
this.authenticationProviders = new AuthenticationProvidersStore(this);
|
||||
this.apiKeys = new ApiKeysStore(this);
|
||||
this.auth = new AuthStore(this);
|
||||
this.collections = new CollectionsStore(this);
|
||||
@@ -57,6 +60,7 @@ export default class RootStore {
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authenticationProviders.clear();
|
||||
this.apiKeys.clear();
|
||||
// this.auth omitted for reasons...
|
||||
this.collections.clear();
|
||||
|
||||
@@ -69,16 +69,14 @@ router.post("authenticationProviders.list", auth(), async (ctx) => {
|
||||
);
|
||||
|
||||
ctx.body = {
|
||||
data: {
|
||||
authenticationProviders: [
|
||||
...teamAuthenticationProviders.map(presentAuthenticationProvider),
|
||||
...otherAuthenticationProviders.map((p) => ({
|
||||
name: p.id,
|
||||
isEnabled: false,
|
||||
isConnected: false,
|
||||
})),
|
||||
],
|
||||
},
|
||||
data: [
|
||||
...teamAuthenticationProviders.map(presentAuthenticationProvider),
|
||||
...otherAuthenticationProviders.map((p) => ({
|
||||
name: p.id,
|
||||
isEnabled: false,
|
||||
isConnected: false,
|
||||
})),
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -140,13 +140,13 @@ describe("#authenticationProviders.list", () => {
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.authenticationProviders.length).toBe(2);
|
||||
expect(body.data.authenticationProviders[0].name).toBe("slack");
|
||||
expect(body.data.authenticationProviders[0].isEnabled).toBe(true);
|
||||
expect(body.data.authenticationProviders[0].isConnected).toBe(true);
|
||||
expect(body.data.authenticationProviders[1].name).toBe("google");
|
||||
expect(body.data.authenticationProviders[1].isEnabled).toBe(false);
|
||||
expect(body.data.authenticationProviders[1].isConnected).toBe(false);
|
||||
expect(body.data.length).toBe(2);
|
||||
expect(body.data[0].name).toBe("slack");
|
||||
expect(body.data[0].isEnabled).toBe(true);
|
||||
expect(body.data[0].isConnected).toBe(true);
|
||||
expect(body.data[1].name).toBe("google");
|
||||
expect(body.data[1].isEnabled).toBe(false);
|
||||
expect(body.data[1].isConnected).toBe(false);
|
||||
});
|
||||
|
||||
it("should require authentication", async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ export default function present(
|
||||
return {
|
||||
id: authenticationProvider.id,
|
||||
name: authenticationProvider.name,
|
||||
providerId: authenticationProvider.providerId,
|
||||
createdAt: authenticationProvider.createdAt,
|
||||
isEnabled: authenticationProvider.enabled,
|
||||
isConnected: true,
|
||||
|
||||
@@ -369,6 +369,14 @@
|
||||
"Delete Account": "Delete Account",
|
||||
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",
|
||||
"Delete account": "Delete account",
|
||||
"Settings saved": "Settings saved",
|
||||
"Settings that impact the access, security, and content of your knowledge base.": "Settings that impact the access, security, and content of your knowledge base.",
|
||||
"Allow email authentication": "Allow email authentication",
|
||||
"When enabled, users can sign-in using their email address": "When enabled, users can sign-in using their email address",
|
||||
"When enabled, documents can be shared publicly on the internet by any team member": "When enabled, documents can be shared publicly on the internet by any team member",
|
||||
"Rich service embeds": "Rich service embeds",
|
||||
"Links to supported services are shown as rich embeds within your documents": "Links to supported services are shown as rich embeds within your documents",
|
||||
"Authentication providers": "Authentication providers",
|
||||
"You’ve not starred any documents yet.": "You’ve not starred any documents yet.",
|
||||
"There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.": "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.",
|
||||
"Trash is empty at the moment.": "Trash is empty at the moment.",
|
||||
|
||||
Reference in New Issue
Block a user