zaikio-jwt_auth 1.0.1 → 2.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.
- checksums.yaml +4 -4
- data/README.md +40 -1
- data/lib/zaikio/jwt_auth/configuration.rb +1 -1
- data/lib/zaikio/jwt_auth/directory_cache.rb +15 -9
- data/lib/zaikio/jwt_auth/version.rb +1 -1
- data/lib/zaikio/jwt_auth.rb +23 -6
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e030f7e4c7f0be8b37a722ff691b7bf1398cd31ca449b41a9923b51f5a008df8
|
4
|
+
data.tar.gz: 13a0d7a174af3ca58772b116e22d2fcc893291d59010ce127df82f1dca77ea5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c91befdc7f28018a2e19bcafdcbd805ef87467a6fde8fc1b4899b6c6a327a6a89327902f84bdd07f42749c265535cdc3b83bd6289e958fcd2b4d520e46462d94
|
7
|
+
data.tar.gz: aa56620ee2936346ef5b6d73ff0e8189ad8cc46d7ab44ca36783ad744f577513fb4786fa48023f4918491b04f166d9f0db5b66ef8b9f9a7d6ee04a315938bde7
|
data/README.md
CHANGED
@@ -28,7 +28,9 @@ $ gem install zaikio-jwt_auth
|
|
28
28
|
Zaikio::JWTAuth.configure do |config|
|
29
29
|
config.environment = :sandbox # or production
|
30
30
|
config.app_name = "test_app" # Your Zaikio App-Name
|
31
|
-
|
31
|
+
|
32
|
+
# Enable caching Hub API responses for e.g. revoked tokens
|
33
|
+
config.cache = Rails.cache
|
32
34
|
end
|
33
35
|
```
|
34
36
|
|
@@ -63,6 +65,24 @@ end
|
|
63
65
|
|
64
66
|
By convention, `authorize_by_jwt_scopes` automatically maps all CRUD actions in a controller. Requests for `show` and `index` with a read or read_write scope are allowed. All other actions like `create`, `update` and `destroy` are accepted if the scope is a write or read_write scope. Therefore it is strongly recommended to always create standard Rails resources. If a custom action is required, you will need to authorize yourself using the `after_jwt_auth`.
|
65
67
|
|
68
|
+
Both of these behaviours are automatically inherited by child classes, for example:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class API::ChildController < API::ResourcesController
|
72
|
+
end
|
73
|
+
|
74
|
+
API::ChildController.authorize_by_jwt_subject_type
|
75
|
+
#=> "Organization"
|
76
|
+
```
|
77
|
+
|
78
|
+
You can always override the behaviour in children if needed:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class API::ChildController < API::ResourcesController
|
82
|
+
authorize_by_jwt_subject_type nil
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
66
86
|
#### Modifying required scopes
|
67
87
|
If you nonetheless want to change the required scopes for CRUD routes, you can use the `type` option which accepts the following values: `:read`, `:write`, `:read_write`
|
68
88
|
|
@@ -165,6 +185,25 @@ rescue JWT::DecodeError, JWT::ExpiredSignature
|
|
165
185
|
end
|
166
186
|
```
|
167
187
|
|
188
|
+
### Using a different cache backend
|
189
|
+
|
190
|
+
This client supports any implementation of
|
191
|
+
[`ActiveSupport::Cache::Store`](https://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html),
|
192
|
+
but you can also write your own client that supports these methods: `#read(key)`,
|
193
|
+
`#write(key, value)`, `#delete(key)`
|
194
|
+
|
195
|
+
### Pass custom options to JWT auth
|
196
|
+
|
197
|
+
In some cases you want to add custom options to the JWT check. For example you want to allow expired JWTs when revoking access tokens.
|
198
|
+
|
199
|
+
```rb
|
200
|
+
class API::RevokedAccessTokensController < API::ApplicationController
|
201
|
+
def jwt_options
|
202
|
+
{ verify_expiration: false }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
168
207
|
## Contributing
|
169
208
|
|
170
209
|
**Make sure you have the dummy app running locally to validate your changes.**
|
@@ -16,7 +16,7 @@ module Zaikio
|
|
16
16
|
|
17
17
|
class << self
|
18
18
|
def fetch(directory_path, options = {})
|
19
|
-
cache = Zaikio::JWTAuth.configuration.
|
19
|
+
cache = Zaikio::JWTAuth.configuration.cache.read("zaikio::jwt_auth::#{directory_path}")
|
20
20
|
|
21
21
|
json = Oj.load(cache) if cache
|
22
22
|
|
@@ -31,14 +31,14 @@ module Zaikio
|
|
31
31
|
def update(directory_path, options = {})
|
32
32
|
data = fetch(directory_path, options)
|
33
33
|
data = yield(data)
|
34
|
-
Zaikio::JWTAuth.configuration.
|
34
|
+
Zaikio::JWTAuth.configuration.cache.write("zaikio::jwt_auth::#{directory_path}", {
|
35
35
|
fetched_at: Time.now.to_i,
|
36
36
|
data: data
|
37
37
|
}.to_json)
|
38
38
|
end
|
39
39
|
|
40
40
|
def reset(directory_path)
|
41
|
-
Zaikio::JWTAuth.configuration.
|
41
|
+
Zaikio::JWTAuth.configuration.cache.delete("zaikio::jwt_auth::#{directory_path}")
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -49,28 +49,34 @@ module Zaikio
|
|
49
49
|
|
50
50
|
def reload_or_enqueue(directory_path)
|
51
51
|
data = fetch_from_directory(directory_path)
|
52
|
-
Zaikio::JWTAuth.configuration.
|
52
|
+
Zaikio::JWTAuth.configuration.cache.write("zaikio::jwt_auth::#{directory_path}", {
|
53
53
|
fetched_at: Time.now.to_i,
|
54
54
|
data: data
|
55
55
|
}.to_json)
|
56
56
|
|
57
57
|
data
|
58
58
|
rescue Errno::ECONNREFUSED, Net::ReadTimeout, BadResponseError
|
59
|
-
Zaikio::JWTAuth.configuration.logger
|
59
|
+
Zaikio::JWTAuth.configuration.logger
|
60
|
+
.info("Error updating DirectoryCache(#{directory_path}), enqueueing job to update")
|
60
61
|
UpdateJob.set(wait: 10.seconds).perform_later(directory_path)
|
61
62
|
nil
|
62
63
|
end
|
63
64
|
|
64
65
|
def fetch_from_directory(directory_path)
|
65
|
-
|
66
|
-
|
67
|
-
http.use_ssl = uri.scheme == "https"
|
68
|
-
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
66
|
+
response = make_http_request(directory_path)
|
67
|
+
|
69
68
|
raise BadResponseError unless (200..299).cover?(response.code.to_i)
|
70
69
|
raise BadResponseError unless response["content-type"].to_s.include?("application/json")
|
71
70
|
|
72
71
|
Oj.load(response.body)
|
73
72
|
end
|
73
|
+
|
74
|
+
def make_http_request(directory_path)
|
75
|
+
uri = URI("#{Zaikio::JWTAuth.configuration.host}/#{directory_path}")
|
76
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
77
|
+
http.use_ssl = uri.scheme == "https"
|
78
|
+
http.request(Net::HTTP::Get.new(uri.request_uri))
|
79
|
+
end
|
74
80
|
end
|
75
81
|
end
|
76
82
|
end
|
data/lib/zaikio/jwt_auth.rb
CHANGED
@@ -45,7 +45,7 @@ module Zaikio
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def self.mocked_jwt_payload
|
48
|
-
@mocked_jwt_payload
|
48
|
+
instance_variable_defined?(:@mocked_jwt_payload) && @mocked_jwt_payload
|
49
49
|
end
|
50
50
|
|
51
51
|
def self.mocked_jwt_payload=(payload)
|
@@ -54,21 +54,27 @@ module Zaikio
|
|
54
54
|
|
55
55
|
HEADER_FORMAT = /\ABearer (.+)\z/.freeze
|
56
56
|
|
57
|
-
def self.extract(authorization_header_string)
|
57
|
+
def self.extract(authorization_header_string, **options)
|
58
58
|
return TokenData.new(Zaikio::JWTAuth.mocked_jwt_payload) if Zaikio::JWTAuth.mocked_jwt_payload
|
59
59
|
|
60
60
|
return if authorization_header_string.blank?
|
61
61
|
|
62
62
|
return unless (token = authorization_header_string[HEADER_FORMAT, 1])
|
63
63
|
|
64
|
-
|
64
|
+
options.reverse_merge!(algorithms: ["RS256"], jwks: JWK.loader)
|
65
|
+
|
66
|
+
payload, = JWT.decode(token, nil, true, **options)
|
65
67
|
|
66
68
|
TokenData.new(payload)
|
67
69
|
end
|
68
70
|
|
69
71
|
module ClassMethods
|
70
|
-
def authorize_by_jwt_subject_type(type =
|
71
|
-
|
72
|
+
def authorize_by_jwt_subject_type(type = :_not_given_)
|
73
|
+
if type != :_not_given_
|
74
|
+
@authorize_by_jwt_subject_type = type
|
75
|
+
elsif instance_variable_defined?(:@authorize_by_jwt_subject_type)
|
76
|
+
@authorize_by_jwt_subject_type
|
77
|
+
end
|
72
78
|
end
|
73
79
|
|
74
80
|
def authorize_by_jwt_scopes(scopes = nil, options = {})
|
@@ -78,11 +84,18 @@ module Zaikio
|
|
78
84
|
|
79
85
|
@authorize_by_jwt_scopes
|
80
86
|
end
|
87
|
+
|
88
|
+
def inherited(child)
|
89
|
+
super(child)
|
90
|
+
|
91
|
+
child.instance_variable_set(:@authorize_by_jwt_subject_type, @authorize_by_jwt_subject_type)
|
92
|
+
child.instance_variable_set(:@authorize_by_jwt_scopes, @authorize_by_jwt_scopes)
|
93
|
+
end
|
81
94
|
end
|
82
95
|
|
83
96
|
module InstanceMethods
|
84
97
|
def authenticate_by_jwt
|
85
|
-
token_data = Zaikio::JWTAuth.extract(request.headers["Authorization"])
|
98
|
+
token_data = Zaikio::JWTAuth.extract(request.headers["Authorization"], **jwt_options)
|
86
99
|
return render_error("no_jwt_passed", status: :unauthorized) unless token_data
|
87
100
|
|
88
101
|
return if show_error_if_token_is_revoked(token_data)
|
@@ -139,6 +152,10 @@ module Zaikio
|
|
139
152
|
def render_error(error, status: :forbidden)
|
140
153
|
render(status: status, json: { "errors" => [error] })
|
141
154
|
end
|
155
|
+
|
156
|
+
def jwt_options
|
157
|
+
{}
|
158
|
+
end
|
142
159
|
end
|
143
160
|
end
|
144
161
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zaikio-jwt_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- crispymtn
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2022-08-02 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activejob
|
@@ -113,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
113
|
- !ruby/object:Gem::Version
|
114
114
|
version: '0'
|
115
115
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
116
|
+
rubygems_version: 3.3.11
|
117
117
|
signing_key:
|
118
118
|
specification_version: 4
|
119
119
|
summary: JWT-Based authentication and authorization with zaikio
|