api_guard_grape 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e667ff860411d0279873e75ec9a26d91021d77cc39b8fd4ff71b80064a9381c
|
|
4
|
+
data.tar.gz: bf6ce23224222985aff4c0ccfd1457b3983c26860611a4ffdc1dee67e300f6b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78be63e439d78752712a6aa7f1473c7af2baeed3e85a855caaed6d3816b3ac29afe51bcf7da3a0e0ab16aeffe59863abc9880aed0fb4496a79a2fe6c9cea7746
|
|
7
|
+
data.tar.gz: 07b3f3e2d4b4752b00892baab81720075e6b409dfbd7894e87b202c57b9fdb1dc303033d599885948fb2df36b232bf6b7953c535c36c575f234fd461e90c71d2
|
|
@@ -5,7 +5,7 @@ module ApiGuard
|
|
|
5
5
|
# Common module for API authentication
|
|
6
6
|
module Authentication
|
|
7
7
|
# Handle authentication of the resource dynamically
|
|
8
|
-
def method_missing(name, *args)
|
|
8
|
+
def self.method_missing(name, *args)
|
|
9
9
|
method_name = name.to_s
|
|
10
10
|
|
|
11
11
|
if method_name.start_with?('authenticate_and_set_')
|
|
@@ -16,12 +16,12 @@ module ApiGuard
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def respond_to_missing?(method_name, include_private = false)
|
|
19
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
20
20
|
method_name.to_s.start_with?('authenticate_and_set_') || super
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Authenticate the JWT token and set resource
|
|
24
|
-
def authenticate_and_set_resource(resource_name)
|
|
24
|
+
def self.authenticate_and_set_resource(resource_name)
|
|
25
25
|
@resource_name = resource_name
|
|
26
26
|
|
|
27
27
|
@token = request.headers['Authorization']&.split('Bearer ')&.last
|
|
@@ -41,7 +41,7 @@ module ApiGuard
|
|
|
41
41
|
|
|
42
42
|
# Decode the JWT token
|
|
43
43
|
# and don't verify token expiry for refresh token API request
|
|
44
|
-
def decode_token
|
|
44
|
+
def self.decode_token
|
|
45
45
|
# TODO: Set token refresh controller dynamic
|
|
46
46
|
verify_token = (controller_name != 'tokens' || action_name != 'create')
|
|
47
47
|
@decoded_token = decode(@token, verify_token)
|
|
@@ -49,7 +49,7 @@ module ApiGuard
|
|
|
49
49
|
|
|
50
50
|
# Returns whether the JWT token is issued after the last password change
|
|
51
51
|
# Returns true if password hasn't changed by the user
|
|
52
|
-
def valid_issued_at?(resource)
|
|
52
|
+
def self.valid_issued_at?(resource)
|
|
53
53
|
return true unless ApiGuard.invalidate_old_tokens_on_password_change
|
|
54
54
|
|
|
55
55
|
!resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i
|
|
@@ -57,7 +57,7 @@ module ApiGuard
|
|
|
57
57
|
|
|
58
58
|
# Defines "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
59
59
|
# that returns "resource" value
|
|
60
|
-
def define_current_resource_accessors(resource)
|
|
60
|
+
def self.define_current_resource_accessors(resource)
|
|
61
61
|
define_singleton_method("current_#{@resource_name}") do
|
|
62
62
|
instance_variable_get("@current_#{@resource_name}") ||
|
|
63
63
|
instance_variable_set("@current_#{@resource_name}", resource)
|
|
@@ -69,7 +69,7 @@ module ApiGuard
|
|
|
69
69
|
#
|
|
70
70
|
# Also, set "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
71
71
|
# for accessing the authenticated resource
|
|
72
|
-
def authenticate_token
|
|
72
|
+
def self.authenticate_token
|
|
73
73
|
return unless decode_token
|
|
74
74
|
|
|
75
75
|
resource = find_resource_from_token(@resource_name.classify.constantize)
|
|
@@ -81,14 +81,14 @@ module ApiGuard
|
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
def find_resource_from_token(resource_class)
|
|
84
|
+
def self.find_resource_from_token(resource_class)
|
|
85
85
|
resource_id = @decoded_token[:"#{@resource_name}_id"]
|
|
86
86
|
return if resource_id.blank?
|
|
87
87
|
|
|
88
88
|
resource_class.find_by(id: resource_id)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
def current_resource
|
|
91
|
+
def self.current_resource
|
|
92
92
|
return unless respond_to?("current_#{@resource_name}")
|
|
93
93
|
|
|
94
94
|
public_send("current_#{@resource_name}")
|
|
@@ -4,28 +4,28 @@ module ApiGuard
|
|
|
4
4
|
module JwtAuth
|
|
5
5
|
# Common module for token blacklisting functionality
|
|
6
6
|
module BlacklistToken
|
|
7
|
-
def blacklisted_token_association(resource)
|
|
7
|
+
def self.blacklisted_token_association(resource)
|
|
8
8
|
resource.class.blacklisted_token_association
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def token_blacklisting_enabled?(resource)
|
|
11
|
+
def self.token_blacklisting_enabled?(resource)
|
|
12
12
|
blacklisted_token_association(resource).present?
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def blacklisted_tokens_for(resource)
|
|
15
|
+
def self.blacklisted_tokens_for(resource)
|
|
16
16
|
blacklisted_token_association = blacklisted_token_association(resource)
|
|
17
17
|
resource.send(blacklisted_token_association)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# Returns whether the JWT token is blacklisted or not
|
|
21
|
-
def blacklisted?(resource)
|
|
21
|
+
def self.blacklisted?(resource)
|
|
22
22
|
return false unless token_blacklisting_enabled?(resource)
|
|
23
23
|
|
|
24
24
|
blacklisted_tokens_for(resource).exists?(token: @token)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Blacklist the current JWT token from future access
|
|
28
|
-
def blacklist_token
|
|
28
|
+
def self.blacklist_token
|
|
29
29
|
return unless token_blacklisting_enabled?(current_resource)
|
|
30
30
|
|
|
31
31
|
blacklisted_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc)
|
|
@@ -6,25 +6,25 @@ module ApiGuard
|
|
|
6
6
|
module JwtAuth
|
|
7
7
|
# Common module for JWT operations
|
|
8
8
|
module JsonWebToken
|
|
9
|
-
def current_time
|
|
9
|
+
def self.current_time
|
|
10
10
|
@current_time ||= Time.now.utc
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def token_expire_at
|
|
13
|
+
def self.token_expire_at
|
|
14
14
|
@token_expire_at ||= (current_time + ApiGuard.token_validity).to_i
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def token_issued_at
|
|
17
|
+
def self.token_issued_at
|
|
18
18
|
@token_issued_at ||= current_time.to_i
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# Encode the payload with the secret key and return the JWT token
|
|
22
|
-
def encode(payload)
|
|
22
|
+
def self.encode(payload)
|
|
23
23
|
JWT.encode(payload, ApiGuard.token_signing_secret)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Decode the JWT token and return the payload
|
|
27
|
-
def decode(token, verify = true)
|
|
27
|
+
def self.decode(token, verify = true)
|
|
28
28
|
HashWithIndifferentAccess.new(
|
|
29
29
|
JWT.decode(token, ApiGuard.token_signing_secret, verify, verify_iat: true)[0]
|
|
30
30
|
)
|
|
@@ -34,7 +34,7 @@ module ApiGuard
|
|
|
34
34
|
# Also, create refresh token if enabled for the resource.
|
|
35
35
|
#
|
|
36
36
|
# This creates expired JWT token if the argument 'expired_token' is true which can be used for testing.
|
|
37
|
-
def jwt_and_refresh_token(resource, resource_name, expired_token = false)
|
|
37
|
+
def self.jwt_and_refresh_token(resource, resource_name, expired_token = false)
|
|
38
38
|
payload = {
|
|
39
39
|
"#{resource_name}_id": resource.id,
|
|
40
40
|
exp: expired_token ? token_issued_at : token_expire_at,
|
|
@@ -48,13 +48,13 @@ module ApiGuard
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Create tokens and set response headers
|
|
51
|
-
def create_token_and_set_header(resource, resource_name)
|
|
51
|
+
def self.create_token_and_set_header(resource, resource_name)
|
|
52
52
|
access_token, refresh_token = jwt_and_refresh_token(resource, resource_name)
|
|
53
53
|
set_token_headers(access_token, refresh_token)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
# Set token details in response headers
|
|
57
|
-
def set_token_headers(token, refresh_token = nil)
|
|
57
|
+
def self.set_token_headers(token, refresh_token = nil)
|
|
58
58
|
response.headers['Access-Token'] = token
|
|
59
59
|
response.headers['Refresh-Token'] = refresh_token if refresh_token
|
|
60
60
|
response.headers['Expire-At'] = token_expire_at.to_s
|
|
@@ -62,11 +62,173 @@ module ApiGuard
|
|
|
62
62
|
|
|
63
63
|
# Set token issued at to current timestamp
|
|
64
64
|
# to restrict access to old access(JWT) tokens
|
|
65
|
-
def invalidate_old_jwt_tokens(resource)
|
|
65
|
+
def self.invalidate_old_jwt_tokens(resource)
|
|
66
66
|
return unless ApiGuard.invalidate_old_tokens_on_password_change
|
|
67
67
|
|
|
68
68
|
resource.token_issued_at = Time.at(token_issued_at).utc
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
# =================================REFRESH JWT TOKEN================
|
|
72
|
+
|
|
73
|
+
def self.refresh_token_association(resource)
|
|
74
|
+
resource.class.refresh_token_association
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.refresh_token_enabled?(resource)
|
|
78
|
+
refresh_token_association(resource).present?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.refresh_tokens_for(resource)
|
|
82
|
+
refresh_token_association = refresh_token_association(resource)
|
|
83
|
+
resource.send(refresh_token_association)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.find_refresh_token_of(resource, refresh_token)
|
|
87
|
+
refresh_tokens_for(resource).find_by_token(refresh_token)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Generate and return unique refresh token for the resource
|
|
91
|
+
def self.uniq_refresh_token(resource)
|
|
92
|
+
loop do
|
|
93
|
+
random_token = SecureRandom.urlsafe_base64
|
|
94
|
+
return random_token unless refresh_tokens_for(resource).exists?(token: random_token)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Create a new refresh_token for the current resource
|
|
99
|
+
def self.new_refresh_token(resource)
|
|
100
|
+
return unless refresh_token_enabled?(resource)
|
|
101
|
+
|
|
102
|
+
refresh_tokens_for(resource).create(token: uniq_refresh_token(resource)).token
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.destroy_all_refresh_tokens(resource)
|
|
106
|
+
return unless refresh_token_enabled?(resource)
|
|
107
|
+
|
|
108
|
+
refresh_tokens_for(resource).destroy_all
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# =================================BLACKLIST JWT TOKEN================
|
|
112
|
+
|
|
113
|
+
def self.blacklisted_token_association(resource)
|
|
114
|
+
resource.class.blacklisted_token_association
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def self.token_blacklisting_enabled?(resource)
|
|
118
|
+
blacklisted_token_association(resource).present?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.blacklisted_tokens_for(resource)
|
|
122
|
+
blacklisted_token_association = blacklisted_token_association(resource)
|
|
123
|
+
resource.send(blacklisted_token_association)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns whether the JWT token is blacklisted or not
|
|
127
|
+
def self.blacklisted?(resource)
|
|
128
|
+
return false unless token_blacklisting_enabled?(resource)
|
|
129
|
+
|
|
130
|
+
blacklisted_tokens_for(resource).exists?(token: @token)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Blacklist the current JWT token from future access
|
|
134
|
+
def self.blacklist_token
|
|
135
|
+
return unless token_blacklisting_enabled?(current_resource)
|
|
136
|
+
|
|
137
|
+
blacklisted_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# =================================AUTHENTICATION JWT TOKEN================
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def self.method_missing(name, *args)
|
|
144
|
+
method_name = name.to_s
|
|
145
|
+
|
|
146
|
+
if method_name.start_with?('authenticate_and_set_')
|
|
147
|
+
resource_name = method_name.split('authenticate_and_set_')[1]
|
|
148
|
+
authenticate_and_set_resource(resource_name)
|
|
149
|
+
else
|
|
150
|
+
super
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
155
|
+
method_name.to_s.start_with?('authenticate_and_set_') || super
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Authenticate the JWT token and set resource
|
|
159
|
+
def self.authenticate_and_set_resource(resource_name)
|
|
160
|
+
@resource_name = resource_name
|
|
161
|
+
|
|
162
|
+
@token = request.headers['Authorization']&.split('Bearer ')&.last
|
|
163
|
+
return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token
|
|
164
|
+
|
|
165
|
+
authenticate_token
|
|
166
|
+
|
|
167
|
+
# Render error response only if no resource found and no previous render happened
|
|
168
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed?
|
|
169
|
+
rescue JWT::DecodeError => e
|
|
170
|
+
if e.message == 'Signature has expired'
|
|
171
|
+
render_error(401, message: I18n.t('api_guard.access_token.expired'))
|
|
172
|
+
else
|
|
173
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Decode the JWT token
|
|
178
|
+
# and don't verify token expiry for refresh token API request
|
|
179
|
+
def self.decode_token
|
|
180
|
+
# TODO: Set token refresh controller dynamic
|
|
181
|
+
verify_token = (controller_name != 'tokens' || action_name != 'create')
|
|
182
|
+
@decoded_token = decode(@token, verify_token)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Returns whether the JWT token is issued after the last password change
|
|
186
|
+
# Returns true if password hasn't changed by the user
|
|
187
|
+
def self.valid_issued_at?(resource)
|
|
188
|
+
return true unless ApiGuard.invalidate_old_tokens_on_password_change
|
|
189
|
+
|
|
190
|
+
!resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Defines "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
194
|
+
# that returns "resource" value
|
|
195
|
+
def self.define_current_resource_accessors(resource)
|
|
196
|
+
define_singleton_method("current_#{@resource_name}") do
|
|
197
|
+
instance_variable_get("@current_#{@resource_name}") ||
|
|
198
|
+
instance_variable_set("@current_#{@resource_name}", resource)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Authenticate the resource with the '{{resource_name}}_id' in the decoded JWT token
|
|
203
|
+
# and also, check for valid issued at time and not blacklisted
|
|
204
|
+
#
|
|
205
|
+
# Also, set "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
206
|
+
# for accessing the authenticated resource
|
|
207
|
+
def self.authenticate_token
|
|
208
|
+
return unless decode_token
|
|
209
|
+
|
|
210
|
+
resource = find_resource_from_token(@resource_name.classify.constantize)
|
|
211
|
+
|
|
212
|
+
if resource && valid_issued_at?(resource) && !blacklisted?(resource)
|
|
213
|
+
define_current_resource_accessors(resource)
|
|
214
|
+
else
|
|
215
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.find_resource_from_token(resource_class)
|
|
220
|
+
resource_id = @decoded_token[:"#{@resource_name}_id"]
|
|
221
|
+
return if resource_id.blank?
|
|
222
|
+
|
|
223
|
+
resource_class.find_by(id: resource_id)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def self.current_resource
|
|
227
|
+
return unless respond_to?("current_#{@resource_name}")
|
|
228
|
+
|
|
229
|
+
public_send("current_#{@resource_name}")
|
|
230
|
+
end
|
|
231
|
+
|
|
70
232
|
end
|
|
71
233
|
end
|
|
72
234
|
end
|
|
@@ -4,25 +4,25 @@ module ApiGuard
|
|
|
4
4
|
module JwtAuth
|
|
5
5
|
# Common module for refresh token functionality
|
|
6
6
|
module RefreshJwtToken
|
|
7
|
-
def refresh_token_association(resource)
|
|
7
|
+
def self.refresh_token_association(resource)
|
|
8
8
|
resource.class.refresh_token_association
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def refresh_token_enabled?(resource)
|
|
11
|
+
def self.refresh_token_enabled?(resource)
|
|
12
12
|
refresh_token_association(resource).present?
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def refresh_tokens_for(resource)
|
|
15
|
+
def self.refresh_tokens_for(resource)
|
|
16
16
|
refresh_token_association = refresh_token_association(resource)
|
|
17
17
|
resource.send(refresh_token_association)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def find_refresh_token_of(resource, refresh_token)
|
|
20
|
+
def self.find_refresh_token_of(resource, refresh_token)
|
|
21
21
|
refresh_tokens_for(resource).find_by_token(refresh_token)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Generate and return unique refresh token for the resource
|
|
25
|
-
def uniq_refresh_token(resource)
|
|
25
|
+
def self.uniq_refresh_token(resource)
|
|
26
26
|
loop do
|
|
27
27
|
random_token = SecureRandom.urlsafe_base64
|
|
28
28
|
return random_token unless refresh_tokens_for(resource).exists?(token: random_token)
|
|
@@ -30,13 +30,13 @@ module ApiGuard
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Create a new refresh_token for the current resource
|
|
33
|
-
def new_refresh_token(resource)
|
|
33
|
+
def self.new_refresh_token(resource)
|
|
34
34
|
return unless refresh_token_enabled?(resource)
|
|
35
35
|
|
|
36
36
|
refresh_tokens_for(resource).create(token: uniq_refresh_token(resource)).token
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def destroy_all_refresh_tokens(resource)
|
|
39
|
+
def self.destroy_all_refresh_tokens(resource)
|
|
40
40
|
return unless refresh_token_enabled?(resource)
|
|
41
41
|
|
|
42
42
|
refresh_tokens_for(resource).destroy_all
|
data/lib/api_guard/version.rb
CHANGED