api_guard_grape 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fa6b87a02bcff0944fd185a949d84c63776d678928ca896fd66ea8b1ad4992a
4
- data.tar.gz: 29f4c5f87ca08a83ed96ec88f1b186ff9e76e0759b6748e1a603534e2ab16bd4
3
+ metadata.gz: 5e667ff860411d0279873e75ec9a26d91021d77cc39b8fd4ff71b80064a9381c
4
+ data.tar.gz: bf6ce23224222985aff4c0ccfd1457b3983c26860611a4ffdc1dee67e300f6b4
5
5
  SHA512:
6
- metadata.gz: 9981e401bbd55f0514320a1bf94aeba015f98c38c2b5a2d14cc10a745a487a52ccb134a4ce9043817fdc84c4974340a6bea9e682740c1ece82172ac9654cfae5
7
- data.tar.gz: 03efd2036116ee5e4fd46b08e7ee08635a702b08e2d5ff9d2d7c36745ede88a89df8197b0db5a39a36b99a63a831d08c283b59e45cca68acc114587024fb8bf8
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApiGuard
4
- VERSION = '0.5.1'
4
+ VERSION = '0.5.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_guard_grape
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Prateek Singh