Coder Social home page Coder Social logo

graph_attack's Introduction

GraphAttack

Build Status

GraphQL analyser for blocking & throttling.

Usage

This gem adds a method to limit access to your GraphQL fields by IP address:

class QueryType < GraphQL::Schema::Object
  field :some_expensive_field, String, null: false do
    extension GraphAttack::RateLimit, threshold: 15, interval: 60
  end

  # …
end

This would allow only 15 calls per minute by the same IP address.

Requirements

Requires GraphQL Ruby and a running instance of Redis.

Installation

Add these lines to your application’s Gemfile:

# GraphQL analyser for blocking & throttling by IP.
gem "graph_attack"

And then execute:

$ bundle

Finally, make sure you add the current user’s IP address as ip: to the GraphQL context. E.g.:

class GraphqlController < ApplicationController
  def create
    result = ApplicationSchema.execute(
      params[:query],
      variables: params[:variables],
      context: {
        ip: request.ip,
      },
    )
    render json: result
  end
end

If that key is nil, throttling will be disabled.

Configuration

Custom context key

If you want to throttle using a different value than the IP address, you can choose which context key you want to use with the on option. E.g.:

extension GraphAttack::RateLimit,
          threshold: 15,
          interval: 60,
          on: :client_id

Custom Redis client

Use a custom Redis client instead of the default with the redis_client option:

extension GraphAttack::RateLimit,
          threshold: 15,
          interval: 60,
          redis_client: Redis.new(url: "…")

Common configuration

To have a default configuration for all rate-limited fields, you can create an initializer:

GraphAttack.configure do |config|
  # config.threshold = 15
  # config.interval = 60
  # config.on = :ip
  # config.redis_client = Redis.new
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bin/rake to run the tests and the linter. You can also run bin/console for an interactive prompt that will allow you to experiment.

Versionning

We use SemVer for versioning. For the versions available, see the tags on this repository.

Releasing

To release a new version, update the version number in version.rb and in the CHANGELOG.md. Update the README.md if there are missing segments, make sure tests and linting are pristine by calling bundle && bin/rake, then create a commit for this version, for example with:

git add --patch
git commit -m v`ruby -rbundler/setup -rgraph_attack/version -e "puts GraphAttack::VERSION"`

You can then run bin/rake release, which will assign a git tag, push using git, and push the gem to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/sunny/graph_attack. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

Code of Conduct

Everyone interacting in the GraphAttack project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Authors

Acknowledgments

Hat tip to Rack::Attack for the the name.

graph_attack's People

Contributors

sunny avatar dependabot[bot] avatar dependabot-preview[bot] avatar cfgv avatar italotabatinga avatar

Stargazers

Juan Camilo Hdz avatar Alexey Poimtsev avatar Ahmed Helil avatar Diego Linhares avatar Anthony Nadaletti avatar Fyodor avatar Andrei Andreev avatar Azamat Khudaygulov avatar Sora Ichigo avatar Henrique A. Lavezzo avatar ouyangjinting avatar Tim Kendall avatar Peter Morgenstern avatar Damien White avatar  avatar Gaston Morixe avatar Valentino Rusconi avatar Bruno Prieto avatar Dmitry Salahutdinov avatar Misha Merkushin avatar Buts Johan avatar Micah Frost avatar Marian Kostyk avatar Corptools API avatar Li Ding avatar Thomas Klemm avatar  avatar  avatar  avatar Adrien S avatar Vesa Vänskä avatar Matias Korhonen avatar  avatar Gonçalo Mendes Cabrita avatar Sergey Ponomarev avatar Vladimir Lyzo avatar Nikita Bulai avatar Jonian Guveli avatar Dmitry Polushkin avatar Dmytro Piliugin avatar HelloWorld.cat avatar MorganJ avatar Agathe avatar Olivier Chuiton avatar William Pollet avatar  avatar Fanny Cheung avatar

Watchers

 avatar James Cloos avatar

graph_attack's Issues

Breaking Change from 2.1.0 to 2.2.0

This is more of an FYI, than a bug.

If Redis.current = is used to configure the redis client in your application, then upgrading from 2.1.0 to 2.2.0 will break the Graph Attack redis client, as it switched form using Redis.current to set the redis client to Redis.new.

2.1.0 GraphAttack::RateLimit#redis_client

def redis_client
  options[:redis_client] || Redis.current
end

https://github.com/sunny/graph_attack/blob/v2.1.0/lib/graph_attack/rate_limit.rb#L52

2.2.0 GraphAttack::RateLimit#redis_client

def redis_client
  options[:redis_client] || Redis.new
end

https://github.com/sunny/graph_attack/blob/v2.2.0/lib/graph_attack/rate_limit.rb#L51

Incompatible with new graphql-ruby AST (>=1.10)

I just tried to use this gem but it seems like the analyzer is not compatible with the "new" graphql-ruby AST. It is opt-in, so the gem still works if we don't opt-in the new analyzer, but I think its the new default.

I think those breaking changes were introduced in graphql-ruby 1.10.0, and the linked PR 2363 is pretty huge.

 Class-based schemas using the interpreter must add use GraphQL::Analysis::AST to their schema (and update their custom analyzers, see https://graphql-ruby.org/queries/ast_analysis.html) #2363

Any idea of the scope of changes required?

Ruby 3.2+ support

Hi there, hope you are doing well!

I was wondering if there are any plans of supporting ruby 3.2+. I don't know if there are any incompatibilities.

Thanks in advance and keep up the good work!

Dependabot can't resolve your Ruby dependency files

Dependabot can't resolve your Ruby dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

Bundler::VersionConflict with message: Bundler found conflicting requirements for the Ruby version:
  In Gemfile:
    Ruby (~> 1.9.3.0)

    rake (~> 13.0) was resolved to 13.0.3, which depends on
      Ruby (>= 2.2)

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

View the update logs.

How to include the extension if a conditional is applied?

Gem works only if the key exists in the context, when it doesn't, it raises an exception; but I think it should just not apply and continue execution to resolver, have you considered this or know of a way to exclude the extension if something in the context does not apply?

For now, I did:

module GraphAttack
  class RateLimitInterceptor < RateLimit
    def resolve(object:, arguments:, **_rest)
      rate_limited_field = object.context[rate_limited_key]

      rate_limited_field ? super : yield(object, arguments)
    end

    private

    def rate_limited_key
      options[:on] || :ip
    end
  end
end
field :field_test, FieldTestType, null: true,
  mutation: Api::V1::Mutations::FieldTest do
  extension GraphAttack::RateLimitInterceptor, threshold: 15, interval: 60, on: :device_ip
end

Or you should do something like that:

def resolve(object:, arguments:, **_rest)
  rate_limited_field = object.context[rate_limited_key]

  # unless rate_limited_field
  #   raise GraphAttack::Error, "Missing :#{rate_limited_key} value on the GraphQL context"
  # end

  if rate_limited_field && calls_exceeded_on_query?(rate_limited_field)
    return RateLimited.new('Query rate limit exceeded')
  end

  yield(object, arguments)
end

Let me know if I could help you

Ruby 3+ support

Hey there, first of all, thanks for this library!

I just noticed that the gemspec is locked for Ruby < 2.8. Any plans on supporting more recent versions?

Thank you

Support rate limiting by custom field

Hello there! We've been using this library on our product and it has been working great!

We have another use case, however, that I think might fit in this gem. Instead of rate limiting by IP, we wanted to rate limit by client id.

In our case, this is useful in some sensitive OTP mutations where we want to block brute force attacks per client instead of per IP, considering that an attacker will have a great range of IPs to use.

There's a possible solution below and I'm happy to open a PR if you find this useful too!

suggestion

This solution allows other context keys to be defined as the rate limited key while still keeping IP by default for backward compatibility.

def resolve(object:, arguments:, **_rest)
  value = object.context[value]
  raise GraphAttack::Error, "Missing #{value} value on the GraphQL context" unless value

  return RateLimited.new('Query rate limit exceeded') if calls_exceeded_on_query?(value)

  yield(object, arguments)
end

private

def value
  options[:value] || :ip
end

In our case, we'd use this like:

extension GraphAttack::RateLimit,
                  threshold: 60,
                  interval: 60,
                  redis_client: REDIS,
                  value: :customer_uuid,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.