Files
speckle-server/modules/shared/index.js
T
Dimitrie Stefanescu 0225fcedc0 feat(graphql): scaffoled permission mutations on streams
Also, intentionally broke a test on how permissions are handled.
2020-04-21 00:11:15 +01:00

220 lines
5.4 KiB
JavaScript

'use strict'
const { ForbiddenError, ApolloError } = require( 'apollo-server-express' )
const debug = require( 'debug' )( 'speckle:middleware' )
const root = require( 'app-root-path' )
const knex = require( `${root}/db/knex` )
const { validateToken } = require( `${root}/modules/core/users/services` )
/*
Authentication middleware: checks bearer token validity and scope validation
*/
// TODO: Cache results
function authenticate( scope, mandatory ) {
mandatory = mandatory !== false // defaults to true if not provided
return async ( req, res, next ) => {
debug( `🔑 authenticate middleware called` )
if ( !req.headers.authorization && mandatory ) {
return res.status( 403 ).send( { error: 'No credentials provided' } ) // next (err)?
}
if ( req.headers.authorization ) {
let token = req.headers.authorization.split( ' ' )[ 1 ]
let { valid, scopes, userId } = await validateToken( token )
if ( !valid && mandatory ) {
return res.status( 403 ).send( { error: 'Invalid authorization' } )
}
if ( scopes.indexOf( scope ) === -1 && scopes.indexOf( '*' ) === -1 ) {
return res.status( 403 ).send( { error: 'Invalid scope' } )
}
req.user = { id: userId, scopes: scopes }
return next( )
}
return next( )
}
}
/*
Graphql server context helper
*/
async function contextApiTokenHelper( { req, res } ) {
// TODO: Cache results for 5 minutes
if ( req.headers.authorization ) {
let token = req.headers.authorization.split( ' ' )[ 1 ]
let { valid, scopes, userId } = await validateToken( token )
if ( !valid ) {
return { auth: false }
}
return { auth: true, userId, token, scopes }
}
}
/*
Graphql scope validator
*/
async function validateScopes( scopes, scope ) {
if ( !scopes )
throw new ForbiddenError( 'You do not have the required priviliges.' )
if ( scopes.indexOf( scope ) === -1 && scopes.indexOf( '*' ) === -1 )
throw new ForbiddenError( 'You do not have the required priviliges.' )
}
/*
Authorization middleware: checks user id against access control lists
*/
let roles = { admin: 1000, owner: 300, write: 200, read: 100 }
// TODO: Cache results
function authorize( aclTable, resourceTable, requiredRole ) {
let ACL = ( ) => knex( aclTable )
let Resource = ( ) => knex( resourceTable )
return async ( req, res, next ) => {
debug( '🔑 authorization middleware called' )
try {
let { isPublic } = await Resource( ).where( { id: req.params.resourceId } ).select( 'isPublic' ).first( )
if ( isPublic ) return next( )
} catch ( e ) {
let err = new Error( `${req.params.resourceId} was not found.` )
err.status = 404
return next( err )
}
if ( !req.user ) return res.status( 401 ).send( { error: 'Unauthorized' } )
let [ entry ] = await ACL( ).where( { resourceId: req.params.resourceId, userId: req.user.id } ).select( '*' )
if ( !entry ) {
return res.status( 401 ).send( { error: 'Unauthorized' } )
}
if ( roles[ entry.role ] >= roles[ requiredRole ] ) {
req.user.role = entry.role
return next( )
} else {
return res.status( 401 ).send( { error: 'Unauthorized' } )
}
}
}
/*
Graphql authorization: checks user id against access control lists
*/
async function authorizeResolver( userId, resourceId, aclTable, resourceTable, requiredRole ) {
let ACL = ( ) => knex( aclTable )
let Resource = ( ) => knex( resourceTable )
try {
let { isPublic } = await Resource( ).where( { id: resourceId } ).select( 'isPublic' ).first( )
if ( isPublic ) return true
} catch ( e ) {
throw new ApolloError( `Resource of type ${resourceTable} with ${resourceId} not found.` )
}
if ( !userId ) throw new AuthenticationError( 'No user id found.' )
let [ entry ] = await ACL( ).where( { resourceId: resourceId, userId: userId } ).select( '*' )
if ( !entry )
throw new ForbiddenError( 'You are not authorized.' )
if ( roles[ entry.role ] >= roles[ requiredRole ] ) {
return true
}
throw new ForbiddenError( 'You are not authorized.' )
}
/*
Announcements: orchestrates pushing out events to any subscribers.
(TODO: implement!)
*/
function announce( eventName, eventScope ) {
return async ( req, res, next ) => {
debug( `📣 announce middleware called: ${eventName}:${eventScope}` )
debug( `Event data: ${JSON.stringify( req.eventData )}` )
next( )
}
}
let customMiddleware = {}
module.exports = {
// Express middleware
get authenticate( ) {
return authenticate
},
set authenticate( value ) {
authenticate = value
},
get authorize( ) {
return authorize
},
set authorize( value ) {
authorize = value
},
get announce( ) {
return announce
},
set announce( value ) {
announce = value
},
// Grahpql helpers
get contextApiTokenHelper( ) {
return contextApiTokenHelper
},
set contextApiTokenHelper( value ) {
contextApiTokenHelper = value
},
get validateScopes( ) {
return validateScopes
},
set validateScopes( value ) {
validateScopes = value
},
get authorizeResolver( ) {
return authorizeResolver
},
set authorizeResolver( value ) {
authorizeResolver = value
},
// Custom helpers
registerCustomMiddleware( name, fn ) {
customMiddleware[ name ] = fn
},
getCustomMiddleware( name ) {
return customMiddleware[ name ]
}
}