api_guard_grape 0.5.1 → 0.5.6
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 +4 -4
- data/README.md +11 -11
- data/lib/api_guard/jwt_auth/blacklist_token.rb +5 -5
- data/lib/api_guard/jwt_auth/json_web_token.rb +170 -12
- data/lib/api_guard/jwt_auth/refresh_jwt_token.rb +98 -7
- data/lib/api_guard/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5cbdbe6a99ddb95a194ca2d457a00c632dcd8e70731b6e8bcebf02388c32a7ed
|
|
4
|
+
data.tar.gz: ded2f1d9537467657b493269e3a06093b2208f871a356f056aaf1179cd8e6e85
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37e5bb2e16a4d3733b630577c070be248fc9d24aa70209434fd0709b2c6efa60b32e3a90b76d08d4d7f127e3a2e0ef48b7ff4832bfd66a1f89f1120a911020ab
|
|
7
|
+
data.tar.gz: 97c8f918a4de68e43c42b207aeb964a8cd5b7c2ff12658533a61db86921b7b2cdd0e6fcd8f4467faa9e21a9706c527a05afe1f3fe440b4b962c00b99892f3d4e
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# API Guard
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/
|
|
4
|
-
[](https://codeclimate.com/github/
|
|
3
|
+
[](https://rubygems.org/gems/api_guard)
|
|
4
|
+
[](https://github.com/Gokul595/api_guard/actions?query=workflow%3Abuild)
|
|
5
|
+
[](https://codeclimate.com/github/Gokul595/api_guard/maintainability)
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
[JSON Web Token (JWT)](https://jwt.io/) based authentication solution with token refreshing & blacklisting for APIs
|
|
@@ -38,8 +38,8 @@ for cryptographic signing.
|
|
|
38
38
|
* [Override finding resource](#override-finding-resource)
|
|
39
39
|
* [Customizing / translating response messages using I18n](#customizing--translating-response-messages-using-i18n)
|
|
40
40
|
* [Testing](#testing)
|
|
41
|
-
* [Wiki](https://github.com/
|
|
42
|
-
* [Using API Guard with Devise](https://github.com/
|
|
41
|
+
* [Wiki](https://github.com/Gokul595/api_guard/wiki)
|
|
42
|
+
* [Using API Guard with Devise](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise)
|
|
43
43
|
* [Contributing](#contributing)
|
|
44
44
|
* [License](#license)
|
|
45
45
|
|
|
@@ -48,7 +48,7 @@ for cryptographic signing.
|
|
|
48
48
|
Add this line to your application's Gemfile:
|
|
49
49
|
|
|
50
50
|
```ruby
|
|
51
|
-
gem '
|
|
51
|
+
gem 'api_guard'
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
And then execute in your terminal:
|
|
@@ -58,7 +58,7 @@ $ bundle install
|
|
|
58
58
|
|
|
59
59
|
Or install it yourself as:
|
|
60
60
|
```bash
|
|
61
|
-
$ gem install
|
|
61
|
+
$ gem install api_guard
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
## Getting Started
|
|
@@ -82,7 +82,7 @@ $ rails db:migrate
|
|
|
82
82
|
Add [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password)
|
|
83
83
|
in `User` model for password authentication.
|
|
84
84
|
|
|
85
|
-
> Refer [this Wiki](https://github.com/
|
|
85
|
+
> Refer [this Wiki](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise#authentication) for configuring API Guard authentication to work with Devise instead of using `has_secure_password`.
|
|
86
86
|
|
|
87
87
|
```ruby
|
|
88
88
|
class User < ApplicationRecord
|
|
@@ -114,7 +114,7 @@ api_guard_routes for: 'users'
|
|
|
114
114
|
|
|
115
115
|
This will generate default routes such as sign up, sign in, sign out, token refresh, password change for User.
|
|
116
116
|
|
|
117
|
-
> Refer [this Wiki](https://github.com/
|
|
117
|
+
> Refer [this Wiki](https://github.com/Gokul595/api_guard/wiki/Using-API-Guard-with-Devise#routes) for configuring API Guard routes to work with Devise.
|
|
118
118
|
|
|
119
119
|
### Registration
|
|
120
120
|
|
|
@@ -633,7 +633,7 @@ en:
|
|
|
633
633
|
```
|
|
634
634
|
|
|
635
635
|
You can find the complete list of available keys in this file:
|
|
636
|
-
https://github.com/
|
|
636
|
+
https://github.com/Gokul595/api_guard/blob/master/config/locales/en.yml
|
|
637
637
|
|
|
638
638
|
## Testing
|
|
639
639
|
|
|
@@ -680,7 +680,7 @@ Then, you can set the access token and refresh token in appropriate request head
|
|
|
680
680
|
|
|
681
681
|
## Contributing
|
|
682
682
|
|
|
683
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
683
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Gokul595/api_guard.
|
|
684
684
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
|
|
685
685
|
the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
686
686
|
|
|
@@ -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)
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'jwt'
|
|
4
|
-
|
|
4
|
+
require 'api_guard/jwt_auth/refresh_jwt_token'
|
|
5
5
|
module ApiGuard
|
|
6
6
|
module JwtAuth
|
|
7
7
|
# Common module for JWT operations
|
|
8
8
|
module JsonWebToken
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
def self.current_time
|
|
10
11
|
@current_time ||= Time.now.utc
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def token_expire_at
|
|
14
|
+
def self.token_expire_at
|
|
14
15
|
@token_expire_at ||= (current_time + ApiGuard.token_validity).to_i
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
def token_issued_at
|
|
18
|
+
def self.token_issued_at
|
|
18
19
|
@token_issued_at ||= current_time.to_i
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# Encode the payload with the secret key and return the JWT token
|
|
22
|
-
def encode(payload)
|
|
23
|
+
def self.encode(payload)
|
|
23
24
|
JWT.encode(payload, ApiGuard.token_signing_secret)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# Decode the JWT token and return the payload
|
|
27
|
-
def decode(token, verify = true)
|
|
28
|
+
def self.decode(token, verify = true)
|
|
28
29
|
HashWithIndifferentAccess.new(
|
|
29
30
|
JWT.decode(token, ApiGuard.token_signing_secret, verify, verify_iat: true)[0]
|
|
30
31
|
)
|
|
@@ -34,7 +35,7 @@ module ApiGuard
|
|
|
34
35
|
# Also, create refresh token if enabled for the resource.
|
|
35
36
|
#
|
|
36
37
|
# 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)
|
|
38
|
+
def self.jwt_and_refresh_token(resource, resource_name, expired_token = false)
|
|
38
39
|
payload = {
|
|
39
40
|
"#{resource_name}_id": resource.id,
|
|
40
41
|
exp: expired_token ? token_issued_at : token_expire_at,
|
|
@@ -43,18 +44,17 @@ module ApiGuard
|
|
|
43
44
|
|
|
44
45
|
# Add custom data in the JWT token payload
|
|
45
46
|
payload.merge!(resource.jwt_token_payload) if resource.respond_to?(:jwt_token_payload)
|
|
46
|
-
|
|
47
|
-
[encode(payload), new_refresh_token(resource)]
|
|
47
|
+
[self.encode(payload), self.new_refresh_token(resource)]
|
|
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,169 @@ 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 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
|
+
resource.class.blacklisted_token_association
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.token_blacklisting_enabled?(resource)
|
|
117
|
+
blacklisted_token_association(resource).present?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def self.blacklisted_tokens_for(resource)
|
|
121
|
+
blacklisted_token_association = blacklisted_token_association(resource)
|
|
122
|
+
resource.send(blacklisted_token_association)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns whether the JWT token is blacklisted or not
|
|
126
|
+
def self.blacklisted?(resource)
|
|
127
|
+
return false unless token_blacklisting_enabled?(resource)
|
|
128
|
+
|
|
129
|
+
blacklisted_tokens_for(resource).exists?(token: @token)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Blacklist the current JWT token from future access
|
|
133
|
+
def self.blacklist_token
|
|
134
|
+
|
|
135
|
+
return unless token_blacklisting_enabled?(current_resource)
|
|
136
|
+
blacklisted_tokens_for(current_resource).create(token: @token, expire_at: Time.at(@decoded_token[:exp]).utc)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def self.method_missing(name, *args)
|
|
141
|
+
method_name = name.to_s
|
|
142
|
+
|
|
143
|
+
if method_name.start_with?('authenticate_and_set_')
|
|
144
|
+
resource_name = method_name.split('authenticate_and_set_')[1]
|
|
145
|
+
authenticate_and_set_resource(resource_name)
|
|
146
|
+
else
|
|
147
|
+
super
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
152
|
+
method_name.to_s.start_with?('authenticate_and_set_') || super
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Authenticate the JWT token and set resource
|
|
156
|
+
def self.authenticate_and_set_resource(resource_name)
|
|
157
|
+
@resource_name = resource_name
|
|
158
|
+
|
|
159
|
+
@token = request.headers['Authorization']&.split('Bearer ')&.last
|
|
160
|
+
return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token
|
|
161
|
+
|
|
162
|
+
authenticate_token
|
|
163
|
+
|
|
164
|
+
# Render error response only if no resource found and no previous render happened
|
|
165
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed?
|
|
166
|
+
rescue JWT::DecodeError => e
|
|
167
|
+
if e.message == 'Signature has expired'
|
|
168
|
+
render_error(401, message: I18n.t('api_guard.access_token.expired'))
|
|
169
|
+
else
|
|
170
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Decode the JWT token
|
|
175
|
+
# and don't verify token expiry for refresh token API request
|
|
176
|
+
def self.decode_token
|
|
177
|
+
# TODO: Set token refresh controller dynamic
|
|
178
|
+
verify_token = (controller_name != 'tokens' || action_name != 'create')
|
|
179
|
+
@decoded_token = decode(@token, verify_token)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Returns whether the JWT token is issued after the last password change
|
|
183
|
+
# Returns true if password hasn't changed by the user
|
|
184
|
+
def self.valid_issued_at?(resource)
|
|
185
|
+
return true unless ApiGuard.invalidate_old_tokens_on_password_change
|
|
186
|
+
|
|
187
|
+
!resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Defines "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
191
|
+
# that returns "resource" value
|
|
192
|
+
def self.define_current_resource_accessors(resource)
|
|
193
|
+
define_singleton_method("current_#{@resource_name}") do
|
|
194
|
+
instance_variable_get("@current_#{@resource_name}") ||
|
|
195
|
+
instance_variable_set("@current_#{@resource_name}", resource)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Authenticate the resource with the '{{resource_name}}_id' in the decoded JWT token
|
|
200
|
+
# and also, check for valid issued at time and not blacklisted
|
|
201
|
+
#
|
|
202
|
+
# Also, set "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
203
|
+
# for accessing the authenticated resource
|
|
204
|
+
def self.authenticate_token
|
|
205
|
+
return unless decode_token
|
|
206
|
+
|
|
207
|
+
resource = find_resource_from_token(@resource_name.classify.constantize)
|
|
208
|
+
|
|
209
|
+
if resource && valid_issued_at?(resource) && !blacklisted?(resource)
|
|
210
|
+
define_current_resource_accessors(resource)
|
|
211
|
+
else
|
|
212
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def self.find_resource_from_token(resource_class)
|
|
217
|
+
resource_id = @decoded_token[:"#{@resource_name}_id"]
|
|
218
|
+
return if resource_id.blank?
|
|
219
|
+
|
|
220
|
+
resource_class.find_by(id: resource_id)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def self.current_resource
|
|
224
|
+
return unless respond_to?("current_#{@resource_name}")
|
|
225
|
+
|
|
226
|
+
public_send("current_#{@resource_name}")
|
|
227
|
+
end
|
|
70
228
|
end
|
|
71
229
|
end
|
|
72
230
|
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,17 +30,108 @@ 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
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
# authenticat-----------
|
|
46
|
+
|
|
47
|
+
def self.method_missing(name, *args)
|
|
48
|
+
method_name = name.to_s
|
|
49
|
+
|
|
50
|
+
if method_name.start_with?('authenticate_and_set_')
|
|
51
|
+
resource_name = method_name.split('authenticate_and_set_')[1]
|
|
52
|
+
authenticate_and_set_resource(resource_name)
|
|
53
|
+
else
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
|
59
|
+
method_name.to_s.start_with?('authenticate_and_set_') || super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Authenticate the JWT token and set resource
|
|
63
|
+
def self.authenticate_and_set_resource(resource_name)
|
|
64
|
+
@resource_name = resource_name
|
|
65
|
+
|
|
66
|
+
@token = request.headers['Authorization']&.split('Bearer ')&.last
|
|
67
|
+
return render_error(401, message: I18n.t('api_guard.access_token.missing')) unless @token
|
|
68
|
+
|
|
69
|
+
authenticate_token
|
|
70
|
+
|
|
71
|
+
# Render error response only if no resource found and no previous render happened
|
|
72
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid')) if !current_resource && !performed?
|
|
73
|
+
rescue JWT::DecodeError => e
|
|
74
|
+
if e.message == 'Signature has expired'
|
|
75
|
+
render_error(401, message: I18n.t('api_guard.access_token.expired'))
|
|
76
|
+
else
|
|
77
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Decode the JWT token
|
|
82
|
+
# and don't verify token expiry for refresh token API request
|
|
83
|
+
def self.decode_token
|
|
84
|
+
# TODO: Set token refresh controller dynamic
|
|
85
|
+
verify_token = (controller_name != 'tokens' || action_name != 'create')
|
|
86
|
+
@decoded_token = decode(@token, verify_token)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns whether the JWT token is issued after the last password change
|
|
90
|
+
# Returns true if password hasn't changed by the user
|
|
91
|
+
def self.valid_issued_at?(resource)
|
|
92
|
+
return true unless ApiGuard.invalidate_old_tokens_on_password_change
|
|
93
|
+
|
|
94
|
+
!resource.token_issued_at || @decoded_token[:iat] >= resource.token_issued_at.to_i
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Defines "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
98
|
+
# that returns "resource" value
|
|
99
|
+
def self.define_current_resource_accessors(resource)
|
|
100
|
+
define_singleton_method("current_#{@resource_name}") do
|
|
101
|
+
instance_variable_get("@current_#{@resource_name}") ||
|
|
102
|
+
instance_variable_set("@current_#{@resource_name}", resource)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Authenticate the resource with the '{{resource_name}}_id' in the decoded JWT token
|
|
107
|
+
# and also, check for valid issued at time and not blacklisted
|
|
108
|
+
#
|
|
109
|
+
# Also, set "current_{{resource_name}}" method and "@current_{{resource_name}}" instance variable
|
|
110
|
+
# for accessing the authenticated resource
|
|
111
|
+
def self.authenticate_token
|
|
112
|
+
return unless decode_token
|
|
113
|
+
|
|
114
|
+
resource = find_resource_from_token(@resource_name.classify.constantize)
|
|
115
|
+
|
|
116
|
+
if resource && valid_issued_at?(resource) && !blacklisted?(resource)
|
|
117
|
+
define_current_resource_accessors(resource)
|
|
118
|
+
else
|
|
119
|
+
render_error(401, message: I18n.t('api_guard.access_token.invalid'))
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.find_resource_from_token(resource_class)
|
|
124
|
+
resource_id = @decoded_token[:"#{@resource_name}_id"]
|
|
125
|
+
return if resource_id.blank?
|
|
126
|
+
|
|
127
|
+
resource_class.find_by(id: resource_id)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def self.current_resource
|
|
131
|
+
return unless respond_to?("current_#{@resource_name}")
|
|
132
|
+
|
|
133
|
+
public_send("current_#{@resource_name}")
|
|
134
|
+
end
|
|
44
135
|
end
|
|
45
136
|
end
|
|
46
137
|
end
|
data/lib/api_guard/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.5.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Prateek Singh
|
|
@@ -176,7 +176,7 @@ files:
|
|
|
176
176
|
- lib/generators/api_guard/initializer/USAGE
|
|
177
177
|
- lib/generators/api_guard/initializer/initializer_generator.rb
|
|
178
178
|
- lib/generators/api_guard/initializer/templates/initializer.rb
|
|
179
|
-
homepage: https://github.com/prateeksinghbundela/
|
|
179
|
+
homepage: https://github.com/prateeksinghbundela/api_guard_grape
|
|
180
180
|
licenses:
|
|
181
181
|
- MIT
|
|
182
182
|
metadata: {}
|
|
@@ -195,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
195
195
|
- !ruby/object:Gem::Version
|
|
196
196
|
version: '0'
|
|
197
197
|
requirements: []
|
|
198
|
-
rubygems_version: 3.
|
|
198
|
+
rubygems_version: 3.1.4
|
|
199
199
|
signing_key:
|
|
200
200
|
specification_version: 4
|
|
201
201
|
summary: Rails API authentication made easy
|