Skip to content

Redesign @hasRole directive: make it as flexible as possible #17

@FluorescentHallucinogen

Description

  1. Take array of enums instead of string as argument in @hasRole.

(Please also keep in mind that roles (permission subsets) can be non-overlapping.)

Current implementation:

directive @hasRole(role: String) on FIELD | FIELD_DEFINITION

type User
  name: String
  banned: Boolean @hasRole(role: "admin")
  canPost: Boolean @hasRole(role: "reviewer, admin")
}

Proposed implementation:

directive @hasRole(roles: [Role] = [UNKNOWN]) on FIELD | FIELD_DEFINITION

enum Role {
  ADMIN
  REVIEWER
  USER
  UNKNOWN
}

type User
  name: String
  banned: Boolean @hasRole(roles: [ADMIN])
  canPost: Boolean @hasRole(roles: [REVIEWER, ADMIN])
}

It seems that an array of strings would be more flexible than an array of enums (because you don't need to update enum in the GraphQL SDL to add a new role), but it less type-safe (you don't get a list of possible values e.g. for autocomplete).

  1. Add support of other directive locations besides FIELD_DEFINITION to apply the directive to e.g. mutations and fields in the input types used in these mutations for more granular permissions.

Did I understand correctly that @hasRole directive can only be applied to FIELD_DEFINITION, while there are many places where directives (in general) can be applied (see the full list here: https://github.com/graphql/graphql-js/blob/master/src/language/directiveLocation.js)?

Example use case:

Everyone can query, create and update users (except canPost and banned fields), but only users with role ADMIN can delete users.
Only users with role REVIEWER or ADMIN can have access (query, create and update) canPost field.
Only users with role ADMIN can have access (query, create and update) banned field.
Only users with role MANAGER can update verified field.

Proposed implementation:

type User @hasRole(roles: [USER]) {
  name: String
  canPost: Boolean @hasRole(roles: [REVIEWER, ADMIN])
  banned: Boolean @hasRole(roles: [ADMIN])
  ...
}

...

type Mutation {
  createUser(data: UserCreateInput!): User!
  updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput!): User!
  deleteUser(where: UserWhereUniqueInput!): User @hasRole(roles: [ADMIN])
  ...
}

...

input UserUpdateInput {
  phone: String
  verified: Boolean! @hasRole(roles: [MANAGER]) @default(value: false)
  ...
}
  1. Add support of logical operations

Currently, @hasRole(role: "user, admin") checks if user has some (at least one any) of specified roles. What about all, none and other more complex cases? (Please also keep in mind that roles (permission subsets) can be non-overlapping.)

What about adding support of logic operations for nest rules (similar to https://github.com/maticzav/graphql-shield#and-or-not)? Something like (the syntax is subject to discussion):

@hasRole(roles: "or(ADMIN, and(OWNER, not(EDITOR)))")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions