graph_attack 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd5c8edf29d269a309957b279c0690cb46f897ca4e02c8909cd80816b9ce76fb
4
- data.tar.gz: ccf872288c199a97d55a36565998e04ca94edc982f1318bd66a67b70140aac9a
3
+ metadata.gz: 71a5e6c0ce41ca59713a49108f5ebcc3a579aefcd1e95273c495a5ae1e4fd6f6
4
+ data.tar.gz: 5430e07ebf58ac5b9addbe8b397f6fd832fa56091112136f8ce972cd2e1fe0aa
5
5
  SHA512:
6
- metadata.gz: 5d7f367fa3125069280b021f6404a19e6a5bcba197ea3c3408b066eadb593e295f2cabcb7d3d4cda5ef0b2a806ac2055dc3943559257aea208dd077a62ff468c
7
- data.tar.gz: 52adf301f34c476a6b6f97013175bc0036c588788d158d971ad553d62c86c54ac27ad1be83be6c99a120a0b90bf7b2e4da58455bd2988e9fd523ffa92956c203
6
+ metadata.gz: cef61dfd8f249877fcdbd6ae1962b6678efd280ef415989d6abe22ebb7e8db68dd164ce47ecce610548ac13352471044741c0bf6918ca050dc2193a5178ab5af
7
+ data.tar.gz: fdf832b9e228ccbf8aa66777f2f9334a7c32a25a5435e6017e0ac8073c7c636cd21dc06f0416a8abbf9f063ff30f64f209d55d9979e2eeea27fd7dff4f1ffe84
data/.rubocop.yml CHANGED
@@ -5,6 +5,7 @@ require:
5
5
  AllCops:
6
6
  TargetRubyVersion: 2.7
7
7
  DisplayCopNames: true
8
+ NewCops: enable
8
9
 
9
10
  # Do not sort gems in Gemfile, since we are grouping them by functionality.
10
11
  Bundler/OrderedGems:
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.3
1
+ 2.7.5
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.7.3
4
+ - 2.7.5
5
+ - 3.1.2
5
6
  services:
6
7
  - redis-server
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  unreleased
2
2
  ----------
3
3
 
4
+ v2.0.0
5
+ ------
6
+
7
+ Breaking changes:
8
+ - Drop support for GraphQL legacy schema, please use GraphQL::Ruby's class-based
9
+ syntax exclusively.
10
+
11
+ Feature:
12
+ - Support Ruby 3.
13
+
4
14
  v1.2.0
5
15
  ------
6
16
 
data/README.md CHANGED
@@ -6,36 +6,19 @@ GraphQL analyser for blocking & throttling.
6
6
 
7
7
  ## Usage
8
8
 
9
- This gem adds a method to limit access to your GraphQL fields by IP:
9
+ This gem adds a method to limit access to your GraphQL fields by IP address:
10
10
 
11
11
  ```rb
12
12
  class QueryType < GraphQL::Schema::Object
13
13
  field :some_expensive_field, String, null: false do
14
- extension(GraphAttack::RateLimit, threshold: 15, interval: 60)
14
+ extension GraphAttack::RateLimit, threshold: 15, interval: 60
15
15
  end
16
16
 
17
17
  # …
18
18
  end
19
19
  ```
20
20
 
21
- <details>
22
- <summary>If using GraphQL::Ruby's legacy schema definition</summary>
23
-
24
- ```rb
25
- QueryType = GraphQL::ObjectType.define do
26
- name 'Query'
27
-
28
- field :someExpensiveField do
29
- rate_limit threshold: 15, interval: 60
30
-
31
- # …
32
- end
33
- end
34
- ```
35
-
36
- </details>
37
-
38
- This would allow only 15 calls per minute by the same IP.
21
+ This would allow only 15 calls per minute by the same IP address.
39
22
 
40
23
  ## Requirements
41
24
 
@@ -44,11 +27,11 @@ of [Redis](https://redis.io/).
44
27
 
45
28
  ## Installation
46
29
 
47
- Add these lines to your application's `Gemfile`:
30
+ Add these lines to your applications `Gemfile`:
48
31
 
49
32
  ```ruby
50
33
  # GraphQL analyser for blocking & throttling by IP.
51
- gem 'graph_attack'
34
+ gem "graph_attack"
52
35
  ```
53
36
 
54
37
  And then execute:
@@ -57,22 +40,7 @@ And then execute:
57
40
  $ bundle
58
41
  ```
59
42
 
60
- <details>
61
- <summary>If using GraphQL::Ruby's legacy schema definition</summary>
62
-
63
- Add the query analyser to your schema:
64
-
65
- ```rb
66
- ApplicationSchema = GraphQL::Schema.define do
67
- query_analyzer GraphAttack::RateLimiter.new
68
-
69
- # …
70
- end
71
- ```
72
-
73
- </details>
74
-
75
- Finally, make sure you add the current user's IP address as `ip:` to the
43
+ Finally, make sure you add the current user’s IP address as `ip:` to the
76
44
  GraphQL context. E.g.:
77
45
 
78
46
  ```rb
@@ -96,26 +64,13 @@ Use a custom Redis client instead of the default:
96
64
 
97
65
  ```rb
98
66
  field :some_expensive_field, String, null: false do
99
- extension(
100
- GraphAttack::RateLimit,
101
- threshold: 15,
102
- interval: 60,
103
- redis_client: Redis.new(url: "…"),
104
- )
67
+ extension GraphAttack::RateLimit,
68
+ threshold: 15,
69
+ interval: 60,
70
+ redis_client: Redis.new(url: "…")
105
71
  end
106
72
  ```
107
73
 
108
- <details>
109
- <summary>If using GraphQL::Ruby's legacy schema definition</summary>
110
-
111
- ```rb
112
- query_analyzer GraphAttack::RateLimiter.new(
113
- redis_client: Redis.new(url: "…")
114
- )
115
- ```
116
-
117
- </details>
118
-
119
74
  ## Development
120
75
 
121
76
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -130,8 +85,8 @@ see the tags on this repository.
130
85
  ## Releasing
131
86
 
132
87
  To release a new version, update the version number in `version.rb`, commit,
133
- and then run `bundle exec rake release`, which will create a git tag for the
134
- version, push git commits and tags, and push the `.gem` file to
88
+ and then run `bin/rake release`, which will create a git tag for the version,
89
+ push git commits and tags, and push the gem to
135
90
  [rubygems.org](https://rubygems.org).
136
91
 
137
92
  ## Contributing
data/graph_attack.gemspec CHANGED
@@ -14,13 +14,16 @@ Gem::Specification.new do |spec|
14
14
  spec.description = 'GraphQL analyser for blocking & throttling'
15
15
  spec.homepage = 'https://github.com/sunny/graph_attack'
16
16
 
17
+ spec.metadata['rubygems_mfa_required'] = 'true'
18
+
17
19
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
20
  f.match(%r{^(test|spec|features)/})
19
21
  end
20
22
  spec.bindir = 'exe'
21
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
24
  spec.require_paths = ['lib']
23
- spec.required_ruby_version = ['>= 2.5.7', '< 2.8']
25
+
26
+ spec.required_ruby_version = ['>= 2.5.7', '< 3.2']
24
27
 
25
28
  # This gem is an analyser for the GraphQL ruby gem.
26
29
  spec.add_dependency 'graphql', '>= 1.7.9'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphAttack
4
- VERSION = '1.2.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/graph_attack.rb CHANGED
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql'
4
3
  require 'ratelimit'
5
-
4
+ require 'graphql'
6
5
  require 'graphql/tracing'
7
6
 
8
7
  require 'graph_attack/version'
9
8
 
10
9
  # Class-based schema
11
- require 'graph_attack/rate_limit'
12
10
  require 'graph_attack/error'
11
+ require 'graph_attack/rate_limit'
13
12
  require 'graph_attack/rate_limited'
14
-
15
- # Legacy schema
16
- require 'graph_attack/rate_limiter'
17
- require 'graph_attack/metadata'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graph_attack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fanny Cheung
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-07-27 00:00:00.000000000 Z
12
+ date: 2022-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: graphql
@@ -165,14 +165,13 @@ files:
165
165
  - graph_attack.gemspec
166
166
  - lib/graph_attack.rb
167
167
  - lib/graph_attack/error.rb
168
- - lib/graph_attack/metadata.rb
169
168
  - lib/graph_attack/rate_limit.rb
170
169
  - lib/graph_attack/rate_limited.rb
171
- - lib/graph_attack/rate_limiter.rb
172
170
  - lib/graph_attack/version.rb
173
171
  homepage: https://github.com/sunny/graph_attack
174
172
  licenses: []
175
- metadata: {}
173
+ metadata:
174
+ rubygems_mfa_required: 'true'
176
175
  post_install_message:
177
176
  rdoc_options: []
178
177
  require_paths:
@@ -184,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
184
183
  version: 2.5.7
185
184
  - - "<"
186
185
  - !ruby/object:Gem::Version
187
- version: '2.8'
186
+ version: '3.2'
188
187
  required_rubygems_version: !ruby/object:Gem::Requirement
189
188
  requirements:
190
189
  - - ">="
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Add custom field metadata
4
- GraphQL::Field.accepts_definitions(
5
- rate_limit: GraphQL::Define.assign_metadata_key(:rate_limit),
6
- )
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphAttack
4
- # Query analyser you can add to your GraphQL schema to limit calls by IP.
5
- #
6
- # ApplicationSchema = GraphQL::Schema.define do
7
- # query_analyzer GraphAttack::RateLimiter.new
8
- # end
9
- #
10
- class RateLimiter
11
- class Error < StandardError; end
12
-
13
- class RateLimited < GraphQL::AnalysisError; end
14
-
15
- def initialize(redis_client: Redis.new)
16
- @redis_client = redis_client
17
- end
18
-
19
- def initial_value(query)
20
- {
21
- ip: query.context[:ip],
22
- query_rate_limits: [],
23
- }
24
- end
25
-
26
- def call(memo, visit_type, irep_node)
27
- if rate_limited_node?(visit_type, irep_node)
28
- data = rate_limit_data(irep_node)
29
-
30
- memo[:query_rate_limits].push(data)
31
-
32
- increment_rate_limit(memo[:ip], data[:key])
33
- end
34
-
35
- memo
36
- end
37
-
38
- def final_value(memo)
39
- handle_exceeded_calls_on_queries(memo)
40
- end
41
-
42
- private
43
-
44
- attr_reader :redis_client
45
-
46
- def increment_rate_limit(ip, key)
47
- raise Error, 'Missing :ip value on the GraphQL context' unless ip
48
-
49
- rate_limit(ip).add(key)
50
- end
51
-
52
- def rate_limit_data(node)
53
- data = node.definition.metadata[:rate_limit]
54
-
55
- data.merge(
56
- key: "graphql-query-#{node.name}",
57
- query_name: node.name,
58
- )
59
- end
60
-
61
- def handle_exceeded_calls_on_queries(memo)
62
- rate_limited_queries = memo[:query_rate_limits].map do |limit_data|
63
- next unless calls_exceeded_on_query?(memo[:ip], limit_data)
64
-
65
- limit_data[:query_name]
66
- end.compact
67
-
68
- return unless rate_limited_queries.any?
69
-
70
- queries = rate_limited_queries.join(', ')
71
- RateLimited.new("Query rate limit exceeded on #{queries}")
72
- end
73
-
74
- def calls_exceeded_on_query?(ip, query_limit_data)
75
- rate_limit(ip).exceeded?(
76
- query_limit_data[:key],
77
- threshold: query_limit_data[:threshold],
78
- interval: query_limit_data[:interval],
79
- )
80
- end
81
-
82
- def rate_limit(ip)
83
- @rate_limit ||= {}
84
- @rate_limit[ip] ||= Ratelimit.new(ip, redis: redis_client)
85
- end
86
-
87
- def rate_limited_node?(visit_type, node)
88
- query_field_node?(node) &&
89
- visit_type == :enter &&
90
- node.definition.metadata[:rate_limit]
91
- end
92
-
93
- def query_field_node?(node)
94
- node.owner_type.name == 'Query' &&
95
- node.ast_node.is_a?(GraphQL::Language::Nodes::Field)
96
- end
97
- end
98
- end