rack-json_web_token_auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 32ba799c3c70c2a2d97847f43783f122061ca956
4
+ data.tar.gz: 48e78024ba8183d174845324994d98e7a7bb2335
5
+ SHA512:
6
+ metadata.gz: b1c1bfb51899743df81370e00ea4961eb5af410e90a10c9d1ab544547f8601b55a2e9aede318e590edb9033d4d0d8203904d303fa9fe10533641ea091745b848
7
+ data.tar.gz: 24257ad9bc718101ce0fe7cbb5c5aaf7df7bdbae56106f7800f6f717291884f31ffe6f65126ac339aa4a62b09d20a493cb17c3d9c77a535bd5bbbc443c542ae1
@@ -0,0 +1,2 @@
1
+ e� ~v(LC�6���86M@z�$b ���mP�$n�N����v��.$`٪�ɩi(e4�����"|��6΂��,��l5bְ�Ǵ�ϽE�B��\C.p�{>�A�]Z/���]��q,Y�h���D5#��=���GԆP�ǕM�~����yi�>���z�
2
+ *P��m`��W���?���C�ƒO~�۾!*���wm�7������V�3�+�J��й��H�)��M�O���y�X�8��� 5[���b�:�袋
@@ -0,0 +1 @@
1
+ �1�
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 Glenn Rempe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,263 @@
1
+ # Rack::JsonWebTokenAuth
2
+
3
+ ## WARNING
4
+
5
+ This is pre-release software. It is pretty well tested but has not yet
6
+ been used in production. Your feedback is requested.
7
+
8
+ ## About
9
+
10
+ `Rack::JsonWebTokenAuth` is a Rack middleware that makes it easy for your
11
+ Rack based application (Sinatra, Rails) to authenticate clients that
12
+ present a valid `Authorization: Bearer token` header with a [JSON Web Token (JWT)](https://jwt.io/).
13
+
14
+ This middleware was inspired by the similar [eigenbart/rack-jwt](https://github.com/eigenbart/rack-jwt)
15
+ middleware but provides a leaner codebase that relies upon the excellent
16
+ [garyf/jwt_claims](https://github.com/garyf/jwt_claims) and [garyf/json_web_token](https://github.com/garyf/json_web_token) gems to provide
17
+ all JWT token validation. This gem also makes extensive use of the [contracts](https://egonschiele.github.io/contracts.ruby/) gem to enforce strict
18
+ type checking on all inputs and outputs. It is designed to fail-fast on errors and
19
+ reject invalid inputs before even trying to parse them using JWT.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's `Gemfile`:
24
+
25
+ ```ruby
26
+ gem 'rack-json_web_token_auth'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```
32
+ $ bundle install
33
+ ```
34
+
35
+ Or install it directly with:
36
+
37
+ ```
38
+ $ gem install rack-json_web_token_auth
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ This Rack middleware is designed to allow adding a simple authentication layer,
44
+ using JSON Web Tokens (JWT), to your Rack based applications. It's easy
45
+ to configure with a simple Ruby DSL.
46
+
47
+ This middleware is not responsible for creating valid JWT tokens for you. It
48
+ only receives and validates them. If the token provided is valid for a specific
49
+ path the request will be allowed to continue as normal. If the token is invalid,
50
+ or the path requested is not a configured path, a `401 Not Authorized`
51
+ HTTP response will be sent.
52
+
53
+ For token creation I recommend the
54
+ [garyf/json_web_token](https://github.com/garyf/json_web_token) gem.
55
+
56
+ ### Creating a JWT
57
+
58
+ Here is an example of creating a JWT with a pretty full set of claims. You may
59
+ not need all of these for your application.
60
+
61
+ ```ruby
62
+ require 'json_web_token'
63
+
64
+ key = '4a7b98c31c3b6918f916d809443c096d02bf686d6bead5baa4a162642cea98b3'
65
+
66
+ claims = {
67
+ name: 'John Doe',
68
+ iat: Time.now.to_i - 1,
69
+ nbf: Time.now.to_i - 5,
70
+ exp: Time.now.to_i + 10,
71
+ aud: %w(api web),
72
+ sub: 'my-user-id',
73
+ jti: 'my-unique-token-id',
74
+ iss: 'https://my.example.com/'
75
+ }
76
+
77
+ # generate a signed token
78
+ jwt = JsonWebToken.sign(claims, key: key, alg: 'HS256')
79
+ #=> "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE0NzY0MTUwMjUsIm5iZiI6MTQ3NjQxNTAyMSwiZXhwIjoxNDc2NDE1MDM2LCJhdWQiOlsiYXBpIiwid2ViIl0sInN1YiI6Im15LXVzZXItaWQiLCJqdGkiOiJteS11bmlxdWUtdG9rZW4taWQiLCJpc3MiOiJodHRwczovL215LmV4YW1wbGUuY29tLyJ9.-zu-FGfLmwLX69DC2UIsk-8oEGoRSkCOUqbJwcarSm4"
80
+ ```
81
+
82
+ ### Submitting a JWT
83
+
84
+ Your client of choice needs to submit an [Authorization Bearer](http://self-issued.info/docs/draft-ietf-oauth-v2-bearer.html) request header.
85
+
86
+ How you do this is client specific and left as an exercise for the reader.
87
+
88
+ ```
89
+ 'Authorization' => "Bearer #{jwt}"
90
+ ```
91
+
92
+ ### Server Config
93
+
94
+ This middleware should be inserted as early as possible into your middleware
95
+ stack.
96
+
97
+ Configuring the Rack middleware to accept JWT tokens on your server is just a
98
+ matter of adding the middleware and configuring which paths are to be considered
99
+ public and `unsecured` (no JWT needed), and which require a valid token
100
+ to continue. These are private `secured` paths.
101
+
102
+ For each `secured` resource you must also provide the JWT config needed to validate
103
+ incoming tokens. The available claims are processed by the [garyf/jwt_claims](https://github.com/garyf/jwt_claims) gem and more info about
104
+ claims can be found in the README for that project. At a minimum a `:key` must
105
+ be provided except if the `none` algorithm is being used (probably not recommended).
106
+
107
+ 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
109
+ `unsecured` resources first and order all resources from most specific to least
110
+ specific.
111
+
112
+ 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.
114
+
115
+ ```ruby
116
+ require 'rack/json_web_token_auth'
117
+
118
+ use Rack::JsonWebTokenAuth.new do
119
+
120
+ # You can define JWT options for all `secured` resources globally
121
+ # or you can specify a hash like this inside each block. If you want to
122
+ # get really granular this config can even be different per `secure` resource.
123
+ jwt_opts = {
124
+ key: '4a7b98c31c3b6918f916d809443c096d02bf686d6bead5baa4a162642cea98b3',
125
+ alg: 'HS256',
126
+ aud: 'api',
127
+ sub: 'my-user-id',
128
+ jti: 'my-unique-token-id',
129
+ iss: 'https://my.example.com/',
130
+ leeway_seconds: 30
131
+ }
132
+
133
+ # Resources defined in this block are whitelisted and
134
+ # require no token for requests to the configured
135
+ # resource path. You should probably define your unsecured
136
+ # paths first. Resources in this block will raise an exception
137
+ # if provided with the :jwt options hash.
138
+ unsecured do
139
+ resource '/users/registration'
140
+ resource '/users/login'
141
+ end
142
+
143
+ # Resources defined in this block require a valid JWT token
144
+ # for access. Each resource takes a path and a Hash of options.
145
+ # The only option supported at the moment is `jwt`. The `:jwt` Hash
146
+ # key should be set to a Hash and only a `:key` must be defined
147
+ # which is a random key of sufficient strength.
148
+ #
149
+ # Additional JWT claims can also be provided in this hash as shown in
150
+ # this example.
151
+ #
152
+ # Resources defined in this block will raise an exception if they
153
+ # are not provided with the `:jwt` options hash and a valid `:key`
154
+ # (unless using the 'none' algorithm).
155
+ secured do
156
+ # a resource can start with a slash and match an exact path
157
+ resource '/private', jwt: jwt_opts
158
+
159
+ # or it can contain a wildcard '*'. The entire path
160
+ # can even be specified with '*' if you wanted to
161
+ # match all paths.
162
+ resource '/private/*/wildcard', jwt: jwt_opts
163
+
164
+ # Every resource can be configured with its own
165
+ # JWT keys and all other valid JWT claim options.
166
+ # For example you could require one token config for
167
+ # login and registration, and on successful login mint
168
+ # another flavor of token for all other app API access.
169
+ resource '/another/path', jwt: {key: 'a long random key', alg: 'HS512'}
170
+ end
171
+
172
+ # You can have more than one `unsecured` or `secured` block if you like.
173
+ unsecured do
174
+ # WARNING : this resource will never be used since it is masked
175
+ # by another resource higher in the stack with the same '/private' path.
176
+ resource '/private'
177
+ end
178
+
179
+ # Requests to any resource path not explictly marked as 'secured' or
180
+ # `unsecured` above will fail-safe and return a 401 status.
181
+ # e.g. /path/to/somewhere/else
182
+ end
183
+ ```
184
+
185
+ ## Development
186
+
187
+ After checking out the repo, run `bundle install` to install dependencies. Then,
188
+ run `bundle exec rake` to run the specs.
189
+
190
+ To install this gem onto your local machine, run `bundle exec rake install`.
191
+
192
+ ### Installation Security : Signed Ruby Gem
193
+
194
+ This gem is cryptographically signed. To be sure the gem you install hasn’t
195
+ been tampered with you can install it using the following method:
196
+
197
+ Add my public key (if you haven’t already) as a trusted certificate
198
+
199
+ ```
200
+ # Caveat: Gem certificates are trusted globally, such that adding a
201
+ # cert.pem for one gem automatically trusts all gems signed by that cert.
202
+ gem cert --add <(curl -Ls https://raw.github.com/grempe/rack-json_web_token_auth/master/certs/gem-public_cert_grempe_2026.pem)
203
+ ```
204
+
205
+ To install, it is possible to specify either `HighSecurity` or `MediumSecurity`
206
+ mode. Since this gem depends on one or more gems that are not cryptographically
207
+ signed you will likely need to use `MediumSecurity`. You should receive a warning
208
+ if any signed gem does not match its signature.
209
+
210
+ ```
211
+ # All signed dependent gems must be verified.
212
+ gem install rack-json_web_token_auth -P MediumSecurity
213
+ ```
214
+
215
+ You can [learn more about security and signed Ruby Gems](http://guides.rubygems.org/security/).
216
+
217
+ ### Installation Security : Signed Git Commits
218
+
219
+ Most, if not all, of the commits and tags to this repository are
220
+ signed with my PGP/GPG code signing key. I have uploaded my code signing public
221
+ keys to GitHub and you can now verify those signatures with the GitHub UI.
222
+ See [this list of commits](https://github.com/grempe/rack-json_web_token_auth/commits/master)
223
+ and look for the `Verified` tag next to each commit. You can click on that tag
224
+ for additional information.
225
+
226
+ You can also clone the repository and verify the signatures locally using your
227
+ own GnuPG installation. You can find my certificates and read about how to conduct
228
+ this verification at [https://www.rempe.us/keys/](https://www.rempe.us/keys/).
229
+
230
+ ### Contributing
231
+
232
+ Bug reports and pull requests are welcome on GitHub
233
+ at [https://github.com/grempe/rack-json_web_token_auth](https://github.com/grempe/rack-json_web_token_auth). This project is intended to be a safe, welcoming space for collaboration, and
234
+ contributors are expected to adhere to the
235
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
236
+
237
+ ## Legal
238
+
239
+ ### Copyright
240
+
241
+ (c) 2016 Glenn Rempe <[glenn@rempe.us](mailto:glenn@rempe.us)> ([https://www.rempe.us/](https://www.rempe.us/))
242
+
243
+ ### License
244
+
245
+ The gem is available as open source under the terms of
246
+ the [MIT License](http://opensource.org/licenses/MIT).
247
+
248
+ ### Warranty
249
+
250
+ Unless required by applicable law or agreed to in writing,
251
+ software distributed under the License is distributed on an
252
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
253
+ either express or implied. See the LICENSE.txt file for the
254
+ specific language governing permissions and limitations under
255
+ the License.
256
+
257
+ ## Thank You!
258
+
259
+ Thanks to Gary Fleshman ([@garyf](https://github.com/garyf)) for
260
+ his very well written implementation of JWT and for accepting my patches.
261
+
262
+ And of course thanks to Mr. Eigenbart ([@eigenbart](https://github.com/eigenbart))
263
+ for the inspiration.
@@ -0,0 +1,135 @@
1
+ module Contracts
2
+ C = Contracts
3
+
4
+ # Custom Contracts
5
+ # See : https://egonschiele.github.io/contracts.ruby/
6
+
7
+ # The last segment gets dropped for 'none' algorithm since there is no
8
+ # signature so both of these patterns are valid. All character chunks
9
+ # are base64url format and periods.
10
+ # Bearer abc123.abc123.abc123
11
+ # Bearer abc123.abc123.
12
+ BEARER_TOKEN_REGEX = %r{
13
+ ^Bearer\s{1}( # starts with Bearer and a single space
14
+ [a-zA-Z0-9\-\_]+\. # 1 or more chars followed by a single period
15
+ [a-zA-Z0-9\-\_]+\. # 1 or more chars followed by a single period
16
+ [a-zA-Z0-9\-\_]* # 0 or more chars, no trailing chars
17
+ )$
18
+ }x
19
+
20
+ class RackRequestHttpAuth
21
+ def self.valid?(val)
22
+ Contract.valid?(val, ({ 'HTTP_AUTHORIZATION' => BEARER_TOKEN_REGEX }))
23
+ end
24
+
25
+ def self.to_s
26
+ 'A Rack request with JWT auth header'
27
+ end
28
+ end
29
+
30
+ class RackResponse
31
+ def self.valid?(val)
32
+ Contract.valid?(val, [C::Int, Hash, C::Any])
33
+ end
34
+
35
+ def self.to_s
36
+ 'A Rack response'
37
+ end
38
+ end
39
+
40
+ class Key
41
+ def self.valid?(val)
42
+ return false if val.is_a?(String) && val.strip.empty?
43
+ C::Or[String, OpenSSL::PKey::RSA, OpenSSL::PKey::EC].valid?(val)
44
+ end
45
+
46
+ def self.to_s
47
+ 'A JWT secret string or signature key'
48
+ end
49
+ end
50
+
51
+ class Algorithm
52
+ def self.valid?(val)
53
+ C::Enum['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'].valid?(val)
54
+ end
55
+
56
+ def self.to_s
57
+ 'A valid JWT token signature algorithm, or none'
58
+ end
59
+ end
60
+
61
+ class VerifierOptions
62
+ def self.valid?(val)
63
+ C::KeywordArgs[
64
+ key: C::Optional[C::Key],
65
+ alg: C::Optional[C::Algorithm],
66
+ iat: C::Optional[C::Int],
67
+ nbf: C::Optional[C::Int],
68
+ exp: C::Optional[C::Int],
69
+ iss: C::Optional[String],
70
+ jti: C::Optional[String],
71
+ aud: C::Optional[C::Or[String, C::ArrayOf[String], Symbol, C::ArrayOf[Symbol]]],
72
+ sub: C::Optional[String],
73
+ leeway_seconds: C::Optional[C::Int]
74
+ ].valid?(val)
75
+ end
76
+
77
+ def self.to_s
78
+ 'A Hash of token verifier options'
79
+ end
80
+ end
81
+
82
+ # abc123.abc123.abc123 (w/ signature)
83
+ # abc123.abc123. ('none')
84
+ class EncodedToken
85
+ def self.valid?(val)
86
+ val =~ /\A([a-zA-Z0-9\-\_]+\.[a-zA-Z0-9\-\_]+\.[a-zA-Z0-9\-\_]*)\z/
87
+ end
88
+
89
+ def self.to_s
90
+ 'A valid encoded token'
91
+ end
92
+ end
93
+
94
+ class DecodedToken
95
+ def self.valid?(val)
96
+ C::ArrayOf[Hash].valid?(val) &&
97
+ C::DecodedTokenClaims.valid?(val[0]) &&
98
+ C::DecodedTokenHeader.valid?(val[1])
99
+ end
100
+
101
+ def self.to_s
102
+ 'A valid Array of decoded token claims and header Hashes'
103
+ end
104
+ end
105
+
106
+ class DecodedTokenClaims
107
+ def self.valid?(val)
108
+ C::HashOf[C::Or[String, Symbol] => C::Maybe[C::Or[String, C::Num, C::Bool, C::ArrayOf[C::Any], Hash]]].valid?(val)
109
+ end
110
+
111
+ def self.to_s
112
+ 'A valid decoded token payload attribute'
113
+ end
114
+ end
115
+
116
+ class DecodedTokenHeader
117
+ def self.valid?(val)
118
+ C::HashOf[C::Enum['typ', 'alg'] => C::Or['JWT', C::TokenAlgorithm]].valid?(val)
119
+ end
120
+
121
+ def self.to_s
122
+ 'A valid decoded token header attribute'
123
+ end
124
+ end
125
+
126
+ class ResourcePath
127
+ def self.valid?(val)
128
+ C::Or[String, Regexp]
129
+ end
130
+
131
+ def self.to_s
132
+ 'A valid resource path string or regex'
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,117 @@
1
+ require 'json'
2
+ require 'contracts'
3
+ require 'hashie'
4
+ require 'jwt_claims'
5
+
6
+ require 'rack/json_web_token_auth/resources'
7
+ require 'rack/json_web_token_auth/resource'
8
+ require 'custom_contracts'
9
+
10
+ module Rack
11
+ # Rack Middleware for JSON Web Token Authentication
12
+ class JsonWebTokenAuth
13
+ include Contracts::Core
14
+ C = Contracts
15
+
16
+ ENV_KEY = 'jwt.claims'.freeze
17
+ PATH_INFO_HEADER_KEY = 'PATH_INFO'.freeze
18
+
19
+ Contract C::Any, Proc => C::Any
20
+ def initialize(app, &block)
21
+ @app = app
22
+ # execute the block methods provided in the context of this class
23
+ instance_eval(&block)
24
+ end
25
+
26
+ Contract Proc => C::ArrayOf[Resources]
27
+ def secured(&block)
28
+ resources = Resources.new(public_resource: false)
29
+ # execute the methods in the 'secured' block in the context of
30
+ # a new Resources object
31
+ resources.instance_eval(&block)
32
+ all_resources << resources
33
+ end
34
+
35
+ Contract Proc => C::ArrayOf[Resources]
36
+ def unsecured(&block)
37
+ resources = Resources.new(public_resource: true)
38
+ # execute the methods in the 'unsecured' block in the context of
39
+ # a new Resources object
40
+ resources.instance_eval(&block)
41
+ all_resources << resources
42
+ end
43
+
44
+ Contract Hash => C::RackResponse
45
+ def call(env)
46
+ begin
47
+ resource = resource_for_path(env[PATH_INFO_HEADER_KEY])
48
+
49
+ if resource.public_resource?
50
+ # whitelisted as `unsecured`. skip all token authentication.
51
+ @app.call(env)
52
+ elsif resource.nil?
53
+ # no matching `secured` or `unsecured` resource.
54
+ # fail-safe with 401 unauthorized
55
+ raise 'No resource for path defined. Deny by default.'
56
+ else
57
+ # a `secured` resource, validate the token to see if authenticated
58
+
59
+ # Test that `env` has a well formed Authorization header
60
+ unless Contract.valid?(env, C::RackRequestHttpAuth)
61
+ raise 'malformed Authorization header or token'
62
+ end
63
+
64
+ # Extract the token from the 'Authorization: Bearer token' string
65
+ token = C::BEARER_TOKEN_REGEX.match(env['HTTP_AUTHORIZATION'])[1]
66
+
67
+ # Verify the token and its claims are valid
68
+ jwt_opts = resource.opts[:jwt]
69
+ jwt = ::JwtClaims.verify(token, jwt_opts)
70
+
71
+ # JwtClaims.verify returns a JWT claims set hash, if the
72
+ # JWT Message Authentication Code (MAC), or signature,
73
+ # are verified and the registered claims are also verified.
74
+ if Contract.valid?(jwt, C::HashOf[ok: C::HashOf[Symbol => C::Any]])
75
+ # Authenticated! Pass all claims into the app env for app use
76
+ # with the hash keys converted to strings to match Rack env.
77
+ env[ENV_KEY] = Hashie.stringify_keys(jwt[:ok])
78
+ elsif Contract.valid?(jwt, C::HashOf[error: C::ArrayOf[Symbol]])
79
+ # a list of any registered claims that fail validation, if the JWT MAC is verified
80
+ raise "invalid JWT claims : #{jwt[:error].sort.join(', ')}"
81
+ elsif Contract.valid?(jwt, C::HashOf[error: 'invalid JWT'])
82
+ # the JWT MAC is not verified
83
+ raise 'invalid JWT'
84
+ elsif Contract.valid?(jwt, C::HashOf[error: 'invalid input'])
85
+ # otherwise
86
+ raise 'invalid JWT input'
87
+ else
88
+ raise 'unhandled JWT error'
89
+ end
90
+
91
+ @app.call(env)
92
+ end
93
+ rescue StandardError => e
94
+ body = e.message.nil? ? 'Unauthorized' : "Unauthorized : #{e.message}"
95
+ headers = { 'WWW-Authenticate' => 'Bearer error="invalid_token"',
96
+ 'Content-Type' => 'text/plain',
97
+ 'Content-Length' => body.bytesize.to_s }
98
+ [401, headers, [body]]
99
+ end
100
+ end
101
+
102
+ Contract C::None => C::Or[C::ArrayOf[Resources], []]
103
+ def all_resources
104
+ @all_resources ||= []
105
+ end
106
+
107
+ Contract String => C::Maybe[Resource]
108
+ def resource_for_path(path_info)
109
+ all_resources.each do |r|
110
+ if found = r.resource_for_path(path_info)
111
+ return found
112
+ end
113
+ end
114
+ nil
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,68 @@
1
+ require 'custom_contracts'
2
+
3
+ module Rack
4
+ class JsonWebTokenAuth
5
+ class Resource
6
+ include Contracts::Core
7
+ C = Contracts
8
+
9
+ attr_accessor :public_resource, :path, :pattern, :opts
10
+
11
+ Contract C::Bool, C::ResourcePath, Hash => C::Any
12
+ def initialize(public_resource, path, opts = {})
13
+ @public_resource = public_resource
14
+ @path = path
15
+ @pattern = compile(path)
16
+ @opts = opts
17
+
18
+ if public_resource
19
+ # unsecured resources should not have any jwt options defined
20
+ if @opts.key?(:jwt)
21
+ raise 'unexpected jwt options provided for unsecured resource'
22
+ end
23
+ else
24
+ # secured resources must have a :jwt hash with a :key
25
+ unless Contract.valid?(@opts, ({ jwt: { key: nil, alg: 'none' } })) ||
26
+ Contract.valid?(@opts, ({ jwt: { key: C::Key } }))
27
+ raise 'invalid or missing jwt options for secured resource'
28
+ end
29
+ end
30
+ end
31
+
32
+ Contract C::ResourcePath => C::Maybe[Fixnum]
33
+ def matches_path?(path)
34
+ pattern =~ path
35
+ end
36
+
37
+ Contract C::None => C::Bool
38
+ def public_resource?
39
+ public_resource
40
+ end
41
+
42
+ protected
43
+
44
+ Contract C::ResourcePath => Regexp
45
+ def compile(path)
46
+ if path.respond_to? :to_str
47
+ special_chars = %w{. + ( )}
48
+ pattern =
49
+ path.to_str.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
50
+ case match
51
+ when "*"
52
+ "(.*?)"
53
+ when *special_chars
54
+ Regexp.escape(match)
55
+ else
56
+ "([^/?&#]+)"
57
+ end
58
+ end
59
+ /^#{pattern}$/
60
+ elsif path.respond_to? :match
61
+ path
62
+ else
63
+ raise TypeError, path
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,33 @@
1
+ require 'custom_contracts'
2
+ require 'rack/json_web_token_auth/resource'
3
+
4
+ module Rack
5
+ class JsonWebTokenAuth
6
+ class Resources
7
+ include Contracts::Core
8
+ C = Contracts
9
+
10
+ Contract C::KeywordArgs[public_resource: C::Bool] => C::Any
11
+ def initialize(public_resource: false)
12
+ @resources = []
13
+ @public_resource = public_resource
14
+ end
15
+
16
+ Contract C::None => C::Bool
17
+ def public_resource?
18
+ @public_resource
19
+ end
20
+
21
+ Contract String, C::Maybe[Hash] => C::ArrayOf[Resource]
22
+ def resource(path, opts = {})
23
+ @resources << Resource.new(public_resource?, path, opts)
24
+ end
25
+
26
+ Contract String => C::Maybe[Resource]
27
+ def resource_for_path(path)
28
+ # return first match
29
+ @resources.detect { |r| r.matches_path?(path) }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class JsonWebTokenAuth
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-json_web_token_auth
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Glenn Rempe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQUFADA7MQ4wDAYDVQQDDAVnbGVu
14
+ bjEVMBMGCgmSJomT8ixkARkWBXJlbXBlMRIwEAYKCZImiZPyLGQBGRYCdXMwHhcN
15
+ MTYxMDEzMDEzMjM5WhcNMjYxMDExMDEzMjM5WjA7MQ4wDAYDVQQDDAVnbGVubjEV
16
+ MBMGCgmSJomT8ixkARkWBXJlbXBlMRIwEAYKCZImiZPyLGQBGRYCdXMwggEiMA0G
17
+ CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrEuLEy11cjgMC4+ldcgLzBrGcfWWg
18
+ nUhdCRn3Arzo2EV1d4V4h6VOHmk4o7kumBeajUMMZ0+xKtu8euRCnbDnlxowfJvT
19
+ S0nzsOt1dm++INeKMpZU84LuH7BbAlyL+B//l1YkI33gsbA8wm06+vV8tUEBuQch
20
+ vBU2xrCyS2+0LQTCaCS+VvHbV97hzIwSIgUFJuFjrcnnpV8Qt1R0Bi8pzDk+2jyN
21
+ AgxaWa41UHn70O0gFRRDGXacRpvy3HRSJrvlHPPAC02CjhKjsOLjZowaHxCv9XIJ
22
+ tCQnVEOUUo9+owG2Gna4k4DMLIjiGChHNFXtO8WyuksukVqcsdc9kvdzAgMBAAGj
23
+ bzBtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBR68/Ook0uwfe6t
24
+ FbLHXIReYQ2VpzAZBgNVHREEEjAQgQ5nbGVubkByZW1wZS51czAZBgNVHRIEEjAQ
25
+ gQ5nbGVubkByZW1wZS51czANBgkqhkiG9w0BAQUFAAOCAQEAI27KUzTE9BoD2irI
26
+ CkMVPC0YS6iANrzQy3zIJI4yLKEZmI1jDE+W2APL11Woo5+sttgqY7148W84ZWdK
27
+ mD9ueqH5hPC8NOd3wYXVMNwmyLhnyh80cOzGeurW1SJ0VV3BqSKEE8q4EFjCzUK9
28
+ Oq8dW9i9Bxn8qgcOSFTYITJZ/mNyy2shHs5gg0MIz0uOsKaHqrrMseVfG7ZoTgV1
29
+ kkyRaYAHI1MSDNGFNwgURPQsgnxQrX8YG48q0ypFC1gOl/l6D0e/oF4SKMS156uc
30
+ vprF5QiDz8HshVP9DjJT2I1wyGyvxEdU3cTRo0upMP/VZLcgyBVFy90N2XYWWk2D
31
+ GIxGSw==
32
+ -----END CERTIFICATE-----
33
+ date: 2016-10-14 00:00:00.000000000 Z
34
+ dependencies:
35
+ - !ruby/object:Gem::Dependency
36
+ name: contracts
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.14'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.14'
49
+ - !ruby/object:Gem::Dependency
50
+ name: hashie
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '3.4'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.4'
63
+ - !ruby/object:Gem::Dependency
64
+ name: json_web_token
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.3.2
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.3.2
77
+ - !ruby/object:Gem::Dependency
78
+ name: jwt_claims
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.1'
84
+ type: :runtime
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0.1'
91
+ - !ruby/object:Gem::Dependency
92
+ name: rake
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '11.3'
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '11.3'
105
+ - !ruby/object:Gem::Dependency
106
+ name: bundler
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '1.13'
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '1.13'
119
+ - !ruby/object:Gem::Dependency
120
+ name: rspec
121
+ requirement: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '3.4'
126
+ type: :development
127
+ prerelease: false
128
+ version_requirements: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '3.4'
133
+ - !ruby/object:Gem::Dependency
134
+ name: rack-test
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '0.6'
140
+ type: :development
141
+ prerelease: false
142
+ version_requirements: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - "~>"
145
+ - !ruby/object:Gem::Version
146
+ version: '0.6'
147
+ - !ruby/object:Gem::Dependency
148
+ name: simplecov
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '0.12'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '0.12'
161
+ - !ruby/object:Gem::Dependency
162
+ name: rubocop
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '0.41'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - "~>"
173
+ - !ruby/object:Gem::Version
174
+ version: '0.41'
175
+ - !ruby/object:Gem::Dependency
176
+ name: wwtd
177
+ requirement: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - "~>"
180
+ - !ruby/object:Gem::Version
181
+ version: '1.3'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - "~>"
187
+ - !ruby/object:Gem::Version
188
+ version: '1.3'
189
+ description: Rack middleware for authentication using JSON Web Tokens using the jwt_claims
190
+ and json_web_token gems.
191
+ email:
192
+ - glenn@rempe.us
193
+ executables: []
194
+ extensions: []
195
+ extra_rdoc_files: []
196
+ files:
197
+ - LICENSE.txt
198
+ - README.md
199
+ - lib/custom_contracts.rb
200
+ - lib/rack/json_web_token_auth.rb
201
+ - lib/rack/json_web_token_auth/resource.rb
202
+ - lib/rack/json_web_token_auth/resources.rb
203
+ - lib/rack/json_web_token_auth/version.rb
204
+ homepage: https://github.com/grempe/rack-json_web_token_auth
205
+ licenses:
206
+ - MIT
207
+ metadata: {}
208
+ post_install_message:
209
+ rdoc_options: []
210
+ require_paths:
211
+ - lib
212
+ required_ruby_version: !ruby/object:Gem::Requirement
213
+ requirements:
214
+ - - ">="
215
+ - !ruby/object:Gem::Version
216
+ version: 2.2.5
217
+ required_rubygems_version: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ requirements: []
223
+ rubyforge_project:
224
+ rubygems_version: 2.5.1
225
+ signing_key:
226
+ specification_version: 4
227
+ summary: Rack middleware for authentication using JSON Web Tokens
228
+ test_files: []
Binary file