Compare commits

...

1 Commits

Author SHA1 Message Date
Tom Moor 343684d1b5 wip 2021-04-03 17:09:29 -07:00
9 changed files with 159 additions and 99 deletions
+4 -3
View File
@@ -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;
}
+13
View File
@@ -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
View File
@@ -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);
}
}
+4
View File
@@ -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();
+8 -10
View File
@@ -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,
})),
],
};
});
+7 -7
View File
@@ -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",
"Youve not starred any documents yet.": "Youve 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.",