[CORL-318] Overhaul Permissions Checks on the Client (#2246)

* feat: overhaul admin permission checks

* feat: overhaul stream permission checks
This commit is contained in:
Kiwi
2019-03-29 18:55:32 +00:00
committed by Wyatt Johnson
parent 618646e71c
commit f9114ef4be
20 changed files with 388 additions and 65 deletions
@@ -10,13 +10,13 @@ import {
import { TabBarContainer_story as StoryData } from "talk-stream/__generated__/TabBarContainer_story.graphql";
import { TabBarContainer_viewer as ViewerData } from "talk-stream/__generated__/TabBarContainer_viewer.graphql";
import { TabBarContainerLocal as Local } from "talk-stream/__generated__/TabBarContainerLocal.graphql";
import { roleIsAtLeast } from "talk-stream/helpers";
import {
SetActiveTabInput,
SetActiveTabMutation,
withSetActiveTabMutation,
} from "talk-stream/mutations";
import { Ability, can } from "talk-stream/permissions";
import TabBar from "../components/TabBar";
interface Props {
@@ -45,7 +45,7 @@ export class TabBarContainer extends Component<Props> {
showProfileTab={loggedIn}
showConfigureTab={
!!this.props.viewer &&
roleIsAtLeast(this.props.viewer.role, "MODERATOR")
can(this.props.viewer, Ability.CHANGE_STORY_CONFIGURATION)
}
onTabClick={this.handleSetActiveTab}
/>
@@ -1,13 +1,15 @@
// TODO: use generated schema types.
type Role =
| "ADMIN"
| "MODERATOR"
| "STAFF"
| "COMMENTER"
| "%future added value";
import { GQLUSER_ROLE, GQLUSER_ROLE_RL } from "talk-framework/schema";
const hierarchy: Role[] = ["COMMENTER", "STAFF", "MODERATOR", "ADMIN"];
export default function roleIsAtLeast(role: Role, atLeast: Role) {
const hierarchy: GQLUSER_ROLE_RL[] = [
GQLUSER_ROLE.COMMENTER,
GQLUSER_ROLE.STAFF,
GQLUSER_ROLE.MODERATOR,
GQLUSER_ROLE.ADMIN,
];
export default function roleIsAtLeast(
role: GQLUSER_ROLE_RL,
atLeast: GQLUSER_ROLE_RL
) {
[role, atLeast].forEach(r => {
if (!hierarchy.includes(r)) {
throw new Error(`Unknown role ${r}`);
@@ -14,6 +14,7 @@ import {
MutationInput,
MutationResponsePromise,
} from "talk-framework/lib/relay";
import { GQLUSER_ROLE } from "talk-framework/schema";
import { CreateCommentMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentMutation.graphql";
import {
@@ -117,7 +118,8 @@ function commit(
// TODO: Generate and use schema types.
const expectPremoderation =
!roleIsAtLeast(me.role, "STAFF") && storySettings.moderation === "PRE";
!roleIsAtLeast(me.role, GQLUSER_ROLE.STAFF) &&
storySettings.moderation === "PRE";
return commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation,
@@ -14,6 +14,7 @@ import {
MutationInput,
MutationResponsePromise,
} from "talk-framework/lib/relay";
import { GQLUSER_ROLE } from "talk-framework/schema";
import { CreateCommentReplyMutation as MutationTypes } from "talk-stream/__generated__/CreateCommentReplyMutation.graphql";
import {
@@ -144,7 +145,8 @@ function commit(
// TODO: Generate and use schema types.
const expectPremoderation =
!roleIsAtLeast(viewer.role, "STAFF") && storySettings.moderation === "PRE";
!roleIsAtLeast(viewer.role, GQLUSER_ROLE.STAFF) &&
storySettings.moderation === "PRE";
return commitMutationPromiseNormalized<MutationTypes>(environment, {
mutation,
+32
View File
@@ -0,0 +1,32 @@
import { mapValues } from "lodash";
import { GQLUSER_ROLE, GQLUSER_ROLE_RL } from "talk-framework/schema";
/**
* permissionMap describes what abilities certain roles have.
*
* This list is currently manually managed. We want to
* get to a point where this is generated from the schema.
*
* We currently specify in the comments which endpoints of
* the graph is important for the ability, which we can later
* used to auto generate the map making the schema become
* the single point of truth.
*/
const permissionMap = {
// Mutation.updateStorySettings
CHANGE_STORY_CONFIGURATION: [GQLUSER_ROLE.ADMIN, GQLUSER_ROLE.MODERATOR],
};
export type AbilityType = keyof typeof permissionMap;
export const Ability = mapValues(permissionMap, (_, key) => key) as {
[P in AbilityType]: P
};
/**
* can is used to check if the `viewer` has permission for `ability`.
*
* Example: `can(props.me, Ability.CHANGE_ROLE)`.
*/
export function can(viewer: { role: GQLUSER_ROLE_RL }, ability: AbilityType) {
return permissionMap[ability].includes(viewer.role as GQLUSER_ROLE);
}
+6 -5
View File
@@ -1,3 +1,4 @@
import { GQLUSER_ROLE } from "talk-framework/schema";
import {
denormalizeComment,
denormalizeComments,
@@ -76,17 +77,17 @@ export const users = [
{
id: "user-0",
username: "Markus",
role: "COMMENTER",
role: GQLUSER_ROLE.COMMENTER,
},
{
id: "user-1",
username: "Lukas",
role: "COMMENTER",
role: GQLUSER_ROLE.COMMENTER,
},
{
id: "user-2",
username: "Isabelle",
role: "COMMENTER",
role: GQLUSER_ROLE.COMMENTER,
},
];
@@ -399,13 +400,13 @@ export const storyWithDeepestReplies = denormalizeStory({
export const viewerAsModerator = {
id: "me-as-moderator",
username: "Moderator",
role: "MODERATOR",
role: GQLUSER_ROLE.MODERATOR,
};
export const viewerWithComments = {
id: "me-with-comments",
username: "Markus",
role: "COMMENTER",
role: GQLUSER_ROLE.COMMENTER,
comments: {
edges: [
{