feat: enforce user roles

enforces user admin role requirement for
- creating / deleting / setting role for organization users
- creating / deleting / setting role for project users
- updating project name
- deleting project

hides action elements based on role for
- admin console
- team settings if team is only visible through project membership
- add project tile if not team admin
- project name text editor if not team / project admin
- add redirect from team page if settings only visible through project
  membership
- add redirect from admin console if not org admin

role enforcement is handled on the api side through a custom GraphQL
directive `hasRole`. on the client side, role information is fetched in
the TopNavbar's `me` query and stored in the `UserContext`.

there is a custom hook, `useCurrentUser`, that provides a user object
with two functions, `isVisibile` & `isAdmin` which is used to check
roles in order to render/hide relevant UI elements.
This commit is contained in:
Jordan Knott
2020-07-31 20:01:14 -05:00
committed by Jordan Knott
parent 5dbdc20b36
commit e64f6f8569
63 changed files with 3017 additions and 1905 deletions

View File

@ -9,8 +9,7 @@ import NormalizeStyles from './NormalizeStyles';
import BaseStyles from './BaseStyles';
import { theme } from './ThemeStyles';
import Routes from './Routes';
import { UserIDContext } from './context';
import Navbar from './Navbar';
import { UserContext, CurrentUserRaw, CurrentUserRoles, PermissionLevel, PermissionObjectType } from './context';
const history = createBrowserHistory();
type RefreshTokenResponse = {
@ -20,7 +19,15 @@ type RefreshTokenResponse = {
const App = () => {
const [loading, setLoading] = useState(true);
const [userID, setUserID] = useState<string | null>(null);
const [user, setUser] = useState<CurrentUserRaw | null>(null);
const setUserRoles = (roles: CurrentUserRoles) => {
if (user) {
setUser({
...user,
roles,
});
}
};
useEffect(() => {
fetch('/auth/refresh_token', {
@ -34,7 +41,11 @@ const App = () => {
const response: RefreshTokenResponse = await x.json();
const { accessToken, isInstalled } = response;
const claims: JWTToken = jwtDecode(accessToken);
setUserID(claims.userId);
const currentUser = {
id: claims.userId,
roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
};
setUser(currentUser);
setAccessToken(accessToken);
if (!isInstalled) {
history.replace('/install');
@ -46,7 +57,7 @@ const App = () => {
return (
<>
<UserIDContext.Provider value={{ userID, setUserID }}>
<UserContext.Provider value={{ user, setUser, setUserRoles }}>
<ThemeProvider theme={theme}>
<NormalizeStyles />
<BaseStyles />
@ -62,7 +73,7 @@ const App = () => {
</PopupProvider>
</Router>
</ThemeProvider>
</UserIDContext.Provider>
</UserContext.Provider>
</>
);
};