Skip to content

Commit

Permalink
feat(graphql): add query authorisation to graphql
Browse files Browse the repository at this point in the history
  • Loading branch information
bahdcoder committed Apr 5, 2021
1 parent 63f32a7 commit 4064f3f
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 91 deletions.
6 changes: 4 additions & 2 deletions packages/common/typings/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ declare module '@tensei/common/config' {
import { Logger } from 'pino'
import Emittery from 'emittery'
import { Request, Response, Application } from 'express'
import { EntityManager } from '@mikro-orm/core'
import { EntityManager, AnyEntity } from '@mikro-orm/core'
import { sanitizer, validator } from 'indicative'
import { EventContract } from '@tensei/common/events'
import { FindOptions, FilterQuery } from '@mikro-orm/core'
Expand Down Expand Up @@ -204,6 +204,7 @@ declare module '@tensei/common/config' {
res: Response
pubsub: PubSub
connection?: ExecutionParams
entity: AnyEntity
authenticationError: (message?: string) => unknown
forbiddenError: (message?: string) => unknown
validationError: (message?: string) => unknown
Expand Down Expand Up @@ -281,7 +282,8 @@ declare module '@tensei/common/config' {
permissions: string[]
}
type AuthorizeFunction<RequestType = Request> = (
ctx: RequestType
ctx: RequestType,
entity: AnyEntity
) => boolean | Promise<boolean>
type HookFunction<EntityType = DataPayload> = (
payload: EventArgs<EntityType>,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/Tensei.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export class Tensei implements TenseiContract {
query: RouteContract
) => {
const authorized = await Promise.all(
query.config.authorize.map(fn => fn(ctx as any))
query.config.authorize.map(fn => fn(ctx as any, ctx.entity))
)

if (
Expand Down
10 changes: 5 additions & 5 deletions packages/create-tensei-app/templates/default/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@tensei/auth": "^0.8.1",
"@tensei/cms": "^0.8.1",
"@tensei/core": "^0.8.1",
"@tensei/media": "^0.8.1",
"@tensei/graphql": "^0.8.1"
"@tensei/auth": "^0.8.2",
"@tensei/cms": "^0.8.2",
"@tensei/core": "^0.8.2",
"@tensei/media": "^0.8.2",
"@tensei/graphql": "^0.8.2"
},
"scripts": {
"dev": "nodemon index.js",
Expand Down
12 changes: 6 additions & 6 deletions packages/create-tensei-app/templates/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@tensei/auth": "^0.8.1",
"@tensei/cms": "^0.8.1",
"@tensei/core": "^0.8.1",
"@tensei/media": "^0.8.1",
"@tensei/next": "^0.8.1",
"@tensei/rest": "^0.8.1"
"@tensei/auth": "^0.8.2",
"@tensei/cms": "^0.8.2",
"@tensei/core": "^0.8.2",
"@tensei/media": "^0.8.2",
"@tensei/next": "^0.8.2",
"@tensei/rest": "^0.8.2"
},
"scripts": {
"dev": "nodemon index.js",
Expand Down
12 changes: 6 additions & 6 deletions packages/create-tensei-app/templates/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@tensei/auth": "^0.8.1",
"@tensei/cms": "^0.8.1",
"@tensei/core": "^0.8.1",
"@tensei/media": "^0.8.1",
"@tensei/nuxt": "^0.8.1",
"@tensei/rest": "^0.8.1"
"@tensei/auth": "^0.8.2",
"@tensei/cms": "^0.8.2",
"@tensei/core": "^0.8.2",
"@tensei/media": "^0.8.2",
"@tensei/nuxt": "^0.8.2",
"@tensei/rest": "^0.8.2"
},
"scripts": {
"dev": "nodemon index.js",
Expand Down
10 changes: 5 additions & 5 deletions packages/create-tensei-app/templates/rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@tensei/auth": "^0.8.1",
"@tensei/cms": "^0.8.1",
"@tensei/core": "^0.8.1",
"@tensei/media": "^0.8.1",
"@tensei/rest": "^0.8.1"
"@tensei/auth": "^0.8.2",
"@tensei/cms": "^0.8.2",
"@tensei/core": "^0.8.2",
"@tensei/media": "^0.8.2",
"@tensei/rest": "^0.8.2"
},
"scripts": {
"dev": "nodemon index.js",
Expand Down
54 changes: 37 additions & 17 deletions packages/graphql/src/Resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Utils, DataPayload } from '@tensei/common'
import { Configuration, EntityManager } from '@mikro-orm/core'
import { Utils, DataPayload, GraphQlMiddleware } from '@tensei/common'
import { Configuration } from '@mikro-orm/core'
import { parseResolveInfo } from 'graphql-parse-resolve-info'
import {
GraphQlQueryContract,
ResourceContract,
FilterOperators,
graphQlQuery,
AuthorizeFunction,
GraphQLPluginContext
} from '@tensei/common'

Expand All @@ -21,6 +22,25 @@ export const getResolvers = (
) => {
const resolversList: GraphQlQueryContract[] = []

const fetchSingleEntityMiddleware: (resource: ResourceContract) => GraphQlMiddleware = (resource) => async (resolve, parent, args, ctx, info) => {
const data: any = await ctx.manager.findOneOrFail(
resource.data.pascalCaseName,
{
id: args.id
}
)

ctx.entity = data

return resolve(parent, args, ctx, info)
}

const authorizeResourceMiddleware: (authorizers: AuthorizeFunction[]) => GraphQlMiddleware = (authorizers) => async (resolve, parent, args, ctx, info) => {
await authorizeResolver(ctx, authorizers)

return resolve(parent, args, ctx, info)
}

resources.forEach(resource => {
!resource.isHiddenOnApi() &&
!resource.data.hideOnFetchApi &&
Expand All @@ -29,6 +49,7 @@ export const getResolvers = (
.path(resource.data.snakeCaseNamePlural)
.query()
.internal()
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToFetch))
.resource(resource)
.handle(async (_, args, ctx, info) => {
const data: any[] = await ctx.manager.find(
Expand Down Expand Up @@ -58,6 +79,7 @@ export const getResolvers = (
.query()
.internal()
.resource(resource)
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToFetch))
.handle(async (_, args, ctx) => {
const count = await ctx.manager.count(
resource.data.pascalCaseName,
Expand All @@ -76,14 +98,10 @@ export const getResolvers = (
.path(resource.data.snakeCaseName)
.query()
.internal()
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToShow), fetchSingleEntityMiddleware(resource))
.resource(resource)
.handle(async (_, args, ctx, info) => {
const data: any = await ctx.manager.findOneOrFail(
resource.data.pascalCaseName,
{
id: args.id
}
)
const data = ctx.entity

await Utils.graphql.populateFromResolvedNodes(
resources,
Expand All @@ -106,6 +124,7 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToCreate))
.handle(async (_, args, ctx, info) => {
const [passed, payload] = await Utils.validator(
resource,
Expand Down Expand Up @@ -164,6 +183,7 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToCreate))
.handle(async (_, args, ctx, info) => {
const data: any[] = args.objects.map((object: any) =>
ctx.manager.create(
Expand Down Expand Up @@ -236,10 +256,9 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(fetchSingleEntityMiddleware(resource), authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToUpdate))
.handle(async (_, args, ctx, info) => {
const data: any = await ctx.manager
.getRepository<any>(resource.data.pascalCaseName)
.findOneOrFail(args.id)
const data: any = ctx.entity

const [passed, payload] = await Utils.validator(
resource,
Expand Down Expand Up @@ -296,6 +315,7 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToUpdate))
.handle(async (_, args, ctx, info) => {
const data = await ctx.manager.find(
resource.data.pascalCaseName,
Expand Down Expand Up @@ -356,10 +376,9 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(fetchSingleEntityMiddleware(resource), authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToDelete))
.handle(async (_, args, ctx, info) => {
const data: any = await ctx.manager
.getRepository<any>(resource.data.pascalCaseName)
.findOneOrFail(args.id)
const data: any = ctx.entity

await Utils.graphql.populateFromResolvedNodes(
resources,
Expand Down Expand Up @@ -399,6 +418,7 @@ export const getResolvers = (
.mutation()
.internal()
.resource(resource)
.middleware(authorizeResourceMiddleware(resource.authorizeCallbacks.authorizedToDelete))
.handle(async (_, args, ctx, info) => {
const data = await ctx.manager.find(
resource.data.pascalCaseName,
Expand Down Expand Up @@ -557,15 +577,15 @@ export const allOperators = filterOperators.concat(topLevelOperators)

export const authorizeResolver = async (
ctx: GraphQLPluginContext,
query: GraphQlQueryContract
authorizers: AuthorizeFunction[]
) => {
const authorized = await Promise.all(
query.config.authorize.map(fn => fn(ctx as any))
authorizers.map(fn => fn(ctx as any, ctx.entity))
)

if (
authorized.filter(result => result).length !==
query.config.authorize.length
authorizers.length
) {
throw ctx.forbiddenError('Unauthorized.')
}
Expand Down
4 changes: 2 additions & 2 deletions packages/graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ input id_where_query {
// await middleware(_, args, ctx, info)
}

await authorizeResolver(ctx, query)
await authorizeResolver(ctx, query.config.authorize)

return withFilter(
() =>
Expand Down Expand Up @@ -815,7 +815,7 @@ input id_where_query {
currentCtx().graphQlQueries.forEach(query => {
query.middleware(
async (resolve, parent, args, ctx, info) => {
await authorizeResolver(ctx, query)
await authorizeResolver(ctx, query.config.authorize)

return resolve(parent, args, ctx, info)
}
Expand Down
9 changes: 9 additions & 0 deletions packages/rest/plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AnyEntity } from '@mikro-orm/core'

declare global {
namespace Express {
export interface Request {
entity: AnyEntity
}
}
}
Loading

0 comments on commit 4064f3f

Please sign in to comment.