Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support middleware #64

Closed
Weakky opened this issue Mar 1, 2019 · 19 comments
Closed

Support middleware #64

Weakky opened this issue Mar 1, 2019 · 19 comments
Labels
community/help-wanted Not our focus, but accepting PRs type/feat Add a new capability or enhance an existing one

Comments

@Weakky
Copy link
Member

Weakky commented Mar 1, 2019

Description

As nexus recently implemented some kind of authorization system (which is a kind of middleware), I think we should also allow for more generic middlewares.

Proposal

Middlewares should be definable at every level of the API:

Globally

import { makeSchema } from 'nexus'
import * as allTypes from './graphql'
import { logging, auth } from './middlewares'

makeSchema({
  types: allTypes,
  middlewares: [logging, auth]
})

On an object type (not sure whether it makes sense to have it elsewhere)

import { objectType } from 'nexus'
import { someMiddleware } from './middlewares'

export const Query = objectType({
  name: 'Query',
  middlewares: [someMiddleware],
  definition(t) { ... }
})

On a field

import { objectType } from 'nexus'
import { fieldMiddleware } from './middlewares'

export const Mutation = objectType({
  name: 'Mutation',
  definition(t) {
    t.field('login', {
      middlewares: [fieldMiddleware],
      resolve(root, args, ctx) { ... }
    })
  }
})

Concerns

The current proposal might makes it hard to build abstractions upon it.

A use-case might be to provide an ACL style authorization system, in which you probably want to handle everything in one file.

With the current proposal, it doesn't seem trivial (or impossible) to inject middlewares with enough granularity (eg: on a field), because fields are evaluated later during the schema construction.

graphql-middleware use the following data-structure to define middlewares at every level of the API:

const beepMiddleware = {
  Query: {
    hello: async (resolve, parent, args, context, info) => {
      // Implement middleware on `Query.hello` field
    },
  },
}

It might be necessary to have something alike on the global middlewares option (and eventually on types)

import { makeSchema } from 'nexus'
import * as allTypes from './graphql'
import { middleware1, middleware2 } from './middlewares'

makeSchema({
  types: allTypes,
  middlewares: {
    Query: {
      hello: [middleware1, middleware2],
      world: [middleware1]
    }
  }
})

Open questions

  • Should we keep authorize as a separate use-case ?
  • Or should we make authorize a simple use-case of middlewares and provide docs?

Thanks 🙌

@kandros
Copy link
Contributor

kandros commented Mar 1, 2019

love the concept! being able to add a middleware at every level open so much scenarios.
Being able to opt-out from a specific middleware or make it conditional would be great for nested Type resolvers or field resolvers

I feel like authorize and possibly validate deserve a special behaviour

@tgriesser tgriesser added the type/feat Add a new capability or enhance an existing one label Mar 3, 2019
@tgriesser
Copy link
Member

Interesting. I like the idea, going to think more about how it could work, what it would mean for types, how it would work in terms of execution, etc.

@beeplin
Copy link

beeplin commented Mar 8, 2019

TypeGraphQL already has @Authorize @Validate and @Middleware for this purpose. It would be nice for Nexus to support this. Could be a decent replacement for graphql-shield, etc.

@rjdmacedo
Copy link

rjdmacedo commented Mar 10, 2019

This is actually what I was looking for. Do you guys need help? @Weakky Do you have any
ETA for such feature?

@williamluke4
Copy link

Is there anything happening with this?

@tgriesser tgriesser added the community/help-wanted Not our focus, but accepting PRs label Apr 2, 2019
@tgriesser
Copy link
Member

Is there anything happening with this?

Nope, not yet. PR's welcome. Haven't had time to start much work on this aside from starting to think about how it could work.

@beeplin
Copy link

beeplin commented Apr 13, 2019

Any progress or plan for this?

I am considering using nexus or yoga2 in my new project, but the lack of middleware support turns out to be a major (or even only) obstacle.

Currently, we need to use t.prismaFields([...]) to forward graphql requests to prisma, but at the same time we need to safeguard these requests with authentication/authorization/input validation logics. Without such middleware support, the t.prismaFields is virtually useless.

And once I go with yoga2, I cannot even figure out how to make my yoga2 project work together with the existing graphql-middleware/graphql-shield etc.

@P4sca1
Copy link
Contributor

P4sca1 commented Apr 13, 2019

@beeplin You can configure middleware, it is just not build into nexus, which makes it a bit more complex. But it is still fairly easy.

Where is the problem for you using graphql-middleware and graphql-shield?

@beeplin
Copy link

beeplin commented Apr 13, 2019

@P4sca1 this might be related more to yoga2 than nexus: In the old-version graphql-yoga we can easily use graphql-middleware by this way:

import { GraphQLServer } from 'graphql-yoga'

const server = new GraphQLServer({
  typeDefs,
  resolvers,
  middlewares: [
    shield(shields, {
      fallbackRule: deny,
      graphiql: true,
      debug,
    }),
    middlewares,
    forward(...forwardings)('prismaBinding'),
  ],
  context,
  resolverValidationOptions,
  debug,
})

But in the new yoga2, currently we lack comprehensive docs to show how to use middlewares. Even with the ejected version of yoga2 (https://github.com/prisma/yoga2/blob/master/examples/minimal-ejected/src/server.ts) I still cannot figure out where middlewares should fit in.

@P4sca1
Copy link
Contributor

P4sca1 commented Apr 13, 2019

Do you have Access to the apollo Server? If so you could overwrite apolloServer.schema after it was created.

@beeplin
Copy link

beeplin commented Apr 13, 2019

@P4sca1 Thanks! Following your direction I just managed to eject yoga2 and have graphql-middleware working with it (the key point is shown here: https://github.com/prisma/graphql-middleware#standalone-usage):

    import { applyMiddleware } from 'graphql-middleware'

    // ...

    const schema = makePrismaSchema({
      // ...
    })

    applyMiddleware(schema, {
      Query: {
       // middlewares
      },
    })

    const apolloServer = new ApolloServer.ApolloServer({
      schema,
      context,
    })

    // ...

@P4sca1
Copy link
Contributor

P4sca1 commented Apr 19, 2019

I also like the idea of having middleware on a per-type / per-field basis.
It reminds me on how I build my REST api using koa-compose.

It would be great to also have a rule field, which directly integrates with graphql-shield.
That way we could have validation and authorization logic inside the rule field using graphql-shield and custom middleware using the proposed middlewares field.

@beeplin
Copy link

beeplin commented Jul 6, 2019

With prisma 2 It would be nice to have this:

t.crud.createOneUser({ alias: 'signupUser', middleware: xxx })
t.crud.deleteOnePost({ middleware: yyy})

@Hebilicious
Copy link

I have been able to use graphql-middleware without any issues with apollo server, nexus and nexus-prisma, but for some reasons I've been unable to use this package https://github.com/JCMais/graphql-yup-middleware. Has anyone been able to implement graphql-yup-middleware with nexus/nexus-prisma ?
Regarding the open question my vote goes to :
We make authorize a simple use-case of middlewares and provide docs.

@tomecho
Copy link

tomecho commented Aug 1, 2019

@Hebilicious I did something similar on my project which uses nexus prisma, prisma client, graphql yoga, yup etc etc. Because nexus generates the schema I wasn't willing/able to add my validation onto the mutation object directly like they library suggests here (https://github.com/JCMais/graphql-yup-middleware#setting-the-validation-schema-of-each-mutation). Instead I wrote my own middleware (just a few lines long) which just intercepts the mutation and looks at the field name (name of the mutation, createUser, createPost, etc) and matches the mutation name with a yup validator (i just have them sitting in an object). Then we take that validator and validate it versus the incoming arguments.

image

@beeplin
Copy link

beeplin commented Oct 14, 2019

@jasonkuhrt any plan on this?

@Weakky
Copy link
Member Author

Weakky commented Oct 14, 2019

Middlewares should be live soon as part of a plugin system implemented in prisma-labs/graphql-framework-experiment#242. It'll probably be available in beta only first.
In the meantime, using graphql-middleware is a valid workaround 👍

@tgriesser
Copy link
Member

So this is more or less implemented in the latest 0.12.0-rc.4, which will turn into 0.12.0 shortly after a few final patches. Early docs below:

https://nexus.js.org/docs/api-plugins

https://nexus.js.org/docs/plugin-nullabilityguard

https://nexus.js.org/docs/plugin-fieldauthorize

Beginning to open new issues labeled request-for-plugin for discussing/opening the floor for folks to submit plugins. Still working on the validate plugin so it can be run ahead-of-time in the execution phase.

@jhalborg
Copy link

@tgriesser - Is there an issue to track the validate` plugin already somewhere? Coudln't find it here yet. Looking forward to it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community/help-wanted Not our focus, but accepting PRs type/feat Add a new capability or enhance an existing one
Projects
None yet
Development

No branches or pull requests

10 participants