api_guard_grape 0.5.4 → 0.5.5

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -37
  3. data/Rakefile +1 -1
  4. data/app/controllers/{api_guard_grape → api_guard}/application_controller.rb +1 -1
  5. data/app/controllers/{api_guard_grape → api_guard}/authentication_controller.rb +6 -6
  6. data/app/controllers/{api_guard_grape → api_guard}/passwords_controller.rb +4 -4
  7. data/app/controllers/{api_guard_grape → api_guard}/registration_controller.rb +4 -4
  8. data/app/controllers/{api_guard_grape → api_guard}/tokens_controller.rb +6 -6
  9. data/config/locales/en.yml +1 -1
  10. data/config/routes.rb +2 -2
  11. data/lib/{api_guard_grape.rb → api_guard.rb} +9 -9
  12. data/lib/{api_guard_grape → api_guard}/app_secret_key.rb +1 -1
  13. data/lib/{api_guard_grape → api_guard}/engine.rb +4 -4
  14. data/lib/{api_guard_grape → api_guard}/jwt_auth/authentication.rb +16 -16
  15. data/lib/{api_guard_grape → api_guard}/jwt_auth/blacklist_token.rb +1 -1
  16. data/lib/api_guard/jwt_auth/json_web_token.rb +143 -0
  17. data/lib/{api_guard_grape/jwt_auth/json_web_token.rb → api_guard/jwt_auth/refresh_jwt_token.rb} +10 -107
  18. data/lib/api_guard/models/concerns.rb +27 -0
  19. data/lib/api_guard/modules.rb +26 -0
  20. data/lib/{api_guard_grape → api_guard}/resource_mapper.rb +3 -3
  21. data/lib/{api_guard_grape → api_guard}/response_formatters/renderer.rb +3 -3
  22. data/lib/{api_guard_grape → api_guard}/route_mapper.rb +10 -10
  23. data/lib/api_guard/test/controller_helper.rb +13 -0
  24. data/lib/api_guard/version.rb +5 -0
  25. data/lib/generators/{api_guard_grape → api_guard}/controllers/USAGE +1 -1
  26. data/lib/generators/{api_guard_grape → api_guard}/controllers/controllers_generator.rb +1 -1
  27. data/lib/generators/{api_guard_grape → api_guard}/controllers/templates/authentication_controller.rb +5 -5
  28. data/lib/generators/{api_guard_grape → api_guard}/controllers/templates/passwords_controller.rb +3 -3
  29. data/lib/generators/{api_guard_grape → api_guard}/controllers/templates/registration_controller.rb +3 -3
  30. data/lib/generators/{api_guard_grape → api_guard}/controllers/templates/tokens_controller.rb +5 -5
  31. data/lib/generators/{api_guard_grape → api_guard}/initializer/USAGE +2 -2
  32. data/lib/generators/{api_guard_grape → api_guard}/initializer/initializer_generator.rb +2 -2
  33. data/lib/generators/{api_guard_grape → api_guard}/initializer/templates/initializer.rb +1 -1
  34. metadata +30 -30
  35. data/lib/api_guard_grape/jwt_auth/refresh_jwt_token.rb +0 -46
  36. data/lib/api_guard_grape/models/concerns.rb +0 -27
  37. data/lib/api_guard_grape/modules.rb +0 -26
  38. data/lib/api_guard_grape/test/controller_helper.rb +0 -13
  39. data/lib/api_guard_grape/version.rb +0 -5
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApiGuardGrape
3
+ module ApiGuard
4
4
  class AppSecretKey
5
5
  def initialize(application)
6
6
  @application = application
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApiGuardGrape
3
+ module ApiGuard
4
4
  class Engine < ::Rails::Engine
5
- isolate_namespace ApiGuardGrape
5
+ isolate_namespace ApiGuard
6
6
 
7
7
  config.generators do |g|
8
8
  g.test_framework :rspec
@@ -10,8 +10,8 @@ module ApiGuardGrape
10
10
  end
11
11
 
12
12
  # Use 'secret_key_base' from Rails secrets if 'token_signing_secret' is not configured
13
- initializer 'ApiGuardGrape.token_signing_secret' do |app|
14
- ApiGuardGrape.token_signing_secret ||= ApiGuardGrape::AppSecretKey.new(app).detect
13
+ initializer 'ApiGuard.token_signing_secret' do |app|
14
+ ApiGuard.token_signing_secret ||= ApiGuard::AppSecretKey.new(app).detect
15
15
  end
16
16
  end
17
17
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApiGuardGrape
3
+ module ApiGuard
4
4
  module JwtAuth
5
5
  # Common module for API authentication
6
6
  module Authentication
7
7
  # Handle authentication of the resource dynamically
8
- def self.method_missing(name, *args)
8
+ def method_missing(name, *args)
9
9
  method_name = name.to_s
10
10
 
11
11
  if method_name.start_with?('authenticate_and_set_')
@@ -16,32 +16,32 @@ module ApiGuardGrape
16
16
  end
17
17
  end
18
18
 
19
- def self.respond_to_missing?(method_name, include_private = false)
19
+ def 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 self.authenticate_and_set_resource(resource_name)
24
+ def authenticate_and_set_resource(resource_name)
25
25
  @resource_name = resource_name
26
26
 
27
27
  @token = request.headers['Authorization']&.split('Bearer ')&.last
28
- return render_error(401, message: I18n.t('api_guard_grape.access_token.missing')) unless @token
28
+ return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token
29
29
 
30
30
  authenticate_token
31
31
 
32
32
  # Render error response only if no resource found and no previous render happened
33
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid')) if !current_resource && !performed?
33
+ render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed?
34
34
  rescue JWT::DecodeError => e
35
35
  if e.message == 'Signature has expired'
36
- render_error(401, message: I18n.t('api_guard_grape.access_token.expired'))
36
+ render_error(401, message: I18n.t('api_guard.access_token.expired'))
37
37
  else
38
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid'))
38
+ render_error(401, message: I18n.t('api_guard.access_token.invalid'))
39
39
  end
40
40
  end
41
41
 
42
42
  # Decode the JWT token
43
43
  # and don't verify token expiry for refresh token API request
44
- def self.decode_token
44
+ def 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,15 +49,15 @@ module ApiGuardGrape
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 self.valid_issued_at?(resource)
53
- return true unless ApiGuardGrape.invalidate_old_tokens_on_password_change
52
+ def valid_issued_at?(resource)
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
56
56
  end
57
57
 
58
58
  # Defines "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
59
59
  # that returns "resource" value
60
- def self.define_current_resource_accessors(resource)
60
+ def 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 ApiGuardGrape
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 self.authenticate_token
72
+ def authenticate_token
73
73
  return unless decode_token
74
74
 
75
75
  resource = find_resource_from_token(@resource_name.classify.constantize)
@@ -77,18 +77,18 @@ module ApiGuardGrape
77
77
  if resource && valid_issued_at?(resource) && !blacklisted?(resource)
78
78
  define_current_resource_accessors(resource)
79
79
  else
80
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid'))
80
+ render_error(401, message: I18n.t('api_guard.access_token.invalid'))
81
81
  end
82
82
  end
83
83
 
84
- def self.find_resource_from_token(resource_class)
84
+ def 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 self.current_resource
91
+ def current_resource
92
92
  return unless respond_to?("current_#{@resource_name}")
93
93
 
94
94
  public_send("current_#{@resource_name}")
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApiGuardGrape
3
+ module ApiGuard
4
4
  module JwtAuth
5
5
  # Common module for token blacklisting functionality
6
6
  module BlacklistToken
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'api_guard/jwt_auth/refresh_jwt_token'
5
+ module ApiGuard
6
+ module JwtAuth
7
+ # Common module for JWT operations
8
+ module JsonWebToken
9
+
10
+ def self.current_time
11
+ @current_time ||= Time.now.utc
12
+ end
13
+
14
+ def self.token_expire_at
15
+ @token_expire_at ||= (current_time + ApiGuard.token_validity).to_i
16
+ end
17
+
18
+ def self.token_issued_at
19
+ @token_issued_at ||= current_time.to_i
20
+ end
21
+
22
+ # Encode the payload with the secret key and return the JWT token
23
+ def self.encode(payload)
24
+ JWT.encode(payload, ApiGuard.token_signing_secret)
25
+ end
26
+
27
+ # Decode the JWT token and return the payload
28
+ def self.decode(token, verify = true)
29
+ HashWithIndifferentAccess.new(
30
+ JWT.decode(token, ApiGuard.token_signing_secret, verify, verify_iat: true)[0]
31
+ )
32
+ end
33
+
34
+ # Create a JWT token with resource detail in payload.
35
+ # Also, create refresh token if enabled for the resource.
36
+ #
37
+ # This creates expired JWT token if the argument 'expired_token' is true which can be used for testing.
38
+ def self.jwt_and_refresh_token(resource, resource_name, expired_token = false)
39
+ payload = {
40
+ "#{resource_name}_id": resource.id,
41
+ exp: expired_token ? token_issued_at : token_expire_at,
42
+ iat: token_issued_at
43
+ }
44
+
45
+ # Add custom data in the JWT token payload
46
+ payload.merge!(resource.jwt_token_payload) if resource.respond_to?(:jwt_token_payload)
47
+ [self.encode(payload), self.new_refresh_token(resource)]
48
+ end
49
+
50
+ # Create tokens and set response headers
51
+ def self.create_token_and_set_header(resource, resource_name)
52
+ access_token, refresh_token = jwt_and_refresh_token(resource, resource_name)
53
+ set_token_headers(access_token, refresh_token)
54
+ end
55
+
56
+ # Set token details in response headers
57
+ def self.set_token_headers(token, refresh_token = nil)
58
+ response.headers['Access-Token'] = token
59
+ response.headers['Refresh-Token'] = refresh_token if refresh_token
60
+ response.headers['Expire-At'] = token_expire_at.to_s
61
+ end
62
+
63
+ # Set token issued at to current timestamp
64
+ # to restrict access to old access(JWT) tokens
65
+ def self.invalidate_old_jwt_tokens(resource)
66
+ return unless ApiGuard.invalidate_old_tokens_on_password_change
67
+
68
+ resource.token_issued_at = Time.at(token_issued_at).utc
69
+ end
70
+
71
+ #refresh token code=======================================
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
+ # blacklisted ======================================================
112
+ def self.blacklisted_token_association(resource)
113
+ debugger
114
+ resource.class.blacklisted_token_association
115
+ end
116
+
117
+ def self.token_blacklisting_enabled?(resource)
118
+ debugger
119
+ blacklisted_token_association(resource).present?
120
+ end
121
+
122
+ def self.blacklisted_tokens_for(resource)
123
+ blacklisted_token_association = blacklisted_token_association(resource)
124
+ resource.send(blacklisted_token_association)
125
+ end
126
+
127
+ # Returns whether the JWT token is blacklisted or not
128
+ def self.blacklisted?(resource)
129
+ return false unless token_blacklisting_enabled?(resource)
130
+
131
+ blacklisted_tokens_for(resource).exists?(token: @token)
132
+ end
133
+
134
+ # Blacklist the current JWT token from future access
135
+ def self.blacklist_token
136
+
137
+ return unless token_blacklisting_enabled?(current_resource)
138
+ blacklisted_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc)
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -1,75 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jwt'
4
-
5
- module ApiGuardGrape
3
+ module ApiGuard
6
4
  module JwtAuth
7
- # Common module for JWT operations
8
- module JsonWebToken
9
- def self.current_time
10
- @current_time ||= Time.now.utc
11
- end
12
-
13
- def self.token_expire_at
14
- @token_expire_at ||= (current_time + ApiGuardGrape.token_validity).to_i
15
- end
16
-
17
- def self.token_issued_at
18
- @token_issued_at ||= current_time.to_i
19
- end
20
-
21
- # Encode the payload with the secret key and return the JWT token
22
- def self.encode(payload)
23
- JWT.encode(payload, ApiGuardGrape.token_signing_secret)
24
- end
25
-
26
- # Decode the JWT token and return the payload
27
- def self.decode(token, verify = true)
28
- HashWithIndifferentAccess.new(
29
- JWT.decode(token, ApiGuardGrape.token_signing_secret, verify, verify_iat: true)[0]
30
- )
31
- end
32
-
33
- # Create a JWT token with resource detail in payload.
34
- # Also, create refresh token if enabled for the resource.
35
- #
36
- # This creates expired JWT token if the argument 'expired_token' is true which can be used for testing.
37
- def self.jwt_and_refresh_token(resource, resource_name, expired_token = false)
38
- payload = {
39
- "#{resource_name}_id": resource.id,
40
- exp: expired_token ? token_issued_at : token_expire_at,
41
- iat: token_issued_at
42
- }
43
-
44
- # Add custom data in the JWT token payload
45
- payload.merge!(resource.jwt_token_payload) if resource.respond_to?(:jwt_token_payload)
46
-
47
- [encode(payload), new_refresh_token(resource)]
48
- end
49
-
50
- # Create tokens and set response headers
51
- def self.create_token_and_set_header(resource, resource_name)
52
- access_token, refresh_token = jwt_and_refresh_token(resource, resource_name)
53
- set_token_headers(access_token, refresh_token)
54
- end
55
-
56
- # Set token details in response headers
57
- def self.set_token_headers(token, refresh_token = nil)
58
- response.headers['Access-Token'] = token
59
- response.headers['Refresh-Token'] = refresh_token if refresh_token
60
- response.headers['Expire-At'] = token_expire_at.to_s
61
- end
62
-
63
- # Set token issued at to current timestamp
64
- # to restrict access to old access(JWT) tokens
65
- def self.invalidate_old_jwt_tokens(resource)
66
- return unless ApiGuardGrape.invalidate_old_tokens_on_password_change
67
-
68
- resource.token_issued_at = Time.at(token_issued_at).utc
69
- end
70
-
71
- # =================================REFRESH JWT TOKEN================
72
-
5
+ # Common module for refresh token functionality
6
+ module RefreshJwtToken
73
7
  def self.refresh_token_association(resource)
74
8
  resource.class.refresh_token_association
75
9
  end
@@ -108,37 +42,7 @@ module ApiGuardGrape
108
42
  refresh_tokens_for(resource).destroy_all
109
43
  end
110
44
 
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
-
45
+ # authenticat-----------
142
46
 
143
47
  def self.method_missing(name, *args)
144
48
  method_name = name.to_s
@@ -160,17 +64,17 @@ module ApiGuardGrape
160
64
  @resource_name = resource_name
161
65
 
162
66
  @token = request.headers['Authorization']&.split('Bearer ')&.last
163
- return render_error(401, message: I18n.t('api_guard_grape.access_token.missing')) unless @token
67
+ return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token
164
68
 
165
69
  authenticate_token
166
70
 
167
71
  # Render error response only if no resource found and no previous render happened
168
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid')) if !current_resource && !performed?
72
+ render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed?
169
73
  rescue JWT::DecodeError => e
170
74
  if e.message == 'Signature has expired'
171
- render_error(401, message: I18n.t('api_guard_grape.access_token.expired'))
75
+ render_error(401, message: I18n.t('api_guard.access_token.expired'))
172
76
  else
173
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid'))
77
+ render_error(401, message: I18n.t('api_guard.access_token.invalid'))
174
78
  end
175
79
  end
176
80
 
@@ -185,7 +89,7 @@ module ApiGuardGrape
185
89
  # Returns whether the JWT token is issued after the last password change
186
90
  # Returns true if password hasn't changed by the user
187
91
  def self.valid_issued_at?(resource)
188
- return true unless ApiGuardGrape.invalidate_old_tokens_on_password_change
92
+ return true unless ApiGuard.invalidate_old_tokens_on_password_change
189
93
 
190
94
  !resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i
191
95
  end
@@ -212,7 +116,7 @@ module ApiGuardGrape
212
116
  if resource && valid_issued_at?(resource) && !blacklisted?(resource)
213
117
  define_current_resource_accessors(resource)
214
118
  else
215
- render_error(401, message: I18n.t('api_guard_grape.access_token.invalid'))
119
+ render_error(401, message: I18n.t('api_guard.access_token.invalid'))
216
120
  end
217
121
  end
218
122
 
@@ -228,7 +132,6 @@ module ApiGuardGrape
228
132
 
229
133
  public_send("current_#{@resource_name}")
230
134
  end
231
-
232
135
  end
233
136
  end
234
137
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiGuard
4
+ module Models
5
+ module Concerns
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def api_guard_associations(refresh_token: nil, blacklisted_token: nil)
10
+ return if ApiGuard.api_guard_associations[name]
11
+
12
+ ApiGuard.api_guard_associations[name] = {}
13
+ ApiGuard.api_guard_associations[name][:refresh_token] = refresh_token
14
+ ApiGuard.api_guard_associations[name][:blacklisted_token] = blacklisted_token
15
+ end
16
+
17
+ def refresh_token_association
18
+ ApiGuard.api_guard_associations.dig(name, :refresh_token)
19
+ end
20
+
21
+ def blacklisted_token_association
22
+ ApiGuard.api_guard_associations.dig(name, :blacklisted_token)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end