api_guard_grape 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
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