graphql-community / graphql-community/graphql-directive-auth
Redesign @hasRole directive: make it as flexible as possible
- Dominant language
- TypeScript
- Stars
- 135
- Forks
- 12
- PR merge metrics
- No merged PRs in 30d
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:
```graphql
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:
```graphql
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).
2. 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:
```graphql
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)
...
}
```
3. 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):
```graphql
@hasRole(roles: "or(ADMIN, and(OWNER, not(EDITOR)))")
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reviewing the current @hasRole implementation and the discussion in this issue, then compare the requested directive locations with graphql-js's directiveLocation.js. The scope covers role arguments, field and input permissions, and nested logical operations; done would require an agreed design and corresponding implementation and tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, typescript
- Domain
- api, authorization, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100