sinatra-jwt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 23e0995306d0b2428617e6f3b6989a18075a1ee149b08bea4bc02be3163e4267
4
+ data.tar.gz: fa80fff93fbd3083ef40b4889114332363f1cf09e657e68a62e2b6b6eec4d4f3
5
+ SHA512:
6
+ metadata.gz: a771461b7047e1c69ea687e29faaf8dd9c7c3ae8b2039978991c1068dc48c10f64146ab219ebb5b779d726acdda091828e0cf5ec25a83e683e093911ebfc4d1e
7
+ data.tar.gz: ac5b2c2e407033c6c6b79dacce4284d2f8bc0e5d91be598e2efa33d0a773c2c140ba9cc4c240b1897641638cad526cd5131c288a4d1910bef4736b3b089bdeec
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ SuggestExtensions: false
4
+ NewCops: enable
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+
14
+ Layout/LineLength:
15
+ Max: 120
16
+
17
+ Style/Documentation:
18
+ Enabled: false
19
+
20
+ Metrics/BlockLength:
21
+ Exclude:
22
+ - 'spec/**/*.rb'
data/CHANGELOG.md ADDED
File without changes
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at suddani@googlemail.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sinatra-jwt.gemspec
6
+ gemspec
7
+
8
+ gem "rbs"
9
+
10
+ gem "rack-test"
11
+
12
+ gem "rake", "~> 13.0"
13
+
14
+ gem "rspec", "~> 3.0"
15
+
16
+ gem "rubocop", "~> 1.21"
data/Gemfile.lock ADDED
@@ -0,0 +1,77 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sinatra-jwt (0.1.0)
5
+ jwt (~> 2.5)
6
+ sinatra (~> 2.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.2)
12
+ diff-lcs (1.5.0)
13
+ json (2.6.2)
14
+ jwt (2.5.0)
15
+ mustermann (2.0.2)
16
+ ruby2_keywords (~> 0.0.1)
17
+ parallel (1.22.1)
18
+ parser (3.1.2.1)
19
+ ast (~> 2.4.1)
20
+ rack (2.2.4)
21
+ rack-protection (2.2.2)
22
+ rack
23
+ rack-test (2.0.2)
24
+ rack (>= 1.3)
25
+ rainbow (3.1.1)
26
+ rake (13.0.6)
27
+ rbs (2.1.0)
28
+ regexp_parser (2.6.0)
29
+ rexml (3.2.5)
30
+ rspec (3.11.0)
31
+ rspec-core (~> 3.11.0)
32
+ rspec-expectations (~> 3.11.0)
33
+ rspec-mocks (~> 3.11.0)
34
+ rspec-core (3.11.0)
35
+ rspec-support (~> 3.11.0)
36
+ rspec-expectations (3.11.1)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.11.0)
39
+ rspec-mocks (3.11.1)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.11.0)
42
+ rspec-support (3.11.1)
43
+ rubocop (1.36.0)
44
+ json (~> 2.3)
45
+ parallel (~> 1.10)
46
+ parser (>= 3.1.2.1)
47
+ rainbow (>= 2.2.2, < 4.0)
48
+ regexp_parser (>= 1.8, < 3.0)
49
+ rexml (>= 3.2.5, < 4.0)
50
+ rubocop-ast (>= 1.20.1, < 2.0)
51
+ ruby-progressbar (~> 1.7)
52
+ unicode-display_width (>= 1.4.0, < 3.0)
53
+ rubocop-ast (1.21.0)
54
+ parser (>= 3.1.1.0)
55
+ ruby-progressbar (1.11.0)
56
+ ruby2_keywords (0.0.5)
57
+ sinatra (2.2.2)
58
+ mustermann (~> 2.0)
59
+ rack (~> 2.2)
60
+ rack-protection (= 2.2.2)
61
+ tilt (~> 2.0)
62
+ tilt (2.0.11)
63
+ unicode-display_width (2.3.0)
64
+
65
+ PLATFORMS
66
+ x86_64-linux
67
+
68
+ DEPENDENCIES
69
+ rack-test
70
+ rake (~> 13.0)
71
+ rbs
72
+ rspec (~> 3.0)
73
+ rubocop (~> 1.21)
74
+ sinatra-jwt!
75
+
76
+ BUNDLED WITH
77
+ 2.3.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Suddani
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # Sinatra::Jwt
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sinatra/jwt`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Install the gem and add to the application's Gemfile by executing:
10
+
11
+ $ bundle add sinatra-jwt
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ $ gem install sinatra-jwt
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ require "sinatra-jwt"
21
+
22
+ class Application < Sinatra::Base
23
+ register Sinatra::Jwt
24
+ end
25
+ ```
26
+
27
+ ## Use a single key
28
+ If you wish to use a single key you can provide it directly
29
+ ```ruby
30
+ require "sinatra-jwt"
31
+
32
+ class Application < Sinatra::Base
33
+ register Sinatra::Jwt
34
+
35
+ jwt_auth "superSecretKey", "HS512"
36
+ end
37
+ ```
38
+
39
+ ## Use a JWK
40
+ By default the extension will try to load a file called `jwk.json` from the current path containing the keys if `jwt_auth` was not called with a key.
41
+
42
+ You should keep in mind that if you are using jwk the header portion of the jwt **has to contain the key id**:
43
+ ```json
44
+ {
45
+ ...
46
+ "kid": "lol"
47
+ }
48
+ ```
49
+ An example JWT:
50
+
51
+ `eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImxvbCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwicmlnaHRzIjpbInJlYWRfYXBpIl19.RxOADRpmII2n14Och-34c0w-3NgB74kRi_XgERkO4joiDKYesSZnopL_yMtzVqdkHtLGS_SlULQjudEeQ7q9eg`
52
+
53
+ that contains the following data:
54
+
55
+ **HEADER**
56
+ ```json
57
+ {
58
+ "alg": "HS512",
59
+ "typ": "JWT",
60
+ "kid": "lol"
61
+ }
62
+ ```
63
+
64
+ **PAYLOAD**
65
+ ```json
66
+ {
67
+ "sub": "1234567890",
68
+ "name": "John Doe",
69
+ "admin": true,
70
+ "iat": 1516239022,
71
+ "rights": ["read_api"]
72
+ }
73
+ ```
74
+
75
+ An example jwk file containing the key could look like this:
76
+ ```json
77
+ {
78
+ "keys": [
79
+ {
80
+ "kid": "lol",
81
+ "k": "superSecretKey",
82
+ "kty": "oct"
83
+ }
84
+ ]
85
+ }
86
+ ```
87
+ The file can contain as many keys as you want all with different algorithms.
88
+
89
+ ### Configure JWK loading
90
+
91
+
92
+ #### Files
93
+ You can change the file that is loaded by either hardcoding the path
94
+ ```ruby
95
+ require "sinatra-jwt"
96
+
97
+ class Application < Sinatra::Base
98
+ register Sinatra::Jwt
99
+
100
+ jwk_file "/path/to/jwk/file.json"
101
+ end
102
+ ```
103
+ or using the env helper method that takes the path from the environment variables
104
+ ```ruby
105
+ require "sinatra-jwt"
106
+
107
+ class Application < Sinatra::Base
108
+ register Sinatra::Jwt
109
+
110
+ jwk_file_env "JWK"
111
+ # is equivalent to:
112
+ # jwk_file ENV["JWK"]
113
+ end
114
+ ```
115
+
116
+ #### Strings
117
+ You can change the file that is loaded by either hardcoding the path
118
+ ```ruby
119
+ require "sinatra-jwt"
120
+
121
+ class Application < Sinatra::Base
122
+ register Sinatra::Jwt
123
+
124
+ jwk_string '{"keys":[{"kid":"lol","k":"superSecretKey","kty":"oct"}]}'
125
+ end
126
+ ```
127
+ or using the env helper method that takes the path from the environment variables
128
+ ```ruby
129
+ require "sinatra-jwt"
130
+
131
+ class Application < Sinatra::Base
132
+ register Sinatra::Jwt
133
+
134
+ jwk_string_env "JWK"
135
+ # is equivalent to:
136
+ # jwk_string ENV["JWK"]
137
+ end
138
+ ```
139
+
140
+
141
+ ## Protect a route
142
+
143
+ ### Require a valid jwt
144
+
145
+ ```ruby
146
+ get "/protected", :auth => true do
147
+ "Hello world login"
148
+ end
149
+ ```
150
+
151
+ ### Allow hitting the next matching url
152
+
153
+ ```ruby
154
+ get "/protected", :auth => [{next: true}] do
155
+ "Hello world login"
156
+ end
157
+
158
+ get "/protected" do
159
+ "Hello world login"
160
+ end
161
+ ```
162
+
163
+ ### Require a valid jwt as well as specific payload
164
+
165
+ ```ruby
166
+ jwt_data_contains_diff Sinatra::Jwt::TopLevelKeyArrayDiff
167
+
168
+ get "/protected", :auth => [{contains: {rights: ["read_api"]}}] do
169
+ "Hello world login"
170
+ end
171
+ ```
172
+
173
+ ### TopLevelKeyArrayDiff
174
+ The TopLevelKeyArrayDiff only works on a simple top level and array diff level.
175
+
176
+ So only:
177
+ ```ruby
178
+ {
179
+ "SOMEKEY": "SOMEVALUE can be array, object, string, number etc"
180
+ }
181
+ ```
182
+ If the value is an array it can also detect if required attributes are in the array. For any other value type it will cause a missing rights error if the objects are not identical.
183
+
184
+ ## Custom Decoder
185
+ You can use a custom decoder by implementing an object that has a `decode` method following the signature of the jwt gem https://github.com/jwt/ruby-jwt
186
+
187
+ ```ruby
188
+ require "base64"
189
+ require "json"
190
+ require "sinatra-jwt"
191
+
192
+ class DummyDecoder
193
+ def self.decode(token, key = nil, verify = false, options = {})
194
+ raise Sinatra::Jwt::JwtDummyDecoderError, "DummyDecoder should not be used in production" if ENV["RACK_ENV"] != "development"
195
+ encoded_header, encoded_payload, signature = token.split(".")
196
+ [JSON.parse(Base64.decode64(encoded_payload)), JSON.parse(Base64.decode64(encoded_header))]
197
+ end
198
+ end
199
+
200
+ class Application < Sinatra::Base
201
+ register Sinatra::Jwt
202
+
203
+ jwt_decoder DummyDecoder
204
+
205
+ get "/protected", :auth => true do
206
+ puts jwt_payload
207
+ "Hello world login"
208
+ end
209
+ end
210
+ ```
211
+
212
+ This decoder is bundled with the extension but will cause `unauthorized calls in any other environment than development`
213
+
214
+ ```ruby
215
+ require "sinatra-jwt"
216
+
217
+ class Application < Sinatra::Base
218
+ register Sinatra::Jwt
219
+
220
+ jwt_decoder Sinatra::Jwt::DummyDecoder
221
+
222
+ get "/protected", :auth => true do
223
+ puts jwt_payload
224
+ "Hello world login"
225
+ end
226
+ end
227
+ ```
228
+
229
+ ## Development
230
+
231
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
232
+
233
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
234
+
235
+ ## Contributing
236
+
237
+ Bug reports and pull requests are welcome on GitHub at https://github.com/suddani/sinatra-jwt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/suddani/sinatra-jwt/blob/master/CODE_OF_CONDUCT.md).
238
+
239
+ ## License
240
+
241
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
242
+
243
+ ## Code of Conduct
244
+
245
+ Everyone interacting in the Sinatra::Jwt project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/suddani/sinatra-jwt/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ class DummyDecoder
6
+ # rubocop:disable Style/OptionalBooleanParameter
7
+ def self.decode(token, _key = nil, _verify = false, _options = {})
8
+ raise JwtDummyDecoderError, "DummyDecoder should not be used in production" if ENV["RACK_ENV"] != "development"
9
+
10
+ encoded_header, encoded_payload, _signature = token.split(".")
11
+ [JSON.parse(Base64.decode64(encoded_payload)), JSON.parse(Base64.decode64(encoded_header))]
12
+ rescue StandardError
13
+ raise JwtDecodingError, "Decoding error"
14
+ end
15
+ # rubocop:enable Style/OptionalBooleanParameter
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ class DummyHashDiff
6
+ def self.added_attr_or_appended?(_request_hash, _required_hash)
7
+ raise JwtRequiredDataError, "No diffing implemented"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ module Helpers
6
+ def authorization_token_string
7
+ @authorization_token_string ||= request.env["HTTP_AUTHORIZATION"]
8
+ end
9
+
10
+ def authorization_token
11
+ @authorization_token ||= authorization_token_string.split("Bearer ").last
12
+ rescue StandardError
13
+ raise JwtMissingError, "Missing JWT"
14
+ end
15
+
16
+ def jwt_decode_options
17
+ if settings.jwt_auth_key.nil?
18
+ {
19
+ algorithms: settings.jwt_auth_allowed_algorithms,
20
+ jwks: settings.jwt_auth_jwk_loader
21
+ }
22
+ else
23
+ {
24
+ algorithm: settings.jwt_auth_algorithm
25
+ }
26
+ end
27
+ end
28
+
29
+ def jwt
30
+ @jwt ||= settings.jwt_auth_decoder.decode(
31
+ authorization_token, settings.jwt_auth_key, true, jwt_decode_options
32
+ )
33
+ end
34
+
35
+ def jwt_payload
36
+ jwt.first
37
+ end
38
+
39
+ def jwt_header
40
+ jwt.last
41
+ end
42
+
43
+ def authorize!
44
+ jwt
45
+ rescue StandardError => e
46
+ halt 401, { status: "Unauthorized", message: e.message }.to_json
47
+ end
48
+
49
+ def authorize
50
+ jwt
51
+ rescue StandardError => e
52
+ logger&.info({ status: "Unauthorized", message: e.message }.to_json)
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ module JwkLoader
6
+ class Loader
7
+ def load
8
+ raise StandardError, "Jwk load is not implemented"
9
+ end
10
+
11
+ def call(options = {})
12
+ if options[:kid_not_found] && @cache_last_update < Time.now.to_i - 300
13
+ logger.info("Invalidating JWK cache. #{options[:kid]} not found from previous cache")
14
+ @cached_keys = nil
15
+ end
16
+ @cached_keys ||= begin
17
+ @cache_last_update = Time.now.to_i
18
+ load
19
+ end
20
+ rescue StandardError
21
+ raise JwkLoadError, "Could not load the jwk file"
22
+ end
23
+ end
24
+
25
+ class File < Loader
26
+ attr_reader :path
27
+
28
+ def initialize(path = nil)
29
+ @path = path || "jwk.json"
30
+ super()
31
+ end
32
+
33
+ def load
34
+ JSON.parse(::File.read(path))
35
+ end
36
+ end
37
+
38
+ class String < Loader
39
+ attr_reader :content
40
+
41
+ def initialize(content = nil)
42
+ @content = content || "{}"
43
+ super()
44
+ end
45
+
46
+ def load
47
+ JSON.parse(content)
48
+ end
49
+ end
50
+
51
+ class EnvString < String
52
+ def initialize(name = nil)
53
+ super(ENV.fetch(name, nil))
54
+ end
55
+ end
56
+
57
+ class EnvFile < File
58
+ def initialize(name = nil)
59
+ super(ENV.fetch(name, nil))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ class TopLevelKeyArrayDiff
6
+ def self.added_attr_or_appended?(request_hash, required_hash)
7
+ return false if request_hash == required_hash
8
+
9
+ required_hash.each do |key, value|
10
+ next if request_hash[key] == value
11
+ return true if request_hash[key].nil?
12
+
13
+ return true unless (value - request_hash[key]).empty?
14
+ end
15
+ false
16
+ rescue StandardError
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinatra
4
+ module Jwt
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sinatra/base"
4
+ require "jwt"
5
+
6
+ require_relative "jwt/dummy_decoder"
7
+ require_relative "jwt/dummy_hash_diff"
8
+ require_relative "jwt/helpers"
9
+ require_relative "jwt/jwk_loader"
10
+ require_relative "jwt/top_level_key_array_diff"
11
+ require_relative "jwt/version"
12
+
13
+ module Sinatra
14
+ module Jwt
15
+ class JwtRequiredDataError < StandardError; end
16
+ class JwtDummyDecoderError < StandardError; end
17
+ class JwtDecodingError < StandardError; end
18
+ class JwtMissingError < StandardError; end
19
+ class JwkLoadError < StandardError; end
20
+
21
+ def jwk_file(path = nil)
22
+ set :jwt_auth_jwk_loader, JwkLoader::File.new(path)
23
+ set :jwt_auth_key, nil
24
+ end
25
+
26
+ def jwk_string(content)
27
+ set :jwt_auth_jwk_loader, JwkLoader::String.new(content)
28
+ set :jwt_auth_key, nil
29
+ end
30
+
31
+ def jwk_file_env(name)
32
+ set :jwt_auth_jwk_loader, JwkLoader::EnvFile.new(name)
33
+ set :jwt_auth_key, nil
34
+ end
35
+
36
+ def jwk_string_env(name)
37
+ set :jwt_auth_jwk_loader, JwkLoader::EnvString.new(name)
38
+ set :jwt_auth_key, nil
39
+ end
40
+
41
+ def jwt_data_contains_diff(differ)
42
+ set :jwt_auth_auth_diff, differ
43
+ end
44
+
45
+ def jwt_auth(key, algorithm = "HS512")
46
+ set :jwt_auth_key, key
47
+ set :jwt_auth_algorithm, algorithm
48
+ end
49
+
50
+ def jwt_decoder(decoder)
51
+ set :jwt_auth_decoder, decoder
52
+ end
53
+
54
+ def self.added_attr_or_appended?(diff)
55
+ return true unless diff[:added_attr].nil?
56
+ return true unless diff[:appended].nil?
57
+
58
+ diff.each do |_k, v|
59
+ return true if v.is_a?(Hash) && added_attr_or_appended?(v)
60
+ end
61
+ false
62
+ end
63
+
64
+ # rubocop:disable Metrics/AbcSize
65
+ # rubocop:disable Metrics/CyclomaticComplexity
66
+ # rubocop:disable Metrics/MethodLength
67
+ # rubocop:disable Metrics/PerceivedComplexity
68
+ def self.registered(app)
69
+ app.helpers Helpers
70
+
71
+ app.set :jwt_auth_decoder, JWT
72
+ app.set :jwt_auth_key, nil
73
+ app.set :jwt_auth_algorithm, "HS512"
74
+ app.set :jwt_auth_allowed_algorithms, %w[HS512 RS512]
75
+ app.set :jwt_auth_allowed_algorithms, %w[HS512 RS512]
76
+ app.set :jwt_auth_jwk_loader, JwkLoader::File.new
77
+ app.set :jwt_auth_auth_diff, DummyHashDiff
78
+
79
+ app.set(:auth) do |options_data|
80
+ condition do
81
+ options = options_data.is_a?(Hash) ? options_data : {}
82
+ should_stop = !options.key?(:next) || !options[:next]
83
+ decoded_key = if should_stop
84
+ authorize!
85
+ else
86
+ authorize
87
+ end
88
+ if decoded_key
89
+ if options.key?(:contains)
90
+ added_keys = settings.jwt_auth_auth_diff.added_attr_or_appended?(
91
+ decoded_key.first,
92
+ JSON.parse(options[:contains].to_json)
93
+ )
94
+ if should_stop && added_keys
95
+ halt 401, { status: "Unauthorized", message: "Missing rights" }.to_json
96
+ elsif added_keys
97
+ false
98
+ end
99
+ end
100
+ else
101
+ false
102
+ end
103
+ end
104
+ end
105
+
106
+ app.error JwtRequiredDataError, JwtMissingError do |e|
107
+ halt 401, { status: "Unauthorized", message: e.message }.to_json
108
+ end
109
+ end
110
+ # rubocop:enable Metrics/AbcSize
111
+ # rubocop:enable Metrics/CyclomaticComplexity
112
+ # rubocop:enable Metrics/MethodLength
113
+ # rubocop:enable Metrics/PerceivedComplexity
114
+ end
115
+ end
@@ -0,0 +1,6 @@
1
+ module Sinatra
2
+ module Jwt
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-jwt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Sudmann
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jwt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ description:
42
+ email:
43
+ - suddani@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rspec"
49
+ - ".rubocop.yml"
50
+ - CHANGELOG.md
51
+ - CODE_OF_CONDUCT.md
52
+ - Gemfile
53
+ - Gemfile.lock
54
+ - LICENSE.txt
55
+ - README.md
56
+ - Rakefile
57
+ - lib/sinatra/jwt.rb
58
+ - lib/sinatra/jwt/dummy_decoder.rb
59
+ - lib/sinatra/jwt/dummy_hash_diff.rb
60
+ - lib/sinatra/jwt/helpers.rb
61
+ - lib/sinatra/jwt/jwk_loader.rb
62
+ - lib/sinatra/jwt/top_level_key_array_diff.rb
63
+ - lib/sinatra/jwt/version.rb
64
+ - sig/sinatra/jwt.rbs
65
+ homepage: https://github.com/suddani/sinatra-jwt
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ allowed_push_host: https://rubygems.org/
70
+ homepage_uri: https://github.com/suddani/sinatra-jwt
71
+ source_code_uri: https://github.com/suddani/sinatra-jwt
72
+ changelog_uri: https://github.com/suddani/sinatra-jwt/blob/master/CHANGELOG.md
73
+ rubygems_mfa_required: 'true'
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.6.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.3.7
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Simple package to handle jwt auth in Sinatra
93
+ test_files: []