redwoodjs-com-archive icon indicating copy to clipboard operation
redwoodjs-com-archive copied to clipboard

rateLimit directive

Open pi0neerpat opened this issue 2 years ago • 2 comments

Howdy, I'm almost done implementing rate limiting using the envelop plugin. Wanted to start throwing together docs for this so others can use them.

Todo:

  • [ ] Finish Redis store
  • [ ] Test the final implementation

Docs for using @envelop/rate-limiter

📖 Adpated for Redwood from https://www.envelop.dev/plugins/use-rate-limiter

First install the necessary packages:

yarn add @envelop/rate-limiter graphql-rate-limit

Generate the rateLimit directive, and choose "Validator" when prompted:

yarn rw g directive rateLimit

We'll need to make a few changes to the directive, so it can accept arguments:

// api/src/directives/rateLimit/rateLimit.js
export const schema = gql`
  """
  Use @rateLimit to validate access to a field, query or mutation.
  """
-  directive @rateLimit on FIELD_DEFINITION
+  directive @rateLimit(max: Int, window: String, message: String) on FIELD_DEFINITION

Then add the directive to your SDLs e.g.:

type Query {
  posts: [Post]! @rateLimit(
    window: "5s", // time interval window for request limit quota
    max: 10,  // maximum requests allowed in time window
    message: "Too many calls!"  // quota reached error message
  )
  # unlimitedField: String
}

Since our function is serverless, we need to keep in mind that using memory store is probably not the best idea. Instead, let's use Redis to manage our IP address logging. Create a redis entity:

// api/src/lib/redis.js

// https://github.com/teamplanes/graphql-rate-limit#redis-store-usage
// TODO

Lastly, add the plugin to the graphql server, and specify the store option to use our Redis instance:

// api/src/functions/graphql.js
import { useRateLimiter } from '@envelop/rate-limiter'
import { createRateLimitDirective, RedisStore } from 'graphql-rate-limit';

import redis from "src/lib/redis"

const identifyFn = async (context) => context.request.ip

export const handler = createGraphQLHandler({
  extraPlugins: [
    useRateLimiter({
      identifyFn,
      // https://github.com/teamplanes/graphql-rate-limit#redis-store-usage
      store: new RedisStore(redis.createClient())
    }),
  ],

pi0neerpat avatar Oct 12 '21 22:10 pi0neerpat

@pi0neerpat Thanks for taking this on -- in fact after @dac09 and I implemented the Validator and Transformer directives, I realized that "traditional" directives might collide -- but then I realized, maybe not!

I hoped (but did not confirm) that since the directive in the schema -- which is declared by the plugin -- is neither a Validator or Transformer:

https://github.com/redwoodjs/redwood/blob/91f907c1b6d3bc89d06bc1333b4394439f4f8803/packages/graphql-server/src/plugins/useRedwoodDirective.ts#L135

I hoped that the useRedwoodDirective would just let it work as normal.

But regardless:

@rateLimit(max: Int, window: String, message: String)

You can pass in arguments. They are available as the directiveArgs:

https://github.com/redwoodjs/redwood/blob/91f907c1b6d3bc89d06bc1333b4394439f4f8803/packages/graphql-server/src/plugins/useRedwoodDirective.ts#L18

That's how requireAuth can look at the roles.

As for Redis, have a look at ioredis.

I used it to implement the responseCache for Redis here:

https://github.com/dotansimha/envelop/blob/4cae3427244f683683ffee5046303b76a926dc43/packages/plugins/response-cache-redis/src/redis-cache.ts#L1

dthyresson avatar Oct 12 '21 23:10 dthyresson

Also, have you seen the redis store provided by rate-limiter?

https://github.com/teamplanes/graphql-rate-limit/blob/master/src/lib/redis-store.ts

dthyresson avatar Oct 12 '21 23:10 dthyresson