rack-json_web_token_auth 0.1.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.
@@ -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