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: 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