rack-json_web_token_auth 0.1.1 → 0.2.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
  SHA1:
3
- metadata.gz: 34e37b26161d949ec37dfa1699ac3c73bd16a6a6
4
- data.tar.gz: 9fc1af9868b25087073f995f0dd497bbc936fd5e
3
+ metadata.gz: 7ae3fd49effa85dae53d4098b01beabf1245a418
4
+ data.tar.gz: cf8334ffaef3a34d486e32de2716ceadec47c010
5
5
  SHA512:
6
- metadata.gz: 41429c9b24ab68bcffc806e9c29c4b615a435237540d47e0f28aee7387259ddec92aa0ba68fd850b2f4902d682fb87a4737ec038668190ff9835f096472315f8
7
- data.tar.gz: 99d552f23c11e324a3985fbf391aa115d42883257124f55517a887d20e1d71605a7eff24fbad192c83be8960b64740a4aefc6c4aab8d676230c52263acfe0e6f
6
+ metadata.gz: f6a071a87d21bc42583a465759c8568242a8af10f45eb1bd77c0a75e4e9f11c5570349a2a5bc4376d3dd8c807a3d38bcf4506df67a66ea69cdd15493cb797029
7
+ data.tar.gz: a8c0137beaa370ec6fe38969ef1a5919a13a87be8adec836ca6e3a613342c797e2140e6cd0f059339f9b72e89cc46219986169ace4866a61d04364294760feaf
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Rack::JsonWebTokenAuth
2
2
 
3
+ [![Build Status](https://travis-ci.org/grempe/rack-json_web_token_auth.svg?branch=master)](https://travis-ci.org/grempe/rack-json_web_token_auth)
4
+ [![Code Climate](https://codeclimate.com/github/grempe/rack-json_web_token_auth/badges/gpa.svg)](https://codeclimate.com/github/grempe/rack-json_web_token_auth)
5
+
3
6
  ## WARNING
4
7
 
5
8
  This is pre-release software. It is pretty well tested but has not yet
@@ -104,18 +107,25 @@ incoming tokens. The available claims are processed by the [garyf/jwt_claims](ht
104
107
  claims can be found in the README for that project. At a minimum a `:key` must
105
108
  be provided except if the `none` algorithm is being used (probably not recommended).
106
109
 
110
+ For `secured` resources you can optionally also pass in a `:methods` option,
111
+ which specifies an array of HTTP methods that are allowed for the specified `resource`.
112
+ One or more of `[:any, :get, :head, :post, :put, :patch, :delete, :options]`
113
+ can be provided. If the `:any` option is desired it must be the only option provided.
114
+
107
115
  Configuration directives are processed in the order that you provide and requests
108
- match against the first path match. For this reason you should probably put your
116
+ match against the **first path match**. For this reason you should probably put your
109
117
  `unsecured` resources first and order all resources from most specific to least
110
- specific.
118
+ specific. If multiple resources with the same path are configured, but with different
119
+ options, only the first resource matched will be used to test the request, all
120
+ others will be ignored.
111
121
 
112
122
  The DSL was heavily inspired by the [rack-cors](https://github.com/cyu/rack-cors)
113
- gem and the resource path matching code is a direct port from it.
123
+ gem.
114
124
 
115
125
  ```ruby
116
126
  require 'rack/json_web_token_auth'
117
127
 
118
- # Sinatra `use` syntax
128
+ # Sinatra style Rack middleware `use` syntax
119
129
  use Rack::JsonWebTokenAuth do
120
130
 
121
131
  # You can define JWT options for all `secured` resources globally
@@ -170,6 +180,35 @@ use Rack::JsonWebTokenAuth do
170
180
  resource '/another/path', jwt: {key: 'a long random key', alg: 'HS512'}
171
181
  end
172
182
 
183
+ # You can get very granular by specifying that a resource can only be accessed
184
+ # when requested with certain HTTP methods. The default for any resource is
185
+ # to allow HTTP `GET` requests only. You need to pass in a :methods array if
186
+ # you want to expose additional methods.
187
+ #
188
+ # The available choices are:
189
+ # [:any, :get, :head, :post, :put, :patch, :delete, :options]
190
+ #
191
+ # If you try to specify :methods on an `unsecured` resource it will throw
192
+ # an exception.
193
+ secured do
194
+ # GET only
195
+ resource '/http_get_only', jwt: jwt_opts, methods: [:get]
196
+
197
+ # GET or POST
198
+ resource '/http_post_or_get', jwt: jwt_opts, methods: [:get, :post]
199
+
200
+ # ANY HTTP method allowed
201
+ resource '/http_any', jwt: jwt_opts, methods: [:any]
202
+
203
+ # ANY HTTP method allowed (alternate)
204
+ # This is the same as [:any]
205
+ resource '/http_any_manual', jwt: jwt_opts, methods: [:get, :head, :post, :put, :patch, :delete, :options]
206
+
207
+ # IGNORED! This resource path was already defined above!
208
+ # Even though it has different methods allowed it will be ignored.
209
+ resource '/http_post_or_get', jwt: jwt_opts, methods: [:post]
210
+ end
211
+
173
212
  # You can have more than one `unsecured` or `secured` block if you like.
174
213
  unsecured do
175
214
  # WARNING : this resource will never be used since it is masked
@@ -3,30 +3,28 @@ require 'contracts'
3
3
  require 'hashie'
4
4
  require 'jwt_claims'
5
5
 
6
+ require 'rack/json_web_token_auth/exceptions'
6
7
  require 'rack/json_web_token_auth/contracts'
7
8
  require 'rack/json_web_token_auth/resources'
8
9
  require 'rack/json_web_token_auth/resource'
9
10
 
10
11
  module Rack
11
- # Custom error class
12
- class TokenError < StandardError; end
13
-
14
12
  # Rack Middleware for JSON Web Token Authentication
15
13
  class JsonWebTokenAuth
16
14
  include Contracts::Core
17
- C = Contracts
15
+ include Contracts::Builtin
18
16
 
19
17
  ENV_KEY = 'jwt.claims'.freeze
20
18
  PATH_INFO_HEADER_KEY = 'PATH_INFO'.freeze
21
19
 
22
- Contract C::Any, Proc => C::Any
20
+ Contract Any, Proc => Any
23
21
  def initialize(app, &block)
24
22
  @app = app
25
23
  # execute the block methods provided in the context of this class
26
24
  instance_eval(&block)
27
25
  end
28
26
 
29
- Contract Proc => C::ArrayOf[Resources]
27
+ Contract Proc => ArrayOf[Resources]
30
28
  def secured(&block)
31
29
  resources = Resources.new(public_resource: false)
32
30
  # execute the methods in the 'secured' block in the context of
@@ -35,7 +33,7 @@ module Rack
35
33
  all_resources << resources
36
34
  end
37
35
 
38
- Contract Proc => C::ArrayOf[Resources]
36
+ Contract Proc => ArrayOf[Resources]
39
37
  def unsecured(&block)
40
38
  resources = Resources.new(public_resource: true)
41
39
  # execute the methods in the 'unsecured' block in the context of
@@ -46,82 +44,88 @@ module Rack
46
44
 
47
45
  Contract Hash => RackResponse
48
46
  def call(env)
49
- begin
50
- resource = resource_for_path(env[PATH_INFO_HEADER_KEY])
51
-
52
- if resource && resource.public_resource?
53
- # whitelisted as `unsecured`. skip all token authentication.
54
- @app.call(env)
55
- elsif resource.nil?
56
- # no matching `secured` or `unsecured` resource.
57
- # fail-safe with 401 unauthorized
58
- raise TokenError, 'No resource for path defined. Deny by default.'
59
- else
60
- # a `secured` resource, validate the token to see if authenticated
61
-
62
- # Test that `env` has a well formed Authorization header
63
- unless Contract.valid?(env, RackRequestHttpAuth)
64
- raise TokenError, 'malformed Authorization header or token'
65
- end
66
-
67
- # Extract the token from the 'Authorization: Bearer token' string
68
- token = BEARER_TOKEN_REGEX.match(env['HTTP_AUTHORIZATION'])[1]
69
-
70
- # Verify the token and its claims are valid
71
- jwt_opts = resource.opts[:jwt]
72
- jwt = ::JwtClaims.verify(token, jwt_opts)
73
-
74
- # JwtClaims.verify returns a JWT claims set hash, if the
75
- # JWT Message Authentication Code (MAC), or signature,
76
- # are verified and the registered claims are also verified.
77
- if Contract.valid?(jwt, C::HashOf[ok: C::HashOf[Symbol => C::Any]])
78
- # Authenticated! Pass all claims into the app env for app use
79
- # with the hash keys converted to strings to match Rack env.
80
- env[ENV_KEY] = Hashie.stringify_keys(jwt[:ok])
81
- elsif Contract.valid?(jwt, C::HashOf[error: C::ArrayOf[Symbol]])
82
- # a list of any registered claims that fail validation, if the JWT MAC is verified
83
- raise TokenError, "invalid JWT claims : #{jwt[:error].sort.join(', ')}"
84
- elsif Contract.valid?(jwt, C::HashOf[error: 'invalid JWT'])
85
- # the JWT MAC is not verified
86
- raise TokenError, 'invalid JWT'
87
- elsif Contract.valid?(jwt, C::HashOf[error: 'invalid input'])
88
- # otherwise
89
- raise TokenError, 'invalid JWT input'
90
- else
91
- raise TokenError, 'unhandled JWT error'
92
- end
93
-
94
- @app.call(env)
47
+ resource = resource_for_path(env[PATH_INFO_HEADER_KEY])
48
+
49
+ # no matching `secured` or `unsecured` resource.
50
+ # fail-safe with 401 unauthorized
51
+ if resource.nil?
52
+ raise TokenError, 'No resource for path defined. Deny by default.'
53
+ end
54
+
55
+ if resource.public_resource?
56
+ # whitelisted as `unsecured`. skip all token authentication.
57
+ @app.call(env)
58
+ else
59
+ # HTTP method not permitted
60
+ if resource.invalid_http_method?(env['REQUEST_METHOD'])
61
+ raise HttpMethodError, 'HTTP request method denied'
95
62
  end
96
- rescue TokenError => e
97
- body = e.message.nil? ? 'Unauthorized' : "Unauthorized : #{e.message}"
98
- headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"',
99
- 'Content-Type' => 'text/plain',
100
- 'Content-Length' => body.bytesize.to_s }
101
- [401, headers, [body]]
102
- rescue StandardError => e
103
- # puts e.message
104
- body = 'Unauthorized'
105
- headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"',
106
- 'Content-Type' => 'text/plain',
107
- 'Content-Length' => body.bytesize.to_s }
108
- [401, headers, [body]]
63
+
64
+ # Test that `env` has a well formed Authorization header
65
+ unless Contract.valid?(env, RackRequestHttpAuth)
66
+ raise TokenError, 'malformed Authorization header or token'
67
+ end
68
+
69
+ # Extract the token from the 'Authorization: Bearer token' string
70
+ token = BEARER_TOKEN_REGEX.match(env['HTTP_AUTHORIZATION'])[1]
71
+
72
+ # Verify the token and its claims are valid
73
+ jwt_opts = resource.opts[:jwt]
74
+ jwt = ::JwtClaims.verify(token, jwt_opts)
75
+ handle_token(env, jwt)
76
+
77
+ @app.call(env)
109
78
  end
79
+ rescue TokenError => e
80
+ return_401(e.message)
81
+ rescue StandardError
82
+ return_401
110
83
  end
111
84
 
112
- Contract C::None => C::Or[C::ArrayOf[Resources], []]
85
+ Contract None => Or[ArrayOf[Resources], []]
113
86
  def all_resources
114
87
  @all_resources ||= []
115
88
  end
116
89
 
117
- Contract String => C::Maybe[Resource]
90
+ Contract String => Maybe[Resource]
118
91
  def resource_for_path(path_info)
119
92
  all_resources.each do |r|
120
- if found = r.resource_for_path(path_info)
121
- return found
122
- end
93
+ found = r.resource_for_path(path_info)
94
+ return found unless found.nil?
123
95
  end
124
96
  nil
125
97
  end
98
+
99
+ Contract String => RackResponse
100
+ def return_401(msg = nil)
101
+ body = msg.nil? ? 'Unauthorized' : "Unauthorized : #{msg}"
102
+ headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"',
103
+ 'Content-Type' => 'text/plain',
104
+ 'Content-Length' => body.bytesize.to_s }
105
+ [401, headers, [body]]
106
+ end
107
+
108
+ # JwtClaims.verify returns a JWT claims set hash, if the
109
+ # JWT Message Authentication Code (MAC), or signature,
110
+ # are verified and the registered claims are also verified.
111
+ Contract Hash, Hash => Hash
112
+ def handle_token(env, jwt)
113
+ if Contract.valid?(jwt, HashOf[ok: HashOf[Symbol => Any]])
114
+ # Authenticated! Pass all claims into the app env for app use
115
+ # with the hash keys converted to strings to match Rack env.
116
+ env[ENV_KEY] = Hashie.stringify_keys(jwt[:ok])
117
+ elsif Contract.valid?(jwt, HashOf[error: ArrayOf[Symbol]])
118
+ # a list of any registered claims that fail validation, if the JWT MAC is verified
119
+ raise TokenError, "invalid JWT claims : #{jwt[:error].sort.join(', ')}"
120
+ elsif Contract.valid?(jwt, HashOf[error: 'invalid JWT'])
121
+ # the JWT MAC is not verified
122
+ raise TokenError, 'invalid JWT'
123
+ elsif Contract.valid?(jwt, HashOf[error: 'invalid input'])
124
+ # otherwise
125
+ raise TokenError, 'invalid JWT input'
126
+ else
127
+ raise TokenError, 'unhandled JWT error'
128
+ end
129
+ end
126
130
  end
127
131
  end
@@ -1,8 +1,5 @@
1
1
  module Rack
2
2
  class JsonWebTokenAuth
3
- include Contracts::Core
4
- C = Contracts
5
-
6
3
  # Custom Contracts
7
4
  # See : https://egonschiele.github.io/contracts.ruby/
8
5
 
@@ -19,76 +16,55 @@ module Rack
19
16
  )$
20
17
  }x
21
18
 
22
- class RackRequestHttpAuth
23
- def self.valid?(val)
24
- Contract.valid?(val, ({ 'HTTP_AUTHORIZATION' => BEARER_TOKEN_REGEX }))
25
- end
26
-
27
- def self.to_s
28
- 'A Rack request with JWT auth header'
29
- end
30
- end
31
-
32
- class RackResponse
19
+ # These are Symbols and include the special :any value
20
+ class ResourceHttpMethods
33
21
  def self.valid?(val)
34
- Contract.valid?(val, [C::Int, Hash, C::Any])
22
+ Contract.valid?(val, Contracts::ArrayOf[Contracts::Enum[:any, :get, :head, :post, :put, :patch, :delete, :options]])
35
23
  end
36
24
 
37
25
  def self.to_s
38
- 'A Rack response'
26
+ 'An array of allowed HTTP methods for initializing a Resource'
39
27
  end
40
28
  end
41
29
 
42
- class Key
30
+ class HttpMethods
43
31
  def self.valid?(val)
44
- return false if val.is_a?(String) && val.strip.empty?
45
- C::Or[String, OpenSSL::PKey::RSA, OpenSSL::PKey::EC].valid?(val)
32
+ Contract.valid?(val, Contracts::Enum['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'])
46
33
  end
47
34
 
48
35
  def self.to_s
49
- 'A JWT secret string or signature key'
36
+ 'An array of allowed HTTP methods'
50
37
  end
51
38
  end
52
39
 
53
- class Algorithm
54
- def self.valid?(val)
55
- C::Enum['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'].valid?(val)
56
- end
57
-
58
- def self.to_s
59
- 'A valid JWT token signature algorithm, or none'
60
- end
61
- end
62
-
63
- class DecodedToken
40
+ class RackRequestHttpAuth
64
41
  def self.valid?(val)
65
- C::ArrayOf[Hash].valid?(val) &&
66
- C::DecodedTokenClaims.valid?(val[0]) &&
67
- C::DecodedTokenHeader.valid?(val[1])
42
+ Contract.valid?(val, ({ 'HTTP_AUTHORIZATION' => BEARER_TOKEN_REGEX }))
68
43
  end
69
44
 
70
45
  def self.to_s
71
- 'A valid Array of decoded token claims and header Hashes'
46
+ 'A Rack request with JWT auth header'
72
47
  end
73
48
  end
74
49
 
75
- class DecodedTokenClaims
50
+ class RackResponse
76
51
  def self.valid?(val)
77
- C::HashOf[C::Or[String, Symbol] => C::Maybe[C::Or[String, C::Num, C::Bool, C::ArrayOf[C::Any], Hash]]].valid?(val)
52
+ Contract.valid?(val, [Contracts::Int, Hash, Contracts::Any])
78
53
  end
79
54
 
80
55
  def self.to_s
81
- 'A valid decoded token payload attribute'
56
+ 'A Rack response'
82
57
  end
83
58
  end
84
59
 
85
- class DecodedTokenHeader
60
+ class Key
86
61
  def self.valid?(val)
87
- C::HashOf[C::Enum['typ', 'alg'] => C::Or['JWT', C::TokenAlgorithm]].valid?(val)
62
+ return false if val.is_a?(String) && val.strip.empty?
63
+ Contracts::Or[String, OpenSSL::PKey::RSA, OpenSSL::PKey::EC].valid?(val)
88
64
  end
89
65
 
90
66
  def self.to_s
91
- 'A valid decoded token header attribute'
67
+ 'A JWT secret string or signature key'
92
68
  end
93
69
  end
94
70
  end
@@ -0,0 +1,6 @@
1
+ module Rack
2
+ class JsonWebTokenAuth
3
+ class TokenError < StandardError; end
4
+ class HttpMethodError < StandardError; end
5
+ end
6
+ end
@@ -2,11 +2,11 @@ module Rack
2
2
  class JsonWebTokenAuth
3
3
  class Resource
4
4
  include Contracts::Core
5
- C = Contracts
5
+ include Contracts::Builtin
6
6
 
7
- attr_accessor :public_resource, :path, :pattern, :opts
7
+ attr_reader :public_resource, :path, :pattern, :methods, :opts
8
8
 
9
- Contract C::Bool, String, Hash => C::Any
9
+ Contract Bool, String, ({ jwt: Maybe[Hash], methods: Maybe[ResourceHttpMethods] }) => Any
10
10
  def initialize(public_resource, path, opts = {})
11
11
  @public_resource = public_resource
12
12
  @path = path
@@ -14,9 +14,14 @@ module Rack
14
14
  @opts = opts
15
15
 
16
16
  if public_resource
17
- # unsecured resources should not have any jwt options defined
17
+ # unsecured resources should not have a :jwt option defined
18
18
  if @opts.key?(:jwt)
19
- raise 'unexpected jwt options provided for unsecured resource'
19
+ raise 'unexpected :jwt option provided for unsecured resource'
20
+ end
21
+
22
+ # unsecured resources should not have a :methods option defined
23
+ if @opts.key?(:methods)
24
+ raise 'unexpected :methods option provided for unsecured resource'
20
25
  end
21
26
  else
22
27
  # secured resources must have a :jwt hash with a :key
@@ -24,42 +29,55 @@ module Rack
24
29
  Contract.valid?(@opts, ({ jwt: { key: Key } }))
25
30
  raise 'invalid or missing jwt options for secured resource'
26
31
  end
32
+
33
+ # Don't allow providing other HTTP methods with :any
34
+ if opts[:methods] && opts[:methods].include?(:any) && opts[:methods].size > 1
35
+ raise 'unexpected additional methods provided with :any'
36
+ end
37
+
38
+ @methods = if opts[:methods].nil?
39
+ [:get]
40
+ elsif opts[:methods] == [:any]
41
+ [:get, :head, :post, :put, :patch, :delete, :options]
42
+ else
43
+ opts[:methods]
44
+ end.map { |e| e.to_s }
27
45
  end
28
46
  end
29
47
 
30
- Contract String => C::Maybe[Fixnum]
48
+ Contract String => Maybe[Integer]
31
49
  def matches_path?(path)
32
50
  pattern =~ path
33
51
  end
34
52
 
35
- Contract C::None => C::Bool
53
+ Contract None => Bool
36
54
  def public_resource?
37
55
  public_resource
38
56
  end
39
57
 
58
+ Contract HttpMethods => Bool
59
+ def invalid_http_method?(request_method)
60
+ request_method.nil? || !methods.include?(request_method.downcase)
61
+ end
62
+
40
63
  protected
41
64
 
42
65
  Contract String => Regexp
43
66
  def compile(path)
44
- if path.respond_to? :to_str
45
- special_chars = %w{. + ( )}
46
- pattern =
47
- path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
48
- case match
49
- when "*"
50
- "(.*?)"
51
- when *special_chars
52
- Regexp.escape(match)
53
- else
54
- "([^/?&#]+)"
55
- end
56
- end
57
- /^#{pattern}$/
58
- elsif path.respond_to? :match
59
- path
60
- else
61
- raise TypeError, path
67
+ special_chars = %w{. + ( )}
68
+
69
+ pattern = path.gsub(/([\*#{special_chars.join}])/) do |match|
70
+ case match
71
+ when '*'
72
+ '(.*?)'
73
+ when *special_chars
74
+ Regexp.escape(match)
75
+ else
76
+ '([^/?&#]+)'
77
+ end
62
78
  end
79
+
80
+ /^#{pattern}$/
63
81
  end
64
82
  end
65
83
  end
@@ -4,25 +4,25 @@ module Rack
4
4
  class JsonWebTokenAuth
5
5
  class Resources
6
6
  include Contracts::Core
7
- C = Contracts
7
+ include Contracts::Builtin
8
8
 
9
- Contract C::KeywordArgs[public_resource: C::Bool] => C::Any
9
+ Contract KeywordArgs[public_resource: Bool] => Any
10
10
  def initialize(public_resource: false)
11
11
  @resources = []
12
12
  @public_resource = public_resource
13
13
  end
14
14
 
15
- Contract C::None => C::Bool
15
+ Contract None => Bool
16
16
  def public_resource?
17
17
  @public_resource
18
18
  end
19
19
 
20
- Contract String, C::Maybe[Hash] => C::ArrayOf[Resource]
20
+ Contract String, Maybe[Hash] => ArrayOf[Resource]
21
21
  def resource(path, opts = {})
22
22
  @resources << Resource.new(public_resource?, path, opts)
23
23
  end
24
24
 
25
- Contract String => C::Maybe[Resource]
25
+ Contract String => Maybe[Resource]
26
26
  def resource_for_path(path)
27
27
  # return first match
28
28
  @resources.detect { |r| r.matches_path?(path) }
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class JsonWebTokenAuth
3
- VERSION = '0.1.1'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-json_web_token_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Glenn Rempe
@@ -30,7 +30,7 @@ cert_chain:
30
30
  vprF5QiDz8HshVP9DjJT2I1wyGyvxEdU3cTRo0upMP/VZLcgyBVFy90N2XYWWk2D
31
31
  GIxGSw==
32
32
  -----END CERTIFICATE-----
33
- date: 2016-10-14 00:00:00.000000000 Z
33
+ date: 2016-10-16 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: contracts
@@ -198,6 +198,7 @@ files:
198
198
  - README.md
199
199
  - lib/rack/json_web_token_auth.rb
200
200
  - lib/rack/json_web_token_auth/contracts.rb
201
+ - lib/rack/json_web_token_auth/exceptions.rb
201
202
  - lib/rack/json_web_token_auth/resource.rb
202
203
  - lib/rack/json_web_token_auth/resources.rb
203
204
  - lib/rack/json_web_token_auth/version.rb
metadata.gz.sig CHANGED
Binary file