Created
June 12, 2018 06:50
-
-
Save dukuo/351f581e54b0af863f7155110e8f39c6 to your computer and use it in GitHub Desktop.
GraphQL Shield Rules class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { shield, and, or, not } from 'graphql-shield' | |
import {rules} from './rules' | |
export const permissions = shield({ | |
Mutation: { | |
createResource: and( rules.isAuthenticated(), rules.hasRoles(["ADMIN", "USER"]), rules.hasScopes(["admin:god", "create:resource"]) ), | |
deleteResource: and( rules.isAuthenticated(), or( rules.hasRole("ADMIN"), rules.hasRole("EDITOR") ) ) | |
} | |
}, { | |
debug:true | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { rule } from 'graphql-shield' | |
import { getUserId } from '../../lib/utils' | |
import * as BBPromise from 'bluebird' | |
/** | |
* Collection of useful methods for checking roles and role scopes from an authenticated user | |
*/ | |
class Rules { | |
/** | |
* Check user roles against Prisma | |
* @param userId (string) User ID from Context | |
* @param role (string) Required Role | |
* @param context | |
*/ | |
private async _hasRole (userId:string, role:string, context:any) { | |
const checkIfUserHasRole = await context.prisma.query.users({ where: { | |
id: userId, | |
roles_some: { | |
type:role | |
} | |
} | |
}) | |
console.log("HAS ROLE ", role, "?", checkIfUserHasRole.length > 0) | |
if(Object.keys(checkIfUserHasRole).length > 0) return true | |
return false | |
} | |
/** | |
* Check scopes from user roles against Prisma | |
* @param userId (string) User ID from Context | |
* @param requiredScope (string) | |
* @param context | |
*/ | |
private async _hasScope (userId:string, requiredScope:string, context:any) { | |
const checkIfUserHasScope = await context.prisma.query.users({ where: { | |
id: userId, | |
roles_some: { | |
scopes_some: { | |
scope: requiredScope | |
} | |
} | |
} | |
}) | |
console.log("HAS SCOPE ", requiredScope, "?", checkIfUserHasScope.length > 0) | |
if(Object.keys(checkIfUserHasScope).length > 0) return true | |
return false | |
} | |
/** | |
* Check whether user is authenticated and if the user exists | |
*/ | |
isAuthenticated() { | |
return rule()(async ( parent, args, context, info ) => { | |
const id = getUserId(context) | |
if(!id) throw Error('User not found') | |
return await context.prisma.exists.User({ id }) | |
}) | |
} | |
/** | |
* Check scopes from user roles in parallel | |
* @param scopes (string[]) | |
*/ | |
hasScopes (scopes:string[]) { | |
return rule()(async ( parent, args, context, info) => { | |
const id = getUserId(context) | |
await BBPromise.all(scopes.map(async (scope) => { | |
if(await this._hasScope(id, scope, context) === false) throw Error('User not found or does not have required permissions') | |
return true | |
})) | |
return true | |
}) | |
} | |
/** | |
* Check if user is authenticated and has the required role scope | |
* @param scope (string) | |
*/ | |
hasScope (scope:string) { | |
return rule()(async ( parent, args, context, info) => { | |
const id = getUserId(context) | |
if ( await this._hasScope(id, scope, context) === false ) throw Error('User not found or does not have required permissions') | |
return true | |
}) | |
} | |
/** | |
* Check user roles in parallel | |
* @param roles (string[]) | |
*/ | |
hasRoles (roles:string[]) { | |
return rule()(async ( parent, args, context, info) => { | |
const id = getUserId(context) | |
await BBPromise.all(roles.map(async (role) => { | |
if(await this._hasRole(id, role, context) === false) throw Error('User not found or doesn\'t have necessary authorization') | |
return true | |
})) | |
return true | |
}) | |
} | |
/** | |
* Check if user is authenticated and has the required role | |
* @param role (string) | |
*/ | |
hasRole (role:string) { | |
return rule()(async ( parent, args, context, info) => { | |
const id = getUserId(context) | |
if(!await this._hasRole(id, role, context)) throw Error('User not found or doesn\'t have necessary authorization') | |
return true | |
}) | |
} | |
} | |
export let rules = new Rules |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey @dukou, I see you've taken one interesting approach! I've never thought of anything like this.
I like the way you pull together all your rules in one class, really interesting. I am not the biggest fan of classes myself, but I see great benefit in how you do it. Would love to see more of this examples! 🎉 🙂