From 405964b653c19b8796d57424406fd75fea59b6b2 Mon Sep 17 00:00:00 2001 From: izzy lyseggen Date: Fri, 14 Aug 2020 15:23:41 +0100 Subject: [PATCH] feat(auth-dir): break auth into three directives @isAuthorized directive for Stream, Branch, and Commit this is a light wrapper around `authorizeResolver` which finds the `resourceId` depending on where the request is coming from. it's still not super cleand and I will continue thinking about how to improve this despite inconsistencies with the id location --- .../graph/directives/isAuthorizedForBranch.js | 29 +++++++++++++++++++ .../graph/directives/isAuthorizedForCommit.js | 29 +++++++++++++++++++ .../graph/directives/isAuthorizedForStream.js | 29 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 modules/core/graph/directives/isAuthorizedForBranch.js create mode 100644 modules/core/graph/directives/isAuthorizedForCommit.js create mode 100644 modules/core/graph/directives/isAuthorizedForStream.js diff --git a/modules/core/graph/directives/isAuthorizedForBranch.js b/modules/core/graph/directives/isAuthorizedForBranch.js new file mode 100644 index 000000000..234d0f3bd --- /dev/null +++ b/modules/core/graph/directives/isAuthorizedForBranch.js @@ -0,0 +1,29 @@ +const { ApolloError, ForbiddenError, SchemaDirectiveVisitor } = require( 'apollo-server-express' ) +const { defaultFieldResolver } = require( 'graphql' ) +const appRoot = require( 'app-root-path' ) +const { authorizeResolver } = require( `${appRoot}/modules/shared` ) + +module.exports = { + isAuthorizedForStream: class IsAuthorizedForStreamDirective extends SchemaDirectiveVisitor { + visitFieldDefinition( field ) { + const { resolver = field.resolve || defaultFieldResolver, name } = field + const requiredRole = this.args.role + let resourceId + + field.resolve = async function ( parent, args, context, info ) { + if ( info.parentType == 'Subscription' ) { + resourceId = ( parent[ name ].streamId || parent[ name ].id ) + } else { + resourceId = args.branch.streamId + } + + if ( !resourceId ) throw new ApolloError( 'Could not find the resource id' ) + + await authorizeResolver( context.userId, resourceId, requiredRole ) + + const data = await resolver.call( this, parent, args, context, info ) + return data + } + } + } +} diff --git a/modules/core/graph/directives/isAuthorizedForCommit.js b/modules/core/graph/directives/isAuthorizedForCommit.js new file mode 100644 index 000000000..396fb24e2 --- /dev/null +++ b/modules/core/graph/directives/isAuthorizedForCommit.js @@ -0,0 +1,29 @@ +const { ApolloError, ForbiddenError, SchemaDirectiveVisitor } = require( 'apollo-server-express' ) +const { defaultFieldResolver } = require( 'graphql' ) +const appRoot = require( 'app-root-path' ) +const { authorizeResolver } = require( `${appRoot}/modules/shared` ) + +module.exports = { + isAuthorizedForStream: class IsAuthorizedForStreamDirective extends SchemaDirectiveVisitor { + visitFieldDefinition( field ) { + const { resolver = field.resolve || defaultFieldResolver, name } = field + const requiredRole = this.args.role + let resourceId + + field.resolve = async function ( parent, args, context, info ) { + if ( info.parentType == 'Subscription' ) { + resourceId = ( parent[ name ].streamId || parent[ name ].id ) + } else { + resourceId = args.commit.streamId + } + + if ( !resourceId ) throw new ApolloError( 'Could not find the resource id' ) + + await authorizeResolver( context.userId, resourceId, requiredRole ) + + const data = await resolver.call( this, parent, args, context, info ) + return data + } + } + } +} diff --git a/modules/core/graph/directives/isAuthorizedForStream.js b/modules/core/graph/directives/isAuthorizedForStream.js new file mode 100644 index 000000000..a44ee8478 --- /dev/null +++ b/modules/core/graph/directives/isAuthorizedForStream.js @@ -0,0 +1,29 @@ +const { ApolloError, ForbiddenError, SchemaDirectiveVisitor } = require( 'apollo-server-express' ) +const { defaultFieldResolver } = require( 'graphql' ) +const appRoot = require( 'app-root-path' ) +const { authorizeResolver } = require( `${appRoot}/modules/shared` ) + +module.exports = { + isAuthorizedForStream: class IsAuthorizedForStreamDirective extends SchemaDirectiveVisitor { + visitFieldDefinition( field ) { + const { resolver = field.resolve || defaultFieldResolver, name } = field + const requiredRole = this.args.role + let resourceId + + field.resolve = async function ( parent, args, context, info ) { + if ( info.parentType == 'Subscription' ) { + resourceId = ( parent[ name ].streamId || parent[ name ].id ) + } else { + resourceId = ( args.id || args.streamId || args.stream.id ) + } + + if ( !resourceId ) throw new ApolloError( 'Could not find the resource id' ) + + await authorizeResolver( context.userId, resourceId, requiredRole ) + + const data = await resolver.call( this, parent, args, context, info ) + return data + } + } + } +}