graphql-persisted_queries 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +25 -147
- data/docs/alternative_stores.md +80 -0
- data/docs/error_handling.md +26 -0
- data/docs/hash.md +17 -0
- data/docs/http_cache.md +42 -0
- data/lib/graphql/persisted_queries.rb +0 -1
- data/lib/graphql/persisted_queries/analyzers/http_method_analyzer.rb +22 -0
- data/lib/graphql/persisted_queries/analyzers/http_method_ast_analyzer.rb +19 -0
- data/lib/graphql/persisted_queries/analyzers/http_method_validator.rb +20 -0
- data/lib/graphql/persisted_queries/schema_patch.rb +20 -5
- data/lib/graphql/persisted_queries/version.rb +1 -1
- metadata +9 -3
- data/lib/graphql/persisted_queries/http_method_analyzer.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 248a30a043f26aabeb1f2fb61599b7dc7479a2a90cc1890c4fd7125fb61426e4
|
4
|
+
data.tar.gz: 6fbb89bf490bcf11c6681cb624d9153bf7c2be2eeb5bdd486407c288056ab494
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aea67f0744376a70950feea5a331cefd279679c0bc2ed5786f2d2c3fe1a13a31ea87348213b41a0a6d94e7daf32c02cc08364869bb2a088fa0ec2a451f4be793
|
7
|
+
data.tar.gz: 458ff6a451faac1fff4e1ce51ad42f9b51bc975270e1666d1730549a726f145acd20ce35ed9d630d62d3ea1c9899e8809c8f755af3d3d53e44af3fe6b30a69d2
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.5.1 (2020-03-18)
|
6
|
+
|
7
|
+
- [PR#33](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/33) Support AST analyzers ([@DmitryTsepelev][])
|
8
|
+
|
5
9
|
## 0.5.0 (2020-03-12)
|
6
10
|
|
7
11
|
- [PR#29](https://github.com/DmitryTsepelev/graphql-ruby-persisted_queries/pull/29) Instrumentation via graphql-ruby's tracer feature ([@bmorton][])
|
data/README.md
CHANGED
@@ -14,11 +14,9 @@
|
|
14
14
|
</a>
|
15
15
|
</p>
|
16
16
|
|
17
|
-
##
|
17
|
+
## Getting started
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
2. Install and configure [apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries):
|
19
|
+
First of all, install and configure [apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) on the front–end side:
|
22
20
|
|
23
21
|
```js
|
24
22
|
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
@@ -35,15 +33,25 @@ const client = new ApolloClient({
|
|
35
33
|
});
|
36
34
|
```
|
37
35
|
|
38
|
-
|
36
|
+
Add the gem to your Gemfile `gem 'graphql-persisted_queries'` and add the plugin to your schema class:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class GraphqlSchema < GraphQL::Schema
|
40
|
+
use GraphQL::PersistedQueries
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
**Heads up!** If you've already switched to interpreter mode and and AST analyzers—make sure AST plugin is added _before_ `GraphQL::PersistedQueries`:
|
39
45
|
|
40
46
|
```ruby
|
41
47
|
class GraphqlSchema < GraphQL::Schema
|
48
|
+
use GraphQL::Execution::Interpreter
|
49
|
+
use GraphQL::Analysis::AST
|
42
50
|
use GraphQL::PersistedQueries
|
43
51
|
end
|
44
52
|
```
|
45
53
|
|
46
|
-
|
54
|
+
Pass `:extensions` argument to all calls of `GraphqlSchema#execute`, usually it happens in `GraphqlController`, `GraphqlChannel` and tests.
|
47
55
|
|
48
56
|
```ruby
|
49
57
|
GraphqlSchema.execute(
|
@@ -55,16 +63,11 @@ GraphqlSchema.execute(
|
|
55
63
|
)
|
56
64
|
```
|
57
65
|
|
58
|
-
|
66
|
+
You're all set!
|
59
67
|
|
60
|
-
##
|
68
|
+
## Advanced usage
|
61
69
|
|
62
|
-
|
63
|
-
In this case you need to use `GraphqlSchema.multiplex(queries)` instead of `#execute`. The gem supports it too, no action required!
|
64
|
-
|
65
|
-
## Alternative stores
|
66
|
-
|
67
|
-
All the queries are stored in memory by default, but you can easily switch to _redis_ or _memcached_:
|
70
|
+
All the queries are stored in memory by default, but you can easily switch to another storage (e.g., _redis_:
|
68
71
|
|
69
72
|
```ruby
|
70
73
|
class GraphqlSchema < GraphQL::Schema
|
@@ -72,145 +75,20 @@ class GraphqlSchema < GraphQL::Schema
|
|
72
75
|
end
|
73
76
|
```
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name`
|
78
|
-
inside the `:redis_client` hash to build the URL from scratch or pass the configured `Redis` or `ConnectionPool` object:
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
class GraphqlSchema < GraphQL::Schema
|
82
|
-
use GraphQL::PersistedQueries,
|
83
|
-
store: :redis,
|
84
|
-
redis_client: { redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
|
85
|
-
# or
|
86
|
-
use GraphQL::PersistedQueries,
|
87
|
-
store: :redis,
|
88
|
-
redis_client: Redis.new(url: "redis://127.0.0.2:2214/7")
|
89
|
-
# or
|
90
|
-
use GraphQL::PersistedQueries,
|
91
|
-
store: :redis,
|
92
|
-
redis_client: ConnectionPool.new { Redis.new(url: "redis://127.0.0.2:2214/7") }
|
93
|
-
end
|
94
|
-
```
|
95
|
-
|
96
|
-
You can also pass options for expiration and namespace to override the defaults:
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
class GraphqlSchema < GraphQL::Schema
|
100
|
-
use GraphQL::PersistedQueries,
|
101
|
-
store: :redis,
|
102
|
-
redis_client: { redis_url: ENV["MY_REDIS_URL"] },
|
103
|
-
expiration: 172800, # optional, default is 24 hours
|
104
|
-
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
105
|
-
end
|
106
|
-
```
|
107
|
-
|
108
|
-
### Memcached
|
109
|
-
|
110
|
-
If you have `ENV["MEMCACHE_SERVERS"]` configured - you don't need to pass it explicitly. Also, you can pass `:memcached_host` and `:memcached_port`
|
111
|
-
inside the `:dalli_client` hash to build the server name from scratch or pass the configured `Dalli::Client` object:
|
112
|
-
|
113
|
-
```ruby
|
114
|
-
class GraphqlSchema < GraphQL::Schema
|
115
|
-
use GraphQL::PersistedQueries,
|
116
|
-
store: :memcached,
|
117
|
-
dalli_client: { memcached_host: "127.0.0.2", memcached_port: "11211" }
|
118
|
-
# or
|
119
|
-
use GraphQL::PersistedQueries,
|
120
|
-
store: :memcached,
|
121
|
-
dalli_client: Dalli::Client.new("127.0.0.2:11211")
|
122
|
-
# or
|
123
|
-
use GraphQL::PersistedQueries,
|
124
|
-
store: :memcached,
|
125
|
-
dalli_client: { memcached_url: "127.0.0.2:11211" }
|
126
|
-
end
|
127
|
-
```
|
128
|
-
|
129
|
-
You can also pass options for `expiration` and `namespace` to override the defaults.
|
130
|
-
Any additional argument inside `dalli_client` will be forwarded to `Dalli::Client.new`.
|
131
|
-
Following example configures Dalli `pool_size` and `compress` options:
|
78
|
+
We currently support `memory`, `redis`, `redis_with_local_cache` and `memcached` out of the box. The detailed documentation can be found [here](docs/alternative_stores.md).
|
132
79
|
|
133
|
-
|
134
|
-
class GraphqlSchema < GraphQL::Schema
|
135
|
-
use GraphQL::PersistedQueries,
|
136
|
-
store: :memcached,
|
137
|
-
dalli_client: {
|
138
|
-
memcached_url: "127.0.0.2:11211",
|
139
|
-
pool_size: 5,
|
140
|
-
compress: true
|
141
|
-
},
|
142
|
-
expiration: 172800, # optional, default is 24 hours
|
143
|
-
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
144
|
-
end
|
145
|
-
```
|
146
|
-
|
147
|
-
### Supported stores
|
148
|
-
|
149
|
-
We currently support a few different stores that can be configured out of the box:
|
150
|
-
|
151
|
-
- `:memory`: This is the default in-memory store and is great for getting started, but will require each instance to cache results independently which can result in lots of ["new query path"](https://blog.apollographql.com/improve-graphql-performance-with-automatic-persisted-queries-c31d27b8e6ea) requests.
|
152
|
-
- `:redis`: This store will allow you to share a Redis cache across all instances of your GraphQL application so that each instance doesn't have to ask the client for the query again if it hasn't seen it yet.
|
153
|
-
- `:redis_with_local_cache`: This store combines both the `:memory` and `:redis` approaches so that we can reduce the number of network requests we make while mitigating the independent cache issue. This adapter is configured identically to the `:redis` store.
|
154
|
-
- `:memcached`: This store will allow you to share a Memcached cache across all instances of your GraphQL application. The client is implemented with the Dalli gem.
|
155
|
-
|
156
|
-
## Alternative hash functions
|
157
|
-
|
158
|
-
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ by default so this gem uses it as a default too, but if you want to override it – you can use `:hash_generator` option:
|
159
|
-
|
160
|
-
```ruby
|
161
|
-
class GraphqlSchema < GraphQL::Schema
|
162
|
-
use GraphQL::PersistedQueries, hash_generator: :md5
|
163
|
-
end
|
164
|
-
```
|
165
|
-
|
166
|
-
If string or symbol is passed – the gem would try to find the class in the `Digest` namespace. Altenatively, you can pass a lambda, e.g.:
|
167
|
-
|
168
|
-
```ruby
|
169
|
-
class GraphqlSchema < GraphQL::Schema
|
170
|
-
use GraphQL::PersistedQueries, hash_generator: proc { |_value| "super_safe_hash!!!" }
|
171
|
-
end
|
172
|
-
```
|
173
|
-
|
174
|
-
## Error handling
|
175
|
-
|
176
|
-
You may optionally specify an object that will be called whenever an error occurs while attempting to resolve or save a query. This will give you the opportunity to both handle (e.g. graceful Redis failure) and/or log the error. By default, errors will be raised when a failure occurs within a `StoreAdapter`.
|
177
|
-
|
178
|
-
An error handler can be a proc or an implementation of `GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler`. Here's an example for treating Redis failures as cache misses:
|
179
|
-
|
180
|
-
```ruby
|
181
|
-
class GracefulRedisErrorHandler < GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler
|
182
|
-
def call(error)
|
183
|
-
case error
|
184
|
-
when Redis::BaseError
|
185
|
-
# Treat Redis errors as a cache miss, but you should log the error into
|
186
|
-
# your instrumentation framework here.
|
187
|
-
else
|
188
|
-
raise error
|
189
|
-
end
|
190
|
-
|
191
|
-
# Return nothing to ensure handled errors are treated as cache misses
|
192
|
-
return
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
class GraphqlSchema < GraphQL::Schema
|
197
|
-
use GraphQL::PersistedQueries, error_handler: GracefulRedisErrorHandler.new
|
198
|
-
end
|
199
|
-
```
|
80
|
+
When the error occurs, the gem tries to not interrupt the regular flow of the app (e.g., when something is wrong with the storage, it will just answer that persisted query is not found). You can add a [custom](docs/error_handling.md) error handler and try to fix the problem or just log it.
|
200
81
|
|
201
|
-
|
82
|
+
Since our queries are slim now, we can switch back to HTTP GET, you can find a [guide](docs/http_cache.md) here.
|
202
83
|
|
203
|
-
|
204
|
-
1. Change the way link is initialized on front-end side (`createPersistedQueryLink({ useGETForHashedQueries: true })`);
|
205
|
-
2. Register a new route `get "/graphql", to: "graphql#execute"`;
|
206
|
-
3. Put the request object to the GraphQL context in the controller `GraphqlSchema.execute(query, variables: variables, context: { request: request })`;
|
207
|
-
4. Turn the `verify_http_method` option on (`use GraphQL::PersistedQueries, verify_http_method: true`) to enforce using `POST` requests for performing mutations (otherwise the error `Mutations cannot be performed via HTTP GET` will be returned).
|
84
|
+
[batch-link](https://www.apollographql.com/docs/link/links/batch-http/) allows to group queries on the client side into a single HTTP request before sending to the server. In this case you need to use `GraphqlSchema.multiplex(queries)` instead of `#execute`. The gem supports it too, no action required!
|
208
85
|
|
209
|
-
|
86
|
+
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ for building hashes by default. Check out this [guide](docs/hash.md) if you want to override this behavior.
|
210
87
|
|
211
|
-
|
88
|
+
An experimental tracing feature can be enabled by setting `tracing: true` when configuring the plugin. Read more about this feature in the [Tracing guide](docs/tracing.md).
|
212
89
|
|
213
|
-
|
90
|
+
> 📖 Read more about the gem internals: [Persisted queries in GraphQL:
|
91
|
+
Slim down Apollo requests to your Ruby application](https://evilmartians.com/chronicles/persisted-queries-in-graphql-slim-down-apollo-requests-to-your-ruby-application)
|
214
92
|
|
215
93
|
## Contributing
|
216
94
|
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Alternative stores
|
2
|
+
|
3
|
+
We currently support a few different stores that can be configured out of the box:
|
4
|
+
|
5
|
+
- `:memory`: This is the default in-memory store and is great for getting started, but will require each instance to cache results independently which can result in lots of ["new query path"](https://blog.apollographql.com/improve-graphql-performance-with-automatic-persisted-queries-c31d27b8e6ea) requests.
|
6
|
+
- `:redis`: This store will allow you to share a Redis cache across all instances of your GraphQL application so that each instance doesn't have to ask the client for the query again if it hasn't seen it yet.
|
7
|
+
- `:redis_with_local_cache`: This store combines both the `:memory` and `:redis` approaches so that we can reduce the number of network requests we make while mitigating the independent cache issue. This adapter is configured identically to the `:redis` store.
|
8
|
+
- `:memcached`: This store will allow you to share a Memcached cache across all instances of your GraphQL application. The client is implemented with the Dalli gem.
|
9
|
+
|
10
|
+
## Redis
|
11
|
+
|
12
|
+
If you have `ENV["REDIS_URL"]` configured – you don't need to pass it explicitly. Also, you can pass `:redis_host`, `:redis_port` and `:redis_db_name`
|
13
|
+
inside the `:redis_client` hash to build the URL from scratch or pass the configured `Redis` or `ConnectionPool` object:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class GraphqlSchema < GraphQL::Schema
|
17
|
+
use GraphQL::PersistedQueries,
|
18
|
+
store: :redis,
|
19
|
+
redis_client: { redis_host: "127.0.0.2", redis_port: "2214", redis_db_name: "7" }
|
20
|
+
# or
|
21
|
+
use GraphQL::PersistedQueries,
|
22
|
+
store: :redis,
|
23
|
+
redis_client: Redis.new(url: "redis://127.0.0.2:2214/7")
|
24
|
+
# or
|
25
|
+
use GraphQL::PersistedQueries,
|
26
|
+
store: :redis,
|
27
|
+
redis_client: ConnectionPool.new { Redis.new(url: "redis://127.0.0.2:2214/7") }
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
You can also pass options for expiration and namespace to override the defaults:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class GraphqlSchema < GraphQL::Schema
|
35
|
+
use GraphQL::PersistedQueries,
|
36
|
+
store: :redis,
|
37
|
+
redis_client: { redis_url: ENV["MY_REDIS_URL"] },
|
38
|
+
expiration: 172800, # optional, default is 24 hours
|
39
|
+
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
## Memcached
|
44
|
+
|
45
|
+
If you have `ENV["MEMCACHE_SERVERS"]` configured - you don't need to pass it explicitly. Also, you can pass `:memcached_host` and `:memcached_port`
|
46
|
+
inside the `:dalli_client` hash to build the server name from scratch or pass the configured `Dalli::Client` object:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class GraphqlSchema < GraphQL::Schema
|
50
|
+
use GraphQL::PersistedQueries,
|
51
|
+
store: :memcached,
|
52
|
+
dalli_client: { memcached_host: "127.0.0.2", memcached_port: "11211" }
|
53
|
+
# or
|
54
|
+
use GraphQL::PersistedQueries,
|
55
|
+
store: :memcached,
|
56
|
+
dalli_client: Dalli::Client.new("127.0.0.2:11211")
|
57
|
+
# or
|
58
|
+
use GraphQL::PersistedQueries,
|
59
|
+
store: :memcached,
|
60
|
+
dalli_client: { memcached_url: "127.0.0.2:11211" }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
You can also pass options for `expiration` and `namespace` to override the defaults.
|
65
|
+
Any additional argument inside `dalli_client` will be forwarded to `Dalli::Client.new`.
|
66
|
+
Following example configures Dalli `pool_size` and `compress` options:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class GraphqlSchema < GraphQL::Schema
|
70
|
+
use GraphQL::PersistedQueries,
|
71
|
+
store: :memcached,
|
72
|
+
dalli_client: {
|
73
|
+
memcached_url: "127.0.0.2:11211",
|
74
|
+
pool_size: 5,
|
75
|
+
compress: true
|
76
|
+
},
|
77
|
+
expiration: 172800, # optional, default is 24 hours
|
78
|
+
namespace: "my-custom-namespace" # optional, default is "graphql-persisted-query"
|
79
|
+
end
|
80
|
+
```
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Error handling
|
2
|
+
|
3
|
+
You may optionally specify an object that will be called whenever an error occurs while attempting to resolve or save a query. This will give you the opportunity to both handle (e.g. graceful Redis failure) and/or log the error. By default, errors will be raised when a failure occurs within a `StoreAdapter`.
|
4
|
+
|
5
|
+
An error handler can be a proc or an implementation of `GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler`. Here's an example for treating Redis failures as cache misses:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class GracefulRedisErrorHandler < GraphQL::PersistedQueries::ErrorHandlers::BaseErrorHandler
|
9
|
+
def call(error)
|
10
|
+
case error
|
11
|
+
when Redis::BaseError
|
12
|
+
# Treat Redis errors as a cache miss, but you should log the error into
|
13
|
+
# your instrumentation framework here.
|
14
|
+
else
|
15
|
+
raise error
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return nothing to ensure handled errors are treated as cache misses
|
19
|
+
return
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class GraphqlSchema < GraphQL::Schema
|
24
|
+
use GraphQL::PersistedQueries, error_handler: GracefulRedisErrorHandler.new
|
25
|
+
end
|
26
|
+
```
|
data/docs/hash.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Alternative hash functions
|
2
|
+
|
3
|
+
[apollo-link-persisted-queries](https://github.com/apollographql/apollo-link-persisted-queries) uses _SHA256_ by default so this gem uses it as a default too, but if you want to override it – you can use `:hash_generator` option:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class GraphqlSchema < GraphQL::Schema
|
7
|
+
use GraphQL::PersistedQueries, hash_generator: :md5
|
8
|
+
end
|
9
|
+
```
|
10
|
+
|
11
|
+
If string or symbol is passed – the gem would try to find the class in the `Digest` namespace. Altenatively, you can pass a lambda, e.g.:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class GraphqlSchema < GraphQL::Schema
|
15
|
+
use GraphQL::PersistedQueries, hash_generator: proc { |_value| "super_safe_hash!!!" }
|
16
|
+
end
|
17
|
+
```
|
data/docs/http_cache.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# GET requests and HTTP cache
|
2
|
+
|
3
|
+
Using `GET` requests for persisted queries allows you to enable HTTP caching (e.g., turn on CDN).
|
4
|
+
|
5
|
+
Firstly, turn on the `useGETForHashedQueries` parameter on the front-end side:
|
6
|
+
|
7
|
+
```js
|
8
|
+
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
|
9
|
+
import { createHttpLink } from "apollo-link-http";
|
10
|
+
import { InMemoryCache } from "apollo-cache-inmemory";
|
11
|
+
import ApolloClient from "apollo-client";
|
12
|
+
|
13
|
+
|
14
|
+
// use this with Apollo Client
|
15
|
+
const link = createPersistedQueryLink({ useGETForHashedQueries: true }).concat(createHttpLink({ uri: "/graphql" }));
|
16
|
+
const client = new ApolloClient({
|
17
|
+
cache: new InMemoryCache(),
|
18
|
+
link: link,
|
19
|
+
});
|
20
|
+
```
|
21
|
+
|
22
|
+
Register a new route in `routes.rb`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
get "/graphql", to: "graphql#execute"
|
26
|
+
```
|
27
|
+
|
28
|
+
Put the request object to the GraphQL context everywhere you execute GraphQL queries:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
GraphqlSchema.execute(query, variables: variables, context: { request: request })
|
32
|
+
```
|
33
|
+
|
34
|
+
Turn the `verify_http_method` option when configuring the plugin to enforce using `POST` requests for performing mutations (otherwise the error `Mutations cannot be performed via HTTP GET` will be returned):
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
class GraphqlSchema < GraphQL::Schema
|
38
|
+
use GraphQL::PersistedQueries, verify_http_method: true
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
HTTP method verification is important, because when mutations are allowed via `GET` requests, it's easy to perform an attack by sending the link containing mutation to a signed in user.
|
@@ -5,7 +5,6 @@ require "graphql/persisted_queries/schema_patch"
|
|
5
5
|
require "graphql/persisted_queries/store_adapters"
|
6
6
|
require "graphql/persisted_queries/version"
|
7
7
|
require "graphql/persisted_queries/builder_helpers"
|
8
|
-
require "graphql/persisted_queries/http_method_analyzer"
|
9
8
|
|
10
9
|
module GraphQL
|
11
10
|
# Plugin definition
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodAnalyzer
|
8
|
+
def initial_value(query)
|
9
|
+
{ query: query }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(memo, _visit_type, _irep_node)
|
13
|
+
memo
|
14
|
+
end
|
15
|
+
|
16
|
+
def final_value(memo)
|
17
|
+
HttpMethodValidator.new(memo[:query]).perform
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodAstAnalyzer < GraphQL::Analysis::AST::Analyzer
|
8
|
+
def initialize(query)
|
9
|
+
super
|
10
|
+
@query = query
|
11
|
+
end
|
12
|
+
|
13
|
+
def result
|
14
|
+
HttpMethodValidator.new(@query).perform
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module PersistedQueries
|
5
|
+
module Analyzers
|
6
|
+
# Verifies that mutations are not executed using GET requests
|
7
|
+
class HttpMethodValidator
|
8
|
+
def initialize(query)
|
9
|
+
@query = query
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
return if !@query.context[:request]&.get? || !@query.mutation?
|
14
|
+
|
15
|
+
GraphQL::AnalysisError.new("Mutations cannot be performed via HTTP GET")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "graphql/persisted_queries/hash_generator_builder"
|
4
4
|
require "graphql/persisted_queries/resolver"
|
5
5
|
require "graphql/persisted_queries/multiplex_resolver"
|
6
|
+
require "graphql/persisted_queries/analyzers/http_method_validator"
|
6
7
|
|
7
8
|
module GraphQL
|
8
9
|
module PersistedQueries
|
@@ -35,12 +36,10 @@ module GraphQL
|
|
35
36
|
def verify_http_method=(verify)
|
36
37
|
return unless verify
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
if Gem::Dependency.new("graphql", ">= 1.10.0").match?("graphql", GraphQL::VERSION)
|
41
|
-
query_analyzer(analyzer)
|
39
|
+
if graphql10?
|
40
|
+
query_analyzer(prepare_analyzer)
|
42
41
|
else
|
43
|
-
query_analyzers <<
|
42
|
+
query_analyzers << prepare_analyzer
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
@@ -60,6 +59,22 @@ module GraphQL
|
|
60
59
|
persisted_query_store.tracers = tracers if persisted_queries_tracing_enabled?
|
61
60
|
end
|
62
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def graphql10?
|
66
|
+
Gem::Dependency.new("graphql", ">= 1.10.0").match?("graphql", GraphQL::VERSION)
|
67
|
+
end
|
68
|
+
|
69
|
+
def prepare_analyzer
|
70
|
+
if graphql10? && using_ast_analysis?
|
71
|
+
require "graphql/persisted_queries/analyzers/http_method_ast_analyzer"
|
72
|
+
Analyzers::HttpMethodAstAnalyzer
|
73
|
+
else
|
74
|
+
require "graphql/persisted_queries/analyzers/http_method_analyzer"
|
75
|
+
Analyzers::HttpMethodAnalyzer.new
|
76
|
+
end
|
77
|
+
end
|
63
78
|
end
|
64
79
|
end
|
65
80
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-persisted_queries
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-03-
|
11
|
+
date: 2020-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -125,6 +125,10 @@ files:
|
|
125
125
|
- Rakefile
|
126
126
|
- bin/console
|
127
127
|
- bin/setup
|
128
|
+
- docs/alternative_stores.md
|
129
|
+
- docs/error_handling.md
|
130
|
+
- docs/hash.md
|
131
|
+
- docs/http_cache.md
|
128
132
|
- docs/tracing.md
|
129
133
|
- gemfiles/graphql_1_10.gemfile
|
130
134
|
- gemfiles/graphql_1_8.gemfile
|
@@ -132,12 +136,14 @@ files:
|
|
132
136
|
- gemfiles/graphql_master.gemfile
|
133
137
|
- graphql-persisted_queries.gemspec
|
134
138
|
- lib/graphql/persisted_queries.rb
|
139
|
+
- lib/graphql/persisted_queries/analyzers/http_method_analyzer.rb
|
140
|
+
- lib/graphql/persisted_queries/analyzers/http_method_ast_analyzer.rb
|
141
|
+
- lib/graphql/persisted_queries/analyzers/http_method_validator.rb
|
135
142
|
- lib/graphql/persisted_queries/builder_helpers.rb
|
136
143
|
- lib/graphql/persisted_queries/error_handlers.rb
|
137
144
|
- lib/graphql/persisted_queries/error_handlers/base_error_handler.rb
|
138
145
|
- lib/graphql/persisted_queries/error_handlers/default_error_handler.rb
|
139
146
|
- lib/graphql/persisted_queries/hash_generator_builder.rb
|
140
|
-
- lib/graphql/persisted_queries/http_method_analyzer.rb
|
141
147
|
- lib/graphql/persisted_queries/multiplex_resolver.rb
|
142
148
|
- lib/graphql/persisted_queries/resolver.rb
|
143
149
|
- lib/graphql/persisted_queries/schema_patch.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module PersistedQueries
|
5
|
-
# Verifies that mutations are not executed using GET requests
|
6
|
-
class HttpMethodAnalyzer
|
7
|
-
def analyze?(query)
|
8
|
-
query.context[:request]
|
9
|
-
end
|
10
|
-
|
11
|
-
def initial_value(query)
|
12
|
-
{
|
13
|
-
get_request: query.context[:request]&.get?,
|
14
|
-
mutation: query.mutation?
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
def call(memo, _visit_type, _irep_node)
|
19
|
-
memo
|
20
|
-
end
|
21
|
-
|
22
|
-
def final_value(memo)
|
23
|
-
return if !memo[:get_request] || !memo[:mutation]
|
24
|
-
|
25
|
-
GraphQL::AnalysisError.new("Mutations cannot be performed via HTTP GET")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|