feat: add access control

This commit is contained in:
41666 2021-07-18 01:57:03 -04:00
parent 9c07ff0e54
commit 3f45153b66
47 changed files with 1084 additions and 164 deletions

View file

@ -4,12 +4,14 @@ import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/gener
import { GuildSlug } from '@roleypoly/types';
import React from 'react';
import { useApiContext } from '../../contexts/api/ApiContext';
import { useGuildContext } from '../../contexts/guild/GuildContext';
import { useSessionContext } from '../../contexts/session/SessionContext';
import { Title } from '../../utils/metaTitle';
const Login = (props: { path: string }) => {
const { apiUrl, fetch } = useApiContext();
const { apiUrl } = useApiContext();
const { isAuthenticated } = useSessionContext();
const { getGuildSlug } = useGuildContext();
// If ?r is in query, then let's render the slug page
// If not, redirect.
const [guildSlug, setGuildSlug] = React.useState<GuildSlug | null>(null);
@ -32,9 +34,8 @@ const Login = (props: { path: string }) => {
localStorage.setItem('rp_postauth_redirect', `/s/${redirectServerID}`);
const fetchGuildSlug = async (id: string) => {
const response = await fetch(`/get-slug/${id}`);
if (response.status === 200) {
const slug = await response.json();
const slug = await getGuildSlug(id);
if (slug) {
setGuildSlug(slug);
}
};
@ -44,7 +45,7 @@ const Login = (props: { path: string }) => {
if (isAuthenticated) {
redirectTo(`/s/${redirectServerID}`);
}
}, [apiUrl, fetch, isAuthenticated]);
}, [apiUrl, getGuildSlug, isAuthenticated]);
if (guildSlug === null) {
return <GenericLoadingTemplate>Sending you to Discord...</GenericLoadingTemplate>;

View file

@ -8,6 +8,7 @@ import {
} from '@roleypoly/types';
import * as React from 'react';
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../contexts/guild/GuildContext';
import { useRecentGuilds } from '../contexts/recent-guilds/RecentGuildsContext';
import { useSessionContext } from '../contexts/session/SessionContext';
import { Title } from '../utils/metaTitle';
@ -22,6 +23,7 @@ const Editor = (props: EditorProps) => {
const { session, authedFetch, isAuthenticated } = useSessionContext();
const { pushRecentGuild } = useRecentGuilds();
const appShellProps = useAppShellProps();
const { getFullGuild } = useGuildContext();
const [guild, setGuild] = React.useState<PresentableGuild | null | false>(null);
const [pending, setPending] = React.useState(false);
@ -38,20 +40,18 @@ const Editor = (props: EditorProps) => {
return false;
};
const fetchGuild = async () => {
const skipCache = shouldPullUncached() ? '?__no_cache' : '';
const response = await authedFetch(`/get-picker-data/${serverID}${skipCache}`);
const data = await response.json();
const guild = await getFullGuild(serverID, shouldPullUncached());
if (response.status !== 200) {
if (guild === null) {
setGuild(false);
return;
}
setGuild(data);
setGuild(guild);
};
fetchGuild();
}, [serverID, authedFetch]);
}, [serverID, getFullGuild]);
React.useCallback((serverID) => pushRecentGuild(serverID), [pushRecentGuild])(serverID);
@ -84,10 +84,11 @@ const Editor = (props: EditorProps) => {
setPending(true);
const updatePayload: GuildDataUpdate = {
const updatePayload: Partial<GuildDataUpdate> = {
message: guild.data.message,
categories: guild.data.categories,
auditLogWebhook: guild.data.auditLogWebhook,
auditLogWebhook:
'https://discord.com/api/webhooks/864658054930759696/vE91liQYwmW4nS6fiT0cMfhe_dpPLBkDXOPynDNLdXZT1KdkDKm8wa4h4E4RPw0GDcJR',
};
const response = await authedFetch(`/update-guild/${serverID}`, {

View file

@ -0,0 +1,91 @@
import { navigate, Redirect } from '@reach/router';
import { EditorAccessControlTemplate } from '@roleypoly/design-system/templates/editor-access-control';
import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading';
import {
GuildAccessControl,
GuildDataUpdate,
PresentableGuild,
UserGuildPermissions,
} from '@roleypoly/types';
import React from 'react';
import { useAppShellProps } from '../../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../../contexts/guild/GuildContext';
import { useRecentGuilds } from '../../contexts/recent-guilds/RecentGuildsContext';
import { useSessionContext } from '../../contexts/session/SessionContext';
const AccessControlPage = (props: { serverID: string; path: string }) => {
const { session, isAuthenticated, authedFetch } = useSessionContext();
const { pushRecentGuild } = useRecentGuilds();
const { getFullGuild, uncacheGuild } = useGuildContext();
const appShellProps = useAppShellProps();
const [guild, setGuild] = React.useState<PresentableGuild | null | false>(null);
React.useEffect(() => {
const fetchGuild = async () => {
const guild = await getFullGuild(props.serverID);
if (guild === null) {
setGuild(false);
return;
}
setGuild(guild);
};
fetchGuild();
}, [props.serverID, getFullGuild]);
React.useCallback(
(serverID) => pushRecentGuild(serverID),
[pushRecentGuild]
)(props.serverID);
// If the user is not authenticated, redirect to the login page.
if (!isAuthenticated) {
return <Redirect to={`/auth/login?r=${props.serverID}`} replace />;
}
// If the user is not an admin, they can't edit the guild
// so we redirect them to the picker
const guildSlug = session?.guilds?.find((guild) => guild.id === props.serverID);
if (guildSlug && guildSlug?.permissionLevel === UserGuildPermissions.User) {
return <Redirect to={`/s/${props.serverID}`} replace />;
}
// If the guild isn't loaded, render a loading placeholder
if (guild === null) {
return <GenericLoadingTemplate />;
}
// If the guild is not found, redirect to the picker page
if (guild === false) {
return <Redirect to={`/s/${props.serverID}`} replace />;
}
const onSubmit = async (accessControl: GuildAccessControl) => {
const updatePayload: Partial<GuildDataUpdate> = {
accessControl,
};
await authedFetch(`/update-guild/${props.serverID}`, {
method: 'PATCH',
body: JSON.stringify(updatePayload),
});
uncacheGuild(props.serverID);
navigate(`/s/${props.serverID}/edit`);
};
return (
<EditorAccessControlTemplate
guild={guild}
guildSlug={guild.guild}
onSubmit={(data: any) => onSubmit(data)}
onExit={() => navigate(`/s/${props.serverID}/edit`)}
{...appShellProps}
/>
);
};
export default AccessControlPage;

View file

@ -1,10 +1,11 @@
import { Redirect } from '@reach/router';
import { Redirect, redirectTo } from '@reach/router';
import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading';
import { RolePickerTemplate } from '@roleypoly/design-system/templates/role-picker';
import { ServerSetupTemplate } from '@roleypoly/design-system/templates/server-setup';
import { PresentableGuild, RoleUpdate, UserGuildPermissions } from '@roleypoly/types';
import * as React from 'react';
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../contexts/guild/GuildContext';
import { useRecentGuilds } from '../contexts/recent-guilds/RecentGuildsContext';
import { useSessionContext } from '../contexts/session/SessionContext';
import { Title } from '../utils/metaTitle';
@ -19,6 +20,7 @@ const Picker = (props: PickerProps) => {
const { session, authedFetch, isAuthenticated } = useSessionContext();
const { pushRecentGuild } = useRecentGuilds();
const appShellProps = useAppShellProps();
const { getFullGuild } = useGuildContext();
const [pickerData, setPickerData] = React.useState<PresentableGuild | null | false>(
null
@ -27,10 +29,14 @@ const Picker = (props: PickerProps) => {
React.useEffect(() => {
const fetchPickerData = async () => {
const response = await authedFetch(`/get-picker-data/${props.serverID}`);
const data = await response.json();
const data = await getFullGuild(props.serverID);
if (response.status !== 200) {
if (data === false) {
redirectTo('/error/accessControlViolation');
return;
}
if (data === null) {
setPickerData(false);
return;
}
@ -39,7 +45,7 @@ const Picker = (props: PickerProps) => {
};
fetchPickerData();
}, [props.serverID, authedFetch, pushRecentGuild]);
}, [props.serverID, getFullGuild]);
React.useCallback(
(serverID) => pushRecentGuild(serverID),