rack-json_web_token_auth 0.1.1 → 0.2.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 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