rack-cloudflare-jwt 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -1
- data/lib/rack/cloudflare_jwt/auth.rb +199 -205
- data/lib/rack/cloudflare_jwt/version.rb +1 -1
- metadata +15 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55cdfb2ba442bac6516a587a070c304d1a4ecab379cbb24a231fd80ddebf6e51
|
|
4
|
+
data.tar.gz: 96e59628339f522d60626d4e82635b2ea864c4d732835e6c20b58fcc6d7c12eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 636a1384bb904479c1fb7e126c919488a307af159651dc5b509ab39012fa01bb4d54af1fd50a3a1e4541e1a40c4be2810e3fda7c47e802774f4c37e101d4e113
|
|
7
|
+
data.tar.gz: d42e6f7ec8d19d16281b9df23daedc13116e8cec2fcd61f94324529197d3b13142d8e3662f3d393fe25924f848c65a5793d1a9eb67dd3391f811558ca7aa11a8
|
data/README.md
CHANGED
|
@@ -38,11 +38,22 @@ $ gem install rack-cloudflare-jwt
|
|
|
38
38
|
|
|
39
39
|
* `Hash` value : `String` : A Application Audience (AUD) Tag.
|
|
40
40
|
|
|
41
|
+
Also, you should provide a Team Domain.
|
|
41
42
|
|
|
42
43
|
### Rails
|
|
43
44
|
|
|
44
45
|
```ruby
|
|
45
|
-
Rails.application.config.middleware.use Rack::CloudflareJwt::Auth, '
|
|
46
|
+
Rails.application.config.middleware.use Rack::CloudflareJwt::Auth, 'my-team-domain.cloudflareaccess.com',
|
|
47
|
+
'/my-path-1' => 'aaa.bbb.ccc'
|
|
48
|
+
'/my-path-2' => 'xxx.yyy.zzz',
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
# Releasing
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
gem signin
|
|
55
|
+
|
|
56
|
+
|
|
46
57
|
```
|
|
47
58
|
|
|
48
59
|
## Contributing
|
|
@@ -5,235 +5,229 @@ require 'multi_json'
|
|
|
5
5
|
require 'net/http'
|
|
6
6
|
require 'rack/jwt'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
8
|
+
# Authentication middleware
|
|
9
|
+
#
|
|
10
|
+
# @see https://developers.cloudflare.com/access/setting-up-access/validate-jwt-tokens/
|
|
11
|
+
class Rack::CloudflareJwt::Auth
|
|
12
|
+
# Custom decode token error.
|
|
13
|
+
class DecodeTokenError < StandardError; end
|
|
14
|
+
|
|
15
|
+
# Certs path
|
|
16
|
+
CERTS_PATH = '/cdn-cgi/access/certs'
|
|
17
|
+
# Default algorithm
|
|
18
|
+
DEFAULT_ALGORITHM = 'RS256'
|
|
19
|
+
# CloudFlare JWT header.
|
|
20
|
+
HEADER_NAME = 'HTTP_CF_ACCESS_JWT_ASSERTION'
|
|
21
|
+
# Key for get current path.
|
|
22
|
+
PATH_INFO = 'PATH_INFO'
|
|
23
|
+
|
|
24
|
+
# Token regex.
|
|
25
|
+
#
|
|
26
|
+
# @see https://github.com/jwt/ruby-jwt/tree/v2.2.1#algorithms-and-usage
|
|
27
|
+
TOKEN_REGEX = /
|
|
28
|
+
^(
|
|
29
|
+
[a-zA-Z0-9\-_]+\. # 1 or more chars followed by a single period
|
|
30
|
+
[a-zA-Z0-9\-_]+\. # 1 or more chars followed by a single period
|
|
31
|
+
[a-zA-Z0-9\-_]+ # 1 or more chars, no trailing chars
|
|
32
|
+
)$
|
|
33
|
+
/x.freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :policies, :team_domain
|
|
36
|
+
|
|
37
|
+
# Initializes middleware
|
|
38
|
+
#
|
|
39
|
+
# @example Initialize middleware in Rails
|
|
40
|
+
# config.middleware.use(
|
|
41
|
+
# Rack::CloudflareJwt::Auth,
|
|
42
|
+
# ENV['RACK_CLOUDFLARE_JWT_TEAM_DOMAIN'],
|
|
43
|
+
# '/admin' => <cloudflare-aud-1>,
|
|
44
|
+
# '/manager' => <cloudflare-aud-2>,
|
|
45
|
+
# )
|
|
46
|
+
#
|
|
47
|
+
# @param team_domain [String] the Team Domain (e.g. 'test.cloudflareaccess.com').
|
|
48
|
+
# @param policies [Hash<String, String>] the policies with paths and AUDs.
|
|
49
|
+
def initialize(app, team_domain, policies = {})
|
|
50
|
+
@app = app
|
|
51
|
+
@team_domain = team_domain
|
|
52
|
+
@policies = policies
|
|
53
|
+
|
|
54
|
+
check_policy_auds!
|
|
55
|
+
check_paths_type!
|
|
56
|
+
end
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
end
|
|
58
|
+
# Public: Call a middleware.
|
|
59
|
+
def call(env)
|
|
60
|
+
if !path_matches?(env)
|
|
61
|
+
@app.call(env)
|
|
62
|
+
elsif missing_auth_header?(env)
|
|
63
|
+
return_error('Missing Authorization header')
|
|
64
|
+
elsif invalid_auth_header?(env)
|
|
65
|
+
return_error('Invalid Authorization header format')
|
|
66
|
+
else
|
|
67
|
+
verify_token(env)
|
|
69
68
|
end
|
|
69
|
+
end
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
private
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
# Private: Check policy auds.
|
|
74
|
+
def check_policy_auds!
|
|
75
|
+
raise ArgumentError, 'policies cannot be nil/empty' if policies.values.empty?
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
policies.each_value do |policy_aud|
|
|
78
|
+
next unless !policy_aud.is_a?(String) || policy_aud.strip.empty?
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
end
|
|
80
|
+
raise ArgumentError, 'policy AUD argument cannot be nil/empty'
|
|
82
81
|
end
|
|
82
|
+
end
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
end
|
|
84
|
+
# Private: Check paths type.
|
|
85
|
+
def check_paths_type!
|
|
86
|
+
policies.each_key do |path|
|
|
87
|
+
raise ArgumentError, 'each key element must be a String' unless path.is_a?(String)
|
|
88
|
+
raise ArgumentError, 'each key element must not be empty' if path.empty?
|
|
89
|
+
raise ArgumentError, 'each key element must start with a /' unless path.start_with?('/')
|
|
91
90
|
end
|
|
91
|
+
end
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
if decoded_token
|
|
106
|
-
logger.debug 'CloudFlare JWT token is valid'
|
|
107
|
-
|
|
108
|
-
env['jwt.payload'] = decoded_token.first
|
|
109
|
-
env['jwt.header'] = decoded_token.last
|
|
110
|
-
@app.call(env)
|
|
111
|
-
else
|
|
112
|
-
return_error('Invalid token')
|
|
113
|
-
end
|
|
93
|
+
# Private: Verify a token.
|
|
94
|
+
def verify_token(env)
|
|
95
|
+
# extract the token from header.
|
|
96
|
+
token = env[HEADER_NAME]
|
|
97
|
+
policy_aud = policies.find { |path, _aud| env[PATH_INFO].start_with?(path) }&.last
|
|
98
|
+
decoded_token = public_keys.find do |key|
|
|
99
|
+
break decode_token(token, key.public_key, policy_aud)
|
|
100
|
+
rescue DecodeTokenError => e
|
|
101
|
+
logger.info e.message
|
|
102
|
+
nil
|
|
114
103
|
end
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# @param token [String] the token.
|
|
119
|
-
# @param secret [String] the public key.
|
|
120
|
-
# @param policy_aud [String] the CloudFlare AUD.
|
|
121
|
-
#
|
|
122
|
-
# @example
|
|
123
|
-
#
|
|
124
|
-
# [
|
|
125
|
-
# {"data"=>"test"}, # payload
|
|
126
|
-
# {"alg"=>"RS256"} # header
|
|
127
|
-
# ]
|
|
128
|
-
#
|
|
129
|
-
# @return [Array<Hash>] the token or `nil` at error.
|
|
130
|
-
# @raise [DecodeTokenError] if the token is invalid.
|
|
131
|
-
#
|
|
132
|
-
# @see https://github.com/jwt/ruby-jwt/tree/v2.2.1#algorithms-and-usage
|
|
133
|
-
def decode_token(token, secret, policy_aud)
|
|
134
|
-
Rack::JWT::Token.decode(token, secret, true, aud: policy_aud, verify_aud: true, algorithm: DEFAULT_ALGORITHM)
|
|
135
|
-
rescue ::JWT::VerificationError
|
|
136
|
-
raise DecodeTokenError, 'Invalid JWT token : Signature Verification Error'
|
|
137
|
-
rescue ::JWT::ExpiredSignature
|
|
138
|
-
raise DecodeTokenError, 'Invalid JWT token : Expired Signature (exp)'
|
|
139
|
-
rescue ::JWT::IncorrectAlgorithm
|
|
140
|
-
raise DecodeTokenError, 'Invalid JWT token : Incorrect Key Algorithm'
|
|
141
|
-
rescue ::JWT::ImmatureSignature
|
|
142
|
-
raise DecodeTokenError, 'Invalid JWT token : Immature Signature (nbf)'
|
|
143
|
-
rescue ::JWT::InvalidIssuerError
|
|
144
|
-
raise DecodeTokenError, 'Invalid JWT token : Invalid Issuer (iss)'
|
|
145
|
-
rescue ::JWT::InvalidIatError
|
|
146
|
-
raise DecodeTokenError, 'Invalid JWT token : Invalid Issued At (iat)'
|
|
147
|
-
rescue ::JWT::InvalidAudError
|
|
148
|
-
raise DecodeTokenError, 'Invalid JWT token : Invalid Audience (aud)'
|
|
149
|
-
rescue ::JWT::InvalidSubError
|
|
150
|
-
raise DecodeTokenError, 'Invalid JWT token : Invalid Subject (sub)'
|
|
151
|
-
rescue ::JWT::InvalidJtiError
|
|
152
|
-
raise DecodeTokenError, 'Invalid JWT token : Invalid JWT ID (jti)'
|
|
153
|
-
rescue ::JWT::DecodeError
|
|
154
|
-
raise DecodeTokenError, 'Invalid JWT token : Decode Error'
|
|
155
|
-
end
|
|
105
|
+
if decoded_token
|
|
106
|
+
logger.debug 'CloudFlare JWT token is valid'
|
|
156
107
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
108
|
+
env['jwt.payload'] = decoded_token.first
|
|
109
|
+
env['jwt.header'] = decoded_token.last
|
|
110
|
+
@app.call(env)
|
|
111
|
+
else
|
|
112
|
+
return_error('Invalid token')
|
|
162
113
|
end
|
|
114
|
+
end
|
|
163
115
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
116
|
+
# Private: Decode a token.
|
|
117
|
+
#
|
|
118
|
+
# @param token [String] the token.
|
|
119
|
+
# @param secret [String] the public key.
|
|
120
|
+
# @param policy_aud [String] the CloudFlare AUD.
|
|
121
|
+
#
|
|
122
|
+
# @example
|
|
123
|
+
#
|
|
124
|
+
# [
|
|
125
|
+
# {"data"=>"test"}, # payload
|
|
126
|
+
# {"alg"=>"RS256"} # header
|
|
127
|
+
# ]
|
|
128
|
+
#
|
|
129
|
+
# @return [Array<Hash>] the token or `nil` at error.
|
|
130
|
+
# @raise [DecodeTokenError] if the token is invalid.
|
|
131
|
+
#
|
|
132
|
+
# @see https://github.com/jwt/ruby-jwt/tree/v2.2.1#algorithms-and-usage
|
|
133
|
+
def decode_token(token, secret, policy_aud)
|
|
134
|
+
Rack::JWT::Token.decode(token, secret, true, aud: policy_aud, verify_aud: true, algorithm: DEFAULT_ALGORITHM)
|
|
135
|
+
rescue ::JWT::VerificationError
|
|
136
|
+
raise DecodeTokenError, 'Invalid JWT token : Signature Verification Error'
|
|
137
|
+
rescue ::JWT::ExpiredSignature
|
|
138
|
+
raise DecodeTokenError, 'Invalid JWT token : Expired Signature (exp)'
|
|
139
|
+
rescue ::JWT::IncorrectAlgorithm
|
|
140
|
+
raise DecodeTokenError, 'Invalid JWT token : Incorrect Key Algorithm'
|
|
141
|
+
rescue ::JWT::ImmatureSignature
|
|
142
|
+
raise DecodeTokenError, 'Invalid JWT token : Immature Signature (nbf)'
|
|
143
|
+
rescue ::JWT::InvalidIssuerError
|
|
144
|
+
raise DecodeTokenError, 'Invalid JWT token : Invalid Issuer (iss)'
|
|
145
|
+
rescue ::JWT::InvalidIatError
|
|
146
|
+
raise DecodeTokenError, 'Invalid JWT token : Invalid Issued At (iat)'
|
|
147
|
+
rescue ::JWT::InvalidAudError
|
|
148
|
+
raise DecodeTokenError, 'Invalid JWT token : Invalid Audience (aud)'
|
|
149
|
+
rescue ::JWT::InvalidSubError
|
|
150
|
+
raise DecodeTokenError, 'Invalid JWT token : Invalid Subject (sub)'
|
|
151
|
+
rescue ::JWT::InvalidJtiError
|
|
152
|
+
raise DecodeTokenError, 'Invalid JWT token : Invalid JWT ID (jti)'
|
|
153
|
+
rescue ::JWT::DecodeError
|
|
154
|
+
raise DecodeTokenError, 'Invalid JWT token : Decode Error'
|
|
155
|
+
end
|
|
170
156
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
157
|
+
# Private: Check if current path is in the policies.
|
|
158
|
+
#
|
|
159
|
+
# @return [Boolean] true if it is, false otherwise.
|
|
160
|
+
def path_matches?(env)
|
|
161
|
+
policies.empty? || policies.keys.any? { |ex| env[PATH_INFO].start_with?(ex) }
|
|
162
|
+
end
|
|
177
163
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
164
|
+
# Private: Check if auth header is invalid.
|
|
165
|
+
#
|
|
166
|
+
# @return [Boolean] true if it is, false otherwise.
|
|
167
|
+
def invalid_auth_header?(env)
|
|
168
|
+
env[HEADER_NAME] !~ TOKEN_REGEX
|
|
169
|
+
end
|
|
182
170
|
|
|
183
|
-
|
|
184
|
-
|
|
171
|
+
# Private: Check if no auth header.
|
|
172
|
+
#
|
|
173
|
+
# @return [Boolean] true if it is, false otherwise.
|
|
174
|
+
def missing_auth_header?(env)
|
|
175
|
+
env[HEADER_NAME].nil? || env[HEADER_NAME].strip.empty?
|
|
176
|
+
end
|
|
185
177
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
end
|
|
194
|
-
end
|
|
178
|
+
# Private: Return an error.
|
|
179
|
+
def return_error(message)
|
|
180
|
+
body = { error: message }.to_json
|
|
181
|
+
headers = { 'Content-Type' => 'application/json' }
|
|
182
|
+
|
|
183
|
+
[403, headers, [body]]
|
|
184
|
+
end
|
|
195
185
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
json = Net::HTTP.get(host, CERTS_PATH)
|
|
203
|
-
json.empty? ? [] : MultiJson.load(json, symbolize_keys: true).fetch(:keys)
|
|
204
|
-
rescue StandardError
|
|
205
|
-
[]
|
|
186
|
+
# Private: Get public keys.
|
|
187
|
+
#
|
|
188
|
+
# @return [Array<OpenSSL::PKey::RSA>] the public keys.
|
|
189
|
+
def public_keys
|
|
190
|
+
fetch_public_keys_cached.map do |jwk_data|
|
|
191
|
+
::JWT::JWK.import(jwk_data).keypair
|
|
206
192
|
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Private: Fetch public keys.
|
|
196
|
+
#
|
|
197
|
+
# @return [Array<Hash>] the public keys.
|
|
198
|
+
def fetch_public_keys
|
|
199
|
+
json = Net::HTTP.get(team_domain, CERTS_PATH)
|
|
200
|
+
json.empty? ? [] : MultiJson.load(json, symbolize_keys: true).fetch(:keys)
|
|
201
|
+
rescue StandardError
|
|
202
|
+
[]
|
|
203
|
+
end
|
|
207
204
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
else
|
|
224
|
-
fetch_public_keys(host)
|
|
225
|
-
end
|
|
205
|
+
# Private: Get cached public keys.
|
|
206
|
+
#
|
|
207
|
+
# Store a keys in the cache only 10 minutes.
|
|
208
|
+
#
|
|
209
|
+
# @return [Array<Hash>] the public keys.
|
|
210
|
+
def fetch_public_keys_cached
|
|
211
|
+
key = [self.class.name, '#secrets'].join('_')
|
|
212
|
+
|
|
213
|
+
if defined? Rails
|
|
214
|
+
Rails.cache.fetch(key, expires_in: 600) { fetch_public_keys }
|
|
215
|
+
elsif defined? Padrino
|
|
216
|
+
keys = Padrino.cache[key]
|
|
217
|
+
keys || Padrino.cache.store(key, fetch_public_keys, expires: 600)
|
|
218
|
+
else
|
|
219
|
+
fetch_public_keys
|
|
226
220
|
end
|
|
221
|
+
end
|
|
227
222
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
end
|
|
223
|
+
# Private: Get a logger.
|
|
224
|
+
#
|
|
225
|
+
# @return [ActiveSupport::Logger] the logger.
|
|
226
|
+
def logger
|
|
227
|
+
if defined? Rails
|
|
228
|
+
Rails.logger
|
|
229
|
+
elsif defined? Padrino
|
|
230
|
+
Padrino.logger
|
|
237
231
|
end
|
|
238
232
|
end
|
|
239
233
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rack-cloudflare-jwt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aleksei Vokhmin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-06-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -126,16 +126,22 @@ dependencies:
|
|
|
126
126
|
name: jwt
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
|
-
- - "
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '2.2'
|
|
132
|
+
- - "<"
|
|
130
133
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: 2.
|
|
134
|
+
version: '2.7'
|
|
132
135
|
type: :runtime
|
|
133
136
|
prerelease: false
|
|
134
137
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
138
|
requirements:
|
|
136
|
-
- - "
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: '2.2'
|
|
142
|
+
- - "<"
|
|
137
143
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: 2.
|
|
144
|
+
version: '2.7'
|
|
139
145
|
- !ruby/object:Gem::Dependency
|
|
140
146
|
name: multi_json
|
|
141
147
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -195,7 +201,8 @@ files:
|
|
|
195
201
|
homepage: https://github.com/Shuttlerock/rack-cloudflare-jwt
|
|
196
202
|
licenses:
|
|
197
203
|
- MIT
|
|
198
|
-
metadata:
|
|
204
|
+
metadata:
|
|
205
|
+
rubygems_mfa_required: 'true'
|
|
199
206
|
post_install_message:
|
|
200
207
|
rdoc_options: []
|
|
201
208
|
require_paths:
|
|
@@ -211,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
211
218
|
- !ruby/object:Gem::Version
|
|
212
219
|
version: '0'
|
|
213
220
|
requirements: []
|
|
214
|
-
rubygems_version: 3.0.3
|
|
221
|
+
rubygems_version: 3.0.3.1
|
|
215
222
|
signing_key:
|
|
216
223
|
specification_version: 4
|
|
217
224
|
summary: Rack middleware that provides authentication based on CloudFlare JSON Web
|