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:
		
				
					committed by
					
						
						Jordan Knott
					
				
			
			
				
	
			
			
			
						parent
						
							5dbdc20b36
						
					
				
				
					commit
					e64f6f8569
				
			@@ -97,7 +97,6 @@ type Project {
 | 
			
		||||
  createdAt: Time!
 | 
			
		||||
  name: String!
 | 
			
		||||
  team: Team!
 | 
			
		||||
  owner: Member!
 | 
			
		||||
  taskGroups: [TaskGroup!]!
 | 
			
		||||
  members: [Member!]!
 | 
			
		||||
  labels: [ProjectLabel!]!
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,23 @@
 | 
			
		||||
enum RoleLevel {
 | 
			
		||||
  ADMIN
 | 
			
		||||
  MEMBER
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ActionLevel {
 | 
			
		||||
  ORG
 | 
			
		||||
  TEAM
 | 
			
		||||
  PROJECT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ObjectType {
 | 
			
		||||
  ORG
 | 
			
		||||
  TEAM
 | 
			
		||||
  PROJECT
 | 
			
		||||
  TASK
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
directive @hasRole(roles: [RoleLevel!]!, level: ActionLevel!, type: ObjectType!) on FIELD_DEFINITION
 | 
			
		||||
 | 
			
		||||
type Query {
 | 
			
		||||
  organizations: [Organization!]!
 | 
			
		||||
  users: [UserAccount!]!
 | 
			
		||||
@@ -9,11 +29,27 @@ type Query {
 | 
			
		||||
  teams: [Team!]!
 | 
			
		||||
  labelColors: [LabelColor!]!
 | 
			
		||||
  taskGroups: [TaskGroup!]!
 | 
			
		||||
  me: UserAccount!
 | 
			
		||||
  me: MePayload! 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Mutation
 | 
			
		||||
 | 
			
		||||
type TeamRole {
 | 
			
		||||
  teamID: UUID!
 | 
			
		||||
  roleCode: RoleCode!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ProjectRole {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  roleCode: RoleCode!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MePayload {
 | 
			
		||||
  user: UserAccount!
 | 
			
		||||
  teamRoles: [TeamRole!]!
 | 
			
		||||
  projectRoles: [ProjectRole!]!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input ProjectsFilter {
 | 
			
		||||
  teamID: UUID
 | 
			
		||||
}
 | 
			
		||||
@@ -23,7 +59,7 @@ input FindUser {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input FindProject {
 | 
			
		||||
  projectId: String!
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input FindTask {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createProject(input: NewProject!): Project!
 | 
			
		||||
  deleteProject(input: DeleteProject!): DeleteProjectPayload!
 | 
			
		||||
  updateProjectName(input: UpdateProjectName): Project!
 | 
			
		||||
  createProject(input: NewProject!): Project! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
 | 
			
		||||
  deleteProject(input: DeleteProject!):
 | 
			
		||||
    DeleteProjectPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateProjectName(input: UpdateProjectName):
 | 
			
		||||
    Project! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewProject {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,14 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createProjectLabel(input: NewProjectLabel!): ProjectLabel!
 | 
			
		||||
  deleteProjectLabel(input: DeleteProjectLabel!): ProjectLabel!
 | 
			
		||||
  updateProjectLabel(input: UpdateProjectLabel!): ProjectLabel!
 | 
			
		||||
  updateProjectLabelName(input: UpdateProjectLabelName!): ProjectLabel!
 | 
			
		||||
  updateProjectLabelColor(input: UpdateProjectLabelColor!): ProjectLabel!
 | 
			
		||||
  createProjectLabel(input: NewProjectLabel!):
 | 
			
		||||
    ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteProjectLabel(input: DeleteProjectLabel!):
 | 
			
		||||
    ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateProjectLabel(input: UpdateProjectLabel!):
 | 
			
		||||
    ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateProjectLabelName(input: UpdateProjectLabelName!):
 | 
			
		||||
    ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateProjectLabelColor(input: UpdateProjectLabelColor!):
 | 
			
		||||
    ProjectLabel! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewProjectLabel {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,10 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createProjectMember(input: CreateProjectMember!): CreateProjectMemberPayload!
 | 
			
		||||
  deleteProjectMember(input: DeleteProjectMember!): DeleteProjectMemberPayload!
 | 
			
		||||
  updateProjectMemberRole(input: UpdateProjectMemberRole!): UpdateProjectMemberRolePayload!
 | 
			
		||||
  setProjectOwner(input: SetProjectOwner!): SetProjectOwnerPayload!
 | 
			
		||||
  createProjectMember(input: CreateProjectMember!):
 | 
			
		||||
    CreateProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteProjectMember(input: DeleteProjectMember!):
 | 
			
		||||
    DeleteProjectMemberPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateProjectMemberRole(input: UpdateProjectMemberRole!):
 | 
			
		||||
    UpdateProjectMemberRolePayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input CreateProjectMember {
 | 
			
		||||
@@ -36,13 +38,3 @@ type UpdateProjectMemberRolePayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  member: Member!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input SetProjectOwner {
 | 
			
		||||
  projectID: UUID!
 | 
			
		||||
  ownerID: UUID!
 | 
			
		||||
}
 | 
			
		||||
type SetProjectOwnerPayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  prevOwner: Member!
 | 
			
		||||
  newOwner: Member!
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,24 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTask(input: NewTask!): Task!
 | 
			
		||||
  deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
 | 
			
		||||
  createTask(input: NewTask!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteTask(input: DeleteTaskInput!):
 | 
			
		||||
    DeleteTaskPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
 | 
			
		||||
  updateTaskDescription(input: UpdateTaskDescriptionInput!): Task!
 | 
			
		||||
  updateTaskLocation(input: NewTaskLocation!): UpdateTaskLocationPayload!
 | 
			
		||||
  updateTaskName(input: UpdateTaskName!): Task!
 | 
			
		||||
  setTaskComplete(input: SetTaskComplete!): Task!
 | 
			
		||||
  updateTaskDueDate(input: UpdateTaskDueDate!): Task!
 | 
			
		||||
  updateTaskDescription(input: UpdateTaskDescriptionInput!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskLocation(input: NewTaskLocation!):
 | 
			
		||||
    UpdateTaskLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskName(input: UpdateTaskName!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  setTaskComplete(input: SetTaskComplete!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskDueDate(input: UpdateTaskDueDate!):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
 | 
			
		||||
  assignTask(input: AssignTaskInput): Task!
 | 
			
		||||
  unassignTask(input: UnassignTaskInput): Task!
 | 
			
		||||
  assignTask(input: AssignTaskInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  unassignTask(input: UnassignTaskInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTask {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,23 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTaskChecklist(input: CreateTaskChecklist!): TaskChecklist!
 | 
			
		||||
  deleteTaskChecklist(input: DeleteTaskChecklist!): DeleteTaskChecklistPayload!
 | 
			
		||||
  updateTaskChecklistName(input: UpdateTaskChecklistName!): TaskChecklist!
 | 
			
		||||
  createTaskChecklistItem(input: CreateTaskChecklistItem!): TaskChecklistItem!
 | 
			
		||||
  updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!): TaskChecklistItem!
 | 
			
		||||
  setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!): TaskChecklistItem!
 | 
			
		||||
  deleteTaskChecklistItem(input: DeleteTaskChecklistItem!): DeleteTaskChecklistItemPayload!
 | 
			
		||||
  createTaskChecklist(input: CreateTaskChecklist!):
 | 
			
		||||
    TaskChecklist! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteTaskChecklist(input: DeleteTaskChecklist!):
 | 
			
		||||
    DeleteTaskChecklistPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskChecklistName(input: UpdateTaskChecklistName!):
 | 
			
		||||
    TaskChecklist! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  createTaskChecklistItem(input: CreateTaskChecklistItem!):
 | 
			
		||||
    TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskChecklistItemName(input: UpdateTaskChecklistItemName!):
 | 
			
		||||
    TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  setTaskChecklistItemComplete(input: SetTaskChecklistItemComplete!):
 | 
			
		||||
    TaskChecklistItem! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteTaskChecklistItem(input: DeleteTaskChecklistItem!):
 | 
			
		||||
    DeleteTaskChecklistItemPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskChecklistLocation(input: UpdateTaskChecklistLocation!):
 | 
			
		||||
    UpdateTaskChecklistLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskChecklistItemLocation(input: UpdateTaskChecklistItemLocation!):
 | 
			
		||||
    UpdateTaskChecklistItemLocationPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
 | 
			
		||||
  updateTaskChecklistLocation(input: UpdateTaskChecklistLocation!): UpdateTaskChecklistLocationPayload!
 | 
			
		||||
  updateTaskChecklistItemLocation(input: UpdateTaskChecklistItemLocation!): UpdateTaskChecklistItemLocationPayload!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateTaskChecklistItemLocation {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createTaskGroup(input: NewTaskGroup!): TaskGroup!
 | 
			
		||||
  updateTaskGroupLocation(input: NewTaskGroupLocation!): TaskGroup!
 | 
			
		||||
  updateTaskGroupName(input: UpdateTaskGroupName!): TaskGroup!
 | 
			
		||||
  deleteTaskGroup(input: DeleteTaskGroupInput!): DeleteTaskGroupPayload!
 | 
			
		||||
  createTaskGroup(input: NewTaskGroup!):
 | 
			
		||||
    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskGroupLocation(input: NewTaskGroupLocation!):
 | 
			
		||||
    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  updateTaskGroupName(input: UpdateTaskGroupName!):
 | 
			
		||||
    TaskGroup! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  deleteTaskGroup(input: DeleteTaskGroupInput!):
 | 
			
		||||
    DeleteTaskGroupPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTaskGroupLocation {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,11 @@ type ToggleTaskLabelPayload {
 | 
			
		||||
  task: Task!
 | 
			
		||||
}
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  addTaskLabel(input: AddTaskLabelInput): Task!
 | 
			
		||||
  removeTaskLabel(input: RemoveTaskLabelInput): Task!
 | 
			
		||||
  toggleTaskLabel(input: ToggleTaskLabelInput!): ToggleTaskLabelPayload!
 | 
			
		||||
  addTaskLabel(input: AddTaskLabelInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  removeTaskLabel(input: RemoveTaskLabelInput):
 | 
			
		||||
    Task! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
  toggleTaskLabel(input: ToggleTaskLabelInput!):
 | 
			
		||||
    ToggleTaskLabelPayload! @hasRole(roles: [ADMIN], level: PROJECT, type: PROJECT)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  deleteTeam(input: DeleteTeam!): DeleteTeamPayload!
 | 
			
		||||
  createTeam(input: NewTeam!): Team!
 | 
			
		||||
  deleteTeam(input: DeleteTeam!):
 | 
			
		||||
    DeleteTeamPayload! @hasRole(roles:[ ADMIN], level: TEAM, type: TEAM)
 | 
			
		||||
  createTeam(input: NewTeam!):
 | 
			
		||||
    Team! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input NewTeam {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  setTeamOwner(input: SetTeamOwner!): SetTeamOwnerPayload!
 | 
			
		||||
  createTeamMember(input: CreateTeamMember!): CreateTeamMemberPayload!
 | 
			
		||||
  updateTeamMemberRole(input: UpdateTeamMemberRole!): UpdateTeamMemberRolePayload!
 | 
			
		||||
  deleteTeamMember(input: DeleteTeamMember!): DeleteTeamMemberPayload!
 | 
			
		||||
  createTeamMember(input: CreateTeamMember!):
 | 
			
		||||
    CreateTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
 | 
			
		||||
  updateTeamMemberRole(input: UpdateTeamMemberRole!):
 | 
			
		||||
    UpdateTeamMemberRolePayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
 | 
			
		||||
  deleteTeamMember(input: DeleteTeamMember!):
 | 
			
		||||
    DeleteTeamMemberPayload! @hasRole(roles: [ADMIN], level: TEAM, type: TEAM)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input DeleteTeamMember {
 | 
			
		||||
@@ -35,16 +38,6 @@ input UpdateTeamMemberRole {
 | 
			
		||||
 | 
			
		||||
type UpdateTeamMemberRolePayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  teamID: UUID!
 | 
			
		||||
  member: Member!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input SetTeamOwner {
 | 
			
		||||
  teamID: UUID!
 | 
			
		||||
  userID: UUID!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SetTeamOwnerPayload {
 | 
			
		||||
  ok: Boolean!
 | 
			
		||||
  prevOwner: Member!
 | 
			
		||||
  newOwner: Member!
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,16 @@
 | 
			
		||||
extend type Mutation {
 | 
			
		||||
  createRefreshToken(input: NewRefreshToken!): RefreshToken!
 | 
			
		||||
  createUserAccount(input: NewUserAccount!): UserAccount!
 | 
			
		||||
  deleteUserAccount(input: DeleteUserAccount!): DeleteUserAccountPayload!
 | 
			
		||||
  createUserAccount(input: NewUserAccount!):
 | 
			
		||||
    UserAccount! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
  deleteUserAccount(input: DeleteUserAccount!):
 | 
			
		||||
    DeleteUserAccountPayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
 | 
			
		||||
  logoutUser(input: LogoutUser!): Boolean!
 | 
			
		||||
  clearProfileAvatar:  UserAccount!
 | 
			
		||||
 | 
			
		||||
  updateUserPassword(input: UpdateUserPassword!): UpdateUserPasswordPayload!
 | 
			
		||||
  updateUserRole(input: UpdateUserRole!): UpdateUserRolePayload!
 | 
			
		||||
  updateUserRole(input: UpdateUserRole!):
 | 
			
		||||
   UpdateUserRolePayload! @hasRole(roles: [ADMIN], level: ORG, type: ORG)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input UpdateUserPassword {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user