rails_jwt_auth 1.3.1 → 1.7.3
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 +69 -35
- data/app/controllers/concerns/rails_jwt_auth/authenticable_helper.rb +8 -4
- data/app/controllers/concerns/rails_jwt_auth/params_helper.rb +3 -3
- data/app/controllers/rails_jwt_auth/confirmations_controller.rb +4 -1
- data/app/controllers/rails_jwt_auth/invitations_controller.rb +1 -0
- data/app/controllers/rails_jwt_auth/passwords_controller.rb +11 -2
- data/app/controllers/rails_jwt_auth/sessions_controller.rb +2 -2
- data/app/controllers/rails_jwt_auth/unlocks_controller.rb +14 -0
- data/app/mailers/rails_jwt_auth/mailer.rb +50 -36
- data/app/models/concerns/rails_jwt_auth/authenticatable.rb +12 -2
- data/app/models/concerns/rails_jwt_auth/confirmable.rb +32 -10
- data/app/models/concerns/rails_jwt_auth/invitable.rb +2 -3
- data/app/models/concerns/rails_jwt_auth/lockable.rb +114 -0
- data/app/models/concerns/rails_jwt_auth/recoverable.rb +12 -5
- data/app/views/rails_jwt_auth/mailer/send_unlock_instructions.html.erb +7 -0
- data/config/locales/en.yml +2 -0
- data/lib/generators/rails_jwt_auth/install_generator.rb +3 -2
- data/lib/generators/templates/initializer.rb +18 -0
- data/lib/generators/templates/migration.rb +6 -0
- data/lib/rails_jwt_auth/version.rb +1 -1
- data/lib/rails_jwt_auth.rb +23 -0
- metadata +7 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe3b51e7a2d8f1f7f8debef824a18683878451b1623b824e4b87f7ca8eef3f97
|
4
|
+
data.tar.gz: 7f2df300a20ecd34567dc76a1c8f03bd0abe89416056d048374ee9c6d1f786b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2262fed0d629d5ce892c39e6b3285e510e5817802e38c17375cd16c20ee9f757cc96cb82a0b33e662a968386ffb3674c60e21a538693834251fc9c25f62630c9
|
7
|
+
data.tar.gz: '0228cbd71bc2cdf9e77a0c7ec9bd58593c9b9540eaef6e169e779fc7f0499668862e4b8e1b13de0d3e96a1f854ebcffd4d05bd6af9e17f93ad2c34877334a427'
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RailsJwtAuth
|
2
2
|
|
3
|
-
|
3
|
+

|
4
4
|

|
5
5
|
|
6
6
|
Rails-API authentication solution based on JWT and inspired by Devise.
|
@@ -59,7 +59,7 @@ rails g rails_jwt_auth:migrate
|
|
59
59
|
|
60
60
|
## Configuration
|
61
61
|
|
62
|
-
You can edit configuration options into `config/initializers/
|
62
|
+
You can edit configuration options into `config/initializers/rails_jwt_auth.rb` file created by generator.
|
63
63
|
|
64
64
|
| Option | Default value | Description |
|
65
65
|
| ------------------------------- | ----------------- | ---------------------------------------------------------------------- |
|
@@ -78,11 +78,17 @@ You can edit configuration options into `config/initializers/auth_token_auth.rb`
|
|
78
78
|
| confirmations_url | nil | Url used to create email link with confirmation token |
|
79
79
|
| reset_passwords_url | nil | Url used to create email link with reset password token |
|
80
80
|
| set_passwords_url | nil | Url used to create email link with set password token |
|
81
|
-
|
|
81
|
+
| invitations_url | nil | Url used to create email link with invitation token |
|
82
|
+
| maximum_attempts | 3 | Number of failed login attempts before locking an account |
|
83
|
+
| lock_strategy | :none | Strategy to be used to lock an account: `:none` or `:failed_attempts` |
|
84
|
+
| unlock_strategy | :time | Strategy to use when unlocking accounts: `:time`, `:email` or `:both` |
|
85
|
+
| unlock_in | 60.minutes | Interval to unlock an account if `unlock_strategy` is `:time` |
|
86
|
+
| reset_attempts_in | 60.minutes | Interval after which to reset failed attempts counter of an account |
|
87
|
+
| unlock_url | nil | Url used to create email link with unlock token |
|
82
88
|
|
83
89
|
## Modules
|
84
90
|
|
85
|
-
It's composed of
|
91
|
+
It's composed of 6 modules:
|
86
92
|
|
87
93
|
| Module | Description |
|
88
94
|
| ------------- | --------------------------------------------------------------------------------------------------------------- |
|
@@ -91,6 +97,7 @@ It's composed of 5 modules:
|
|
91
97
|
| Recoverable | Resets the user password and sends reset instructions |
|
92
98
|
| Trackable | Tracks sign in timestamps and IP address |
|
93
99
|
| Invitable | Allows you to invite an user to your application sending an invitation mail |
|
100
|
+
| Lockable | Locks the user after a specified number of failed sign in attempts |
|
94
101
|
|
95
102
|
## ORMs support
|
96
103
|
|
@@ -108,6 +115,7 @@ class User < ApplicationRecord
|
|
108
115
|
include RailsJwtAuth::Recoverable
|
109
116
|
include RailsJwtAuth::Trackable
|
110
117
|
include RailsJwtAuth::Invitable
|
118
|
+
include RailsJwtAuth::Lockable
|
111
119
|
|
112
120
|
validates :email, presence: true,
|
113
121
|
uniqueness: true,
|
@@ -127,6 +135,7 @@ class User
|
|
127
135
|
include RailsJwtAuth::Recoverable
|
128
136
|
include RailsJwtAuth::Trackable
|
129
137
|
include RailsJwtAuth::Invitable
|
138
|
+
include RailsJwtAuth::Lockable
|
130
139
|
|
131
140
|
field :email, type: String
|
132
141
|
|
@@ -159,7 +168,7 @@ end
|
|
159
168
|
end
|
160
169
|
```
|
161
170
|
|
162
|
-
This helper expect that token has been into **AUTHORIZATION** header.
|
171
|
+
This helper expect that token has been into **AUTHORIZATION** header.
|
163
172
|
Raises `RailsJwtAuth::NotAuthorized` exception when it fails.
|
164
173
|
|
165
174
|
- **authenticate**
|
@@ -178,12 +187,31 @@ end
|
|
178
187
|
|
179
188
|
Return current signed-in user.
|
180
189
|
|
190
|
+
- **jwt_payload**
|
191
|
+
|
192
|
+
Return current jwt payload.
|
193
|
+
|
181
194
|
- **signed_in?**
|
182
195
|
|
183
196
|
Verify if a user is signed in.
|
184
197
|
|
185
198
|
## Default Controllers API
|
186
199
|
|
200
|
+
| Prefix | Verb | URI Pattern | Controller#Action |
|
201
|
+
| ------------ | ------ | ---------------------------- | ----------------------------------- |
|
202
|
+
| session | DELETE | /session(.:format) | rails_jwt_auth/sessions#destroy |
|
203
|
+
| | POST | /session(.:format) | rails_jwt_auth/sessions#create |
|
204
|
+
| registration | POST | /registration(.:format) | rails_jwt_auth/registrations#create |
|
205
|
+
|confirmations | POST | /confirmations(.:format) | rails_jwt_auth/confirmations#create |
|
206
|
+
| confirmation | PATCH | /confirmations/:id(.:format) | rails_jwt_auth/confirmations#update |
|
207
|
+
| | PUT | /confirmations/:id(.:format) | rails_jwt_auth/confirmations#update |
|
208
|
+
| passwords | POST | /passwords(.:format) | rails_jwt_auth/passwords#create |
|
209
|
+
| password | PATCH | /passwords/:id(.:format) | rails_jwt_auth/passwords#update |
|
210
|
+
| | PUT | /passwords/:id(.:format) | rails_jwt_auth/passwords#update |
|
211
|
+
| invitations | POST | /invitations(.:format) | rails_jwt_auth/invitations#create |
|
212
|
+
| invitation | PATCH | /invitations/:id(.:format) | rails_jwt_auth/invitations#update |
|
213
|
+
| | PUT | /invitations/:id(.:format) | rails_jwt_auth/invitations#update |
|
214
|
+
|
187
215
|
### Session
|
188
216
|
|
189
217
|
Session api is defined by `RailsJwtAuth::SessionsController`.
|
@@ -196,8 +224,8 @@ Session api is defined by `RailsJwtAuth::SessionsController`.
|
|
196
224
|
method: POST,
|
197
225
|
data: {
|
198
226
|
session: {
|
199
|
-
email:
|
200
|
-
password:
|
227
|
+
email: 'user@email.com',
|
228
|
+
password: '12345678'
|
201
229
|
}
|
202
230
|
}
|
203
231
|
}
|
@@ -225,36 +253,26 @@ Registration api is defined by `RailsJwtAuth::RegistrationsController`.
|
|
225
253
|
method: POST,
|
226
254
|
data: {
|
227
255
|
user: {
|
228
|
-
email:
|
229
|
-
password:
|
256
|
+
email: 'user@email.com',
|
257
|
+
password: '12345678'
|
230
258
|
}
|
231
259
|
}
|
232
260
|
}
|
233
261
|
```
|
234
262
|
|
235
|
-
2. Delete user:
|
236
|
-
|
237
|
-
```js
|
238
|
-
{
|
239
|
-
url: host/registration,
|
240
|
-
method: DELETE,
|
241
|
-
headers: { 'Authorization': 'Bearer auth_token'}
|
242
|
-
}
|
243
|
-
```
|
244
|
-
|
245
263
|
### Confirmation
|
246
264
|
|
247
265
|
Confirmation api is defined by `RailsJwtAuth::ConfirmationsController`.
|
248
266
|
|
267
|
+
It is necessary to set a value for `confirmations_url` option into `config/initializers/rails_jwt_auth.rb`.
|
268
|
+
|
249
269
|
1. Confirm user:
|
250
270
|
|
251
271
|
```js
|
252
272
|
{
|
253
|
-
url: host/
|
273
|
+
url: host/confirmations/:token,
|
254
274
|
method: PUT
|
255
|
-
data: {
|
256
|
-
confirmation_token: "token"
|
257
|
-
}
|
275
|
+
data: {}
|
258
276
|
}
|
259
277
|
```
|
260
278
|
|
@@ -262,11 +280,11 @@ Confirmation api is defined by `RailsJwtAuth::ConfirmationsController`.
|
|
262
280
|
|
263
281
|
```js
|
264
282
|
{
|
265
|
-
url: host/
|
283
|
+
url: host/confirmations,
|
266
284
|
method: POST,
|
267
285
|
data: {
|
268
286
|
confirmation: {
|
269
|
-
email:
|
287
|
+
email: 'user@example.com'
|
270
288
|
}
|
271
289
|
}
|
272
290
|
}
|
@@ -280,11 +298,11 @@ Password api is defined by `RailsJwtAuth::PasswordsController`.
|
|
280
298
|
|
281
299
|
```js
|
282
300
|
{
|
283
|
-
url: host/
|
301
|
+
url: host/passwords,
|
284
302
|
method: POST,
|
285
303
|
data: {
|
286
304
|
password: {
|
287
|
-
email:
|
305
|
+
email: 'user@example.com'
|
288
306
|
}
|
289
307
|
}
|
290
308
|
}
|
@@ -294,10 +312,9 @@ Password api is defined by `RailsJwtAuth::PasswordsController`.
|
|
294
312
|
|
295
313
|
```js
|
296
314
|
{
|
297
|
-
url: host/
|
315
|
+
url: host/passwords/:token,
|
298
316
|
method: PUT,
|
299
317
|
data: {
|
300
|
-
reset_password_token: "token",
|
301
318
|
password: {
|
302
319
|
password: '1234',
|
303
320
|
password_confirmation: '1234'
|
@@ -318,7 +335,7 @@ Invitations api is provided by `RailsJwtAuth::InvitationsController`.
|
|
318
335
|
method: POST,
|
319
336
|
data: {
|
320
337
|
invitation: {
|
321
|
-
email:
|
338
|
+
email: 'user@example.com',
|
322
339
|
// More fields of your user
|
323
340
|
}
|
324
341
|
}
|
@@ -342,6 +359,20 @@ Invitations api is provided by `RailsJwtAuth::InvitationsController`.
|
|
342
359
|
|
343
360
|
Note: To add more fields, see "Custom strong parameters" below.
|
344
361
|
|
362
|
+
### Unlocks
|
363
|
+
|
364
|
+
Unlock api is provided by `RailsJwtAuth::UnlocksController`.
|
365
|
+
|
366
|
+
1. Unlock user:
|
367
|
+
|
368
|
+
```js
|
369
|
+
{
|
370
|
+
url: host/unlocks/:unlock_token,
|
371
|
+
method: PUT,
|
372
|
+
data: {}
|
373
|
+
}
|
374
|
+
```
|
375
|
+
|
345
376
|
## Customize
|
346
377
|
|
347
378
|
RailsJwtAuth offers an easy way to customize certain parts.
|
@@ -414,7 +445,10 @@ class CurrentUserController < ApplicationController
|
|
414
445
|
|
415
446
|
def update
|
416
447
|
if update_params[:password]
|
417
|
-
|
448
|
+
# update password and remove other sessions tokens
|
449
|
+
current_user.update_with_password(
|
450
|
+
update_params.merge(auth_tokens: [jwt_payload['auth_token']])
|
451
|
+
)
|
418
452
|
else
|
419
453
|
current_user.update_attributes(update_params)
|
420
454
|
end
|
@@ -459,7 +493,7 @@ require 'rails_jwt_auth/spec_helpers'
|
|
459
493
|
...
|
460
494
|
RSpec.configure do |config|
|
461
495
|
...
|
462
|
-
config.include RailsJwtAuth::SpecHelpers, :
|
496
|
+
config.include RailsJwtAuth::SpecHelpers, type: :controller
|
463
497
|
end
|
464
498
|
```
|
465
499
|
|
@@ -467,11 +501,11 @@ And then we can just call sign_in(user) to sign in as a user:
|
|
467
501
|
|
468
502
|
```ruby
|
469
503
|
describe ExampleController
|
470
|
-
it
|
471
|
-
expect { get :index }.to raise_error(RailsJwtAuth::
|
504
|
+
it 'blocks unauthenticated access' do
|
505
|
+
expect { get :index }.to raise_error(RailsJwtAuth::NotAuthorized)
|
472
506
|
end
|
473
507
|
|
474
|
-
it
|
508
|
+
it 'allows authenticated access' do
|
475
509
|
sign_in user
|
476
510
|
get :index
|
477
511
|
expect(response).to be_success
|
@@ -6,18 +6,22 @@ module RailsJwtAuth
|
|
6
6
|
@current_user
|
7
7
|
end
|
8
8
|
|
9
|
+
def jwt_payload
|
10
|
+
@jwt_payload
|
11
|
+
end
|
12
|
+
|
9
13
|
def signed_in?
|
10
14
|
!current_user.nil?
|
11
15
|
end
|
12
16
|
|
13
17
|
def authenticate!
|
14
18
|
begin
|
15
|
-
|
19
|
+
@jwt_payload = RailsJwtAuth::JwtManager.decode_from_request(request).first
|
16
20
|
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
|
17
21
|
unauthorize!
|
18
22
|
end
|
19
23
|
|
20
|
-
if !@current_user = RailsJwtAuth.model.from_token_payload(
|
24
|
+
if !@current_user = RailsJwtAuth.model.from_token_payload(@jwt_payload)
|
21
25
|
unauthorize!
|
22
26
|
elsif @current_user.respond_to? :update_tracked_fields!
|
23
27
|
@current_user.update_tracked_fields!(request)
|
@@ -26,8 +30,8 @@ module RailsJwtAuth
|
|
26
30
|
|
27
31
|
def authenticate
|
28
32
|
begin
|
29
|
-
|
30
|
-
@current_user = RailsJwtAuth.model.from_token_payload(
|
33
|
+
@jwt_payload = RailsJwtAuth::JwtManager.decode_from_request(request).first
|
34
|
+
@current_user = RailsJwtAuth.model.from_token_payload(@jwt_payload)
|
31
35
|
rescue JWT::ExpiredSignature, JWT::VerificationError, JWT::DecodeError
|
32
36
|
@current_user = nil
|
33
37
|
end
|
@@ -9,7 +9,7 @@ module RailsJwtAuth
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def confirmation_create_params
|
12
|
-
params.require(:confirmation).permit(
|
12
|
+
params.require(:confirmation).permit(RailsJwtAuth.email_field_name)
|
13
13
|
end
|
14
14
|
|
15
15
|
def session_create_params
|
@@ -17,7 +17,7 @@ module RailsJwtAuth
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def password_create_params
|
20
|
-
params.require(:password).permit(
|
20
|
+
params.require(:password).permit(RailsJwtAuth.email_field_name)
|
21
21
|
end
|
22
22
|
|
23
23
|
def password_update_params
|
@@ -25,7 +25,7 @@ module RailsJwtAuth
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def invitation_create_params
|
28
|
-
params.require(:invitation).permit(
|
28
|
+
params.require(:invitation).permit(RailsJwtAuth.email_field_name)
|
29
29
|
end
|
30
30
|
|
31
31
|
def invitation_update_params
|
@@ -4,7 +4,10 @@ module RailsJwtAuth
|
|
4
4
|
include RenderHelper
|
5
5
|
|
6
6
|
def create
|
7
|
-
user = RailsJwtAuth.model.where(
|
7
|
+
user = RailsJwtAuth.model.where(
|
8
|
+
email: confirmation_create_params[RailsJwtAuth.email_field_name]
|
9
|
+
).first
|
10
|
+
|
8
11
|
return render_422(email: [{error: :not_found}]) unless user
|
9
12
|
|
10
13
|
user.send_confirmation_instructions ? render_204 : render_422(user.errors.details)
|
@@ -4,8 +4,17 @@ module RailsJwtAuth
|
|
4
4
|
include RenderHelper
|
5
5
|
|
6
6
|
def create
|
7
|
-
|
8
|
-
|
7
|
+
email_field = RailsJwtAuth.email_field_name
|
8
|
+
|
9
|
+
if password_create_params[email_field].blank?
|
10
|
+
return render_422(email_field => [{error: :blank}])
|
11
|
+
end
|
12
|
+
|
13
|
+
user = RailsJwtAuth.model.where(
|
14
|
+
email_field => password_create_params[email_field].to_s.strip.downcase
|
15
|
+
).first
|
16
|
+
|
17
|
+
return render_422(email_field => [{error: :not_found}]) unless user
|
9
18
|
|
10
19
|
user.send_reset_password_instructions ? render_204 : render_422(user.errors.details)
|
11
20
|
end
|
@@ -10,10 +10,10 @@ module RailsJwtAuth
|
|
10
10
|
render_422 session: [{error: :invalid_session}]
|
11
11
|
elsif user.respond_to?('confirmed?') && !user.confirmed?
|
12
12
|
render_422 session: [{error: :unconfirmed}]
|
13
|
-
elsif user.
|
13
|
+
elsif user.authentication?(session_create_params[:password])
|
14
14
|
render_session generate_jwt(user), user
|
15
15
|
else
|
16
|
-
render_422 session: [
|
16
|
+
render_422 session: [user.unauthenticated_error]
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RailsJwtAuth
|
2
|
+
class UnlocksController < ApplicationController
|
3
|
+
include ParamsHelper
|
4
|
+
include RenderHelper
|
5
|
+
|
6
|
+
def update
|
7
|
+
return render_404 unless
|
8
|
+
params[:id] &&
|
9
|
+
(user = RailsJwtAuth.model.where(unlock_token: params[:id]).first)
|
10
|
+
|
11
|
+
user.unlock_access! ? render_204 : render_422(user.errors.details)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -2,62 +2,76 @@ if defined?(ActionMailer)
|
|
2
2
|
class RailsJwtAuth::Mailer < ApplicationMailer
|
3
3
|
default from: RailsJwtAuth.mailer_sender
|
4
4
|
|
5
|
-
|
5
|
+
before_action do
|
6
|
+
@user = RailsJwtAuth.model.find(params[:user_id])
|
7
|
+
@subject = I18n.t("rails_jwt_auth.mailer.#{action_name}.subject")
|
8
|
+
end
|
9
|
+
|
10
|
+
def confirmation_instructions
|
6
11
|
raise RailsJwtAuth::NotConfirmationsUrl unless RailsJwtAuth.confirmations_url.present?
|
7
|
-
@user = user
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
@confirmations_url = add_param_to_url(
|
14
|
+
RailsJwtAuth.confirmations_url,
|
15
|
+
'confirmation_token',
|
16
|
+
@user.confirmation_token
|
17
|
+
)
|
13
18
|
|
14
|
-
|
15
|
-
mail(to: @user.unconfirmed_email || @user[RailsJwtAuth.email_field_name], subject: subject)
|
19
|
+
mail(to: @user.unconfirmed_email || @user[RailsJwtAuth.email_field_name], subject: @subject)
|
16
20
|
end
|
17
21
|
|
18
|
-
def email_changed
|
19
|
-
@user
|
20
|
-
subject = I18n.t('rails_jwt_auth.mailer.email_changed.subject')
|
21
|
-
mail(to: @user[RailsJwtAuth.email_field_name!], subject: subject)
|
22
|
+
def email_changed
|
23
|
+
mail(to: @user[RailsJwtAuth.email_field_name!], subject: @subject)
|
22
24
|
end
|
23
25
|
|
24
|
-
def reset_password_instructions
|
26
|
+
def reset_password_instructions
|
25
27
|
raise RailsJwtAuth::NotResetPasswordsUrl unless RailsJwtAuth.reset_passwords_url.present?
|
26
|
-
@user = user
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
@reset_passwords_url = add_param_to_url(
|
30
|
+
RailsJwtAuth.reset_passwords_url,
|
31
|
+
'reset_password_token',
|
32
|
+
@user.reset_password_token
|
33
|
+
)
|
32
34
|
|
33
|
-
|
34
|
-
mail(to: @user[RailsJwtAuth.email_field_name], subject: subject)
|
35
|
+
mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
|
35
36
|
end
|
36
37
|
|
37
|
-
def set_password_instructions
|
38
|
+
def set_password_instructions
|
38
39
|
raise RailsJwtAuth::NotSetPasswordsUrl unless RailsJwtAuth.set_passwords_url.present?
|
39
|
-
@user = user
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
@reset_passwords_url = add_param_to_url(
|
42
|
+
RailsJwtAuth.set_passwords_url,
|
43
|
+
'reset_password_token',
|
44
|
+
@user.reset_password_token
|
45
|
+
)
|
45
46
|
|
46
|
-
|
47
|
-
mail(to: @user[RailsJwtAuth.email_field_name], subject: subject)
|
47
|
+
mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
|
48
48
|
end
|
49
49
|
|
50
|
-
def send_invitation
|
50
|
+
def send_invitation
|
51
51
|
raise RailsJwtAuth::NotInvitationsUrl unless RailsJwtAuth.invitations_url.present?
|
52
|
-
@user = user
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
@invitations_url = add_param_to_url(
|
54
|
+
RailsJwtAuth.invitations_url,
|
55
|
+
'invitation_token',
|
56
|
+
@user.invitation_token
|
57
|
+
)
|
58
58
|
|
59
|
-
|
60
|
-
|
59
|
+
mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
|
60
|
+
end
|
61
|
+
|
62
|
+
def send_unlock_instructions
|
63
|
+
@unlock_url = add_param_to_url(RailsJwtAuth.unlock_url, 'unlock_token', @user.unlock_token)
|
64
|
+
|
65
|
+
mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def add_param_to_url(url, param_name, param_value)
|
71
|
+
path, params = url.split '?'
|
72
|
+
params = params ? params.split('&') : []
|
73
|
+
params.push("#{param_name}=#{param_value}")
|
74
|
+
"#{path}?#{params.join('&')}"
|
61
75
|
end
|
62
76
|
end
|
63
77
|
end
|
@@ -46,8 +46,10 @@ module RailsJwtAuth
|
|
46
46
|
'invalid'
|
47
47
|
end
|
48
48
|
|
49
|
-
#
|
50
|
-
|
49
|
+
# if recoberable module is enabled ensure clean recovery to allow save
|
50
|
+
if self.respond_to? :reset_password_token
|
51
|
+
self.reset_password_token = self.reset_password_sent_at = nil
|
52
|
+
end
|
51
53
|
|
52
54
|
assign_attributes(params)
|
53
55
|
valid? # validates first other fields
|
@@ -65,6 +67,14 @@ module RailsJwtAuth
|
|
65
67
|
end
|
66
68
|
end
|
67
69
|
|
70
|
+
def authentication?(pass)
|
71
|
+
authenticate(pass)
|
72
|
+
end
|
73
|
+
|
74
|
+
def unauthenticated_error
|
75
|
+
{error: :invalid_session}
|
76
|
+
end
|
77
|
+
|
68
78
|
module ClassMethods
|
69
79
|
def from_token_payload(payload)
|
70
80
|
if RailsJwtAuth.simultaneous_sessions > 0
|
@@ -33,13 +33,19 @@ module RailsJwtAuth
|
|
33
33
|
|
34
34
|
self.confirmation_token = SecureRandom.base58(24)
|
35
35
|
self.confirmation_sent_at = Time.current
|
36
|
+
end
|
37
|
+
end
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
if defined?(ActiveRecord) && ancestors.include?(ActiveRecord::Base)
|
40
|
+
after_commit do
|
41
|
+
if unconfirmed_email && saved_change_to_unconfirmed_email?
|
42
|
+
deliver_email_changed_emails
|
43
|
+
end
|
44
|
+
end
|
45
|
+
elsif defined?(Mongoid) && ancestors.include?(Mongoid::Document)
|
46
|
+
after_update do
|
47
|
+
if unconfirmed_email && unconfirmed_email_changed?
|
48
|
+
deliver_email_changed_emails
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
@@ -58,8 +64,7 @@ module RailsJwtAuth
|
|
58
64
|
self.confirmation_sent_at = Time.current
|
59
65
|
return false unless save
|
60
66
|
|
61
|
-
|
62
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
67
|
+
RailsJwtAuth.send_email(:confirmation_instructions, self)
|
63
68
|
true
|
64
69
|
end
|
65
70
|
|
@@ -72,9 +77,15 @@ module RailsJwtAuth
|
|
72
77
|
self.confirmation_token = nil
|
73
78
|
|
74
79
|
if unconfirmed_email
|
75
|
-
|
76
|
-
|
80
|
+
email_field = RailsJwtAuth.email_field_name!
|
81
|
+
|
82
|
+
self[email_field] = unconfirmed_email
|
77
83
|
self.unconfirmed_email = nil
|
84
|
+
|
85
|
+
# supports email confirmation attr_accessor validation
|
86
|
+
if respond_to?("#{email_field}_confirmation")
|
87
|
+
instance_variable_set("@#{email_field}_confirmation", self[email_field])
|
88
|
+
end
|
78
89
|
end
|
79
90
|
|
80
91
|
save
|
@@ -89,6 +100,7 @@ module RailsJwtAuth
|
|
89
100
|
|
90
101
|
def validate_confirmation
|
91
102
|
return true unless confirmed_at
|
103
|
+
|
92
104
|
email_field = RailsJwtAuth.email_field_name!
|
93
105
|
|
94
106
|
if confirmed_at_was && !public_send("#{email_field}_changed?")
|
@@ -98,5 +110,15 @@ module RailsJwtAuth
|
|
98
110
|
errors.add(:confirmation_token, :expired)
|
99
111
|
end
|
100
112
|
end
|
113
|
+
|
114
|
+
def deliver_email_changed_emails
|
115
|
+
# send confirmation to new email
|
116
|
+
RailsJwtAuth.send_email(:confirmation_instructions, self)
|
117
|
+
|
118
|
+
# send notify to old email
|
119
|
+
if RailsJwtAuth.send_email_changed_notification
|
120
|
+
RailsJwtAuth.send_email(:email_changed, self)
|
121
|
+
end
|
122
|
+
end
|
101
123
|
end
|
102
124
|
end
|
@@ -112,9 +112,8 @@ module RailsJwtAuth
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def send_invitation_mail
|
115
|
-
RailsJwtAuth.email_field_name! # ensure email field
|
116
|
-
|
117
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
115
|
+
RailsJwtAuth.email_field_name! # ensure email field is valid
|
116
|
+
RailsJwtAuth.send_email(:send_invitation, self)
|
118
117
|
end
|
119
118
|
|
120
119
|
def invitation_period_valid?
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module RailsJwtAuth
|
2
|
+
module Lockable
|
3
|
+
BOTH_UNLOCK_STRATEGIES = %i[time email].freeze
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
if defined?(Mongoid) && ancestors.include?(Mongoid::Document)
|
8
|
+
field :failed_attempts, type: Integer
|
9
|
+
field :unlock_token, type: String
|
10
|
+
field :first_failed_attempt_at, type: Time
|
11
|
+
field :locked_at, type: Time
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def lock_access!
|
17
|
+
self.locked_at = Time.now.utc
|
18
|
+
save(validate: false).tap do |result|
|
19
|
+
send_unlock_instructions if result && unlock_strategy_enabled?(:email)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def unlock_access!
|
24
|
+
self.locked_at = nil
|
25
|
+
self.failed_attempts = 0
|
26
|
+
self.first_failed_attempt_at = nil
|
27
|
+
self.unlock_token = nil
|
28
|
+
save(validate: false)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset_attempts!
|
32
|
+
self.failed_attempts = 0
|
33
|
+
self.first_failed_attempt_at = nil
|
34
|
+
save(validate: false)
|
35
|
+
end
|
36
|
+
|
37
|
+
def authentication?(pass)
|
38
|
+
return super(pass) unless lock_strategy_enabled?(:failed_attempts)
|
39
|
+
|
40
|
+
reset_attempts! if !access_locked? && attempts_expired?
|
41
|
+
unlock_access! if lock_expired?
|
42
|
+
|
43
|
+
if access_locked?
|
44
|
+
false
|
45
|
+
elsif super(pass)
|
46
|
+
unlock_access!
|
47
|
+
self
|
48
|
+
else
|
49
|
+
failed_attempt!
|
50
|
+
lock_access! if attempts_exceeded?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def unauthenticated_error
|
56
|
+
return super unless lock_strategy_enabled?(:failed_attempts)
|
57
|
+
|
58
|
+
if access_locked?
|
59
|
+
{error: :locked}
|
60
|
+
else
|
61
|
+
{error: :invalid_session, remaining_attempts: remaining_attempts}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def send_unlock_instructions
|
68
|
+
self.unlock_token = SecureRandom.base58(24)
|
69
|
+
save(validate: false)
|
70
|
+
|
71
|
+
RailsJwtAuth.send_email(:send_unlock_instructions, self)
|
72
|
+
end
|
73
|
+
|
74
|
+
def access_locked?
|
75
|
+
locked_at && !lock_expired?
|
76
|
+
end
|
77
|
+
|
78
|
+
def lock_expired?
|
79
|
+
if unlock_strategy_enabled?(:time)
|
80
|
+
locked_at && locked_at < RailsJwtAuth.unlock_in.ago
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def failed_attempt!
|
87
|
+
self.failed_attempts ||= 0
|
88
|
+
self.failed_attempts += 1
|
89
|
+
self.first_failed_attempt_at = Time.now.utc if failed_attempts == 1
|
90
|
+
save(validate: false)
|
91
|
+
end
|
92
|
+
|
93
|
+
def attempts_exceeded?
|
94
|
+
failed_attempts && failed_attempts >= RailsJwtAuth.maximum_attempts
|
95
|
+
end
|
96
|
+
|
97
|
+
def remaining_attempts
|
98
|
+
RailsJwtAuth.maximum_attempts - failed_attempts.to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
def attempts_expired?
|
102
|
+
first_failed_attempt_at && first_failed_attempt_at < RailsJwtAuth.reset_attempts_in.ago
|
103
|
+
end
|
104
|
+
|
105
|
+
def lock_strategy_enabled?(strategy)
|
106
|
+
RailsJwtAuth.lock_strategy == strategy
|
107
|
+
end
|
108
|
+
|
109
|
+
def unlock_strategy_enabled?(strategy)
|
110
|
+
RailsJwtAuth.unlock_strategy == strategy ||
|
111
|
+
(RailsJwtAuth.unlock_strategy == :both && BOTH_UNLOCK_STRATEGIES.include?(strategy))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -14,7 +14,10 @@ module RailsJwtAuth
|
|
14
14
|
validate :validate_reset_password_token, if: :password_digest_changed?
|
15
15
|
|
16
16
|
before_update do
|
17
|
-
|
17
|
+
if password_digest_changed? && reset_password_token
|
18
|
+
self.reset_password_token = nil
|
19
|
+
self.auth_tokens = []
|
20
|
+
end
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
@@ -27,12 +30,17 @@ module RailsJwtAuth
|
|
27
30
|
return false
|
28
31
|
end
|
29
32
|
|
33
|
+
if self.class.ancestors.include?(RailsJwtAuth::Lockable) &&
|
34
|
+
lock_strategy_enabled?(:failed_attempts) && access_locked?
|
35
|
+
errors.add(email_field, :locked)
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
|
30
39
|
self.reset_password_token = SecureRandom.base58(24)
|
31
40
|
self.reset_password_sent_at = Time.current
|
32
41
|
return false unless save
|
33
42
|
|
34
|
-
|
35
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
43
|
+
RailsJwtAuth.send_email(:reset_password_instructions, self)
|
36
44
|
end
|
37
45
|
|
38
46
|
def set_and_send_password_instructions
|
@@ -47,8 +55,7 @@ module RailsJwtAuth
|
|
47
55
|
self.reset_password_sent_at = Time.current
|
48
56
|
return false unless save
|
49
57
|
|
50
|
-
|
51
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
58
|
+
RailsJwtAuth.send_email(:set_password_instructions, self)
|
52
59
|
true
|
53
60
|
end
|
54
61
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<p>Hello <%= @user[RailsJwtAuth.email_field_name] %>!</p>
|
2
|
+
|
3
|
+
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
|
4
|
+
|
5
|
+
<p>Click the link below to unlock your account:</p>
|
6
|
+
|
7
|
+
<p><%= link_to 'Unlock my account', @unlock_url.html_safe %></p>
|
data/config/locales/en.yml
CHANGED
@@ -6,11 +6,12 @@ class RailsJwtAuth::InstallGenerator < Rails::Generators::Base
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def create_routes
|
9
|
-
route "
|
10
|
-
route "
|
9
|
+
route "resource :session, controller: 'rails_jwt_auth/sessions', only: [:create, :destroy]"
|
10
|
+
route "resource :registration, controller: 'rails_jwt_auth/registrations', only: [:create]"
|
11
11
|
|
12
12
|
route "resources :confirmations, controller: 'rails_jwt_auth/confirmations', only: [:create, :update]"
|
13
13
|
route "resources :passwords, controller: 'rails_jwt_auth/passwords', only: [:create, :update]"
|
14
14
|
route "resources :invitations, controller: 'rails_jwt_auth/invitations', only: [:create, :update]"
|
15
|
+
route "resources :unlocks, controller: 'rails_jwt_auth/unlocks', only: %i[update]"
|
15
16
|
end
|
16
17
|
end
|
@@ -44,4 +44,22 @@ RailsJwtAuth.setup do |config|
|
|
44
44
|
|
45
45
|
# uses deliver_later to send emails instead of deliver method
|
46
46
|
#config.deliver_later = false
|
47
|
+
|
48
|
+
# maximum login attempts before locking an account
|
49
|
+
#config.maximum_attempts = 3
|
50
|
+
|
51
|
+
# strategy to lock an account: :none or :failed_attempts
|
52
|
+
#config.lock_strategy = :failed_attempts
|
53
|
+
|
54
|
+
# strategy to use when unlocking accounts: :time, :email or :both
|
55
|
+
#config.unlock_strategy = :time
|
56
|
+
|
57
|
+
# interval to unlock an account if unlock_strategy is :time
|
58
|
+
#config.unlock_in = 60.minutes
|
59
|
+
|
60
|
+
# interval after which to reset failed attempts counter of an account
|
61
|
+
#config.reset_attempts_in = 60.minutes
|
62
|
+
|
63
|
+
# url used to create email link with unlock token
|
64
|
+
#config.unlock_url = 'http://frontend.com/unlock-account'
|
47
65
|
end
|
@@ -24,6 +24,12 @@ class Create<%= RailsJwtAuth.model_name.pluralize %> < ActiveRecord::Migration<%
|
|
24
24
|
# t.datetime :invitation_sent_at
|
25
25
|
# t.datetime :invitation_accepted_at
|
26
26
|
# t.datetime :invitation_created_at
|
27
|
+
|
28
|
+
## Lockable
|
29
|
+
# t.integer :failed_attempts
|
30
|
+
# t.string :unlock_token
|
31
|
+
# t.datetime :first_failed_attempt_at
|
32
|
+
# t.datetime :locked_at
|
27
33
|
end
|
28
34
|
end
|
29
35
|
end
|
data/lib/rails_jwt_auth.rb
CHANGED
@@ -59,6 +59,24 @@ module RailsJwtAuth
|
|
59
59
|
mattr_accessor :deliver_later
|
60
60
|
self.deliver_later = false
|
61
61
|
|
62
|
+
mattr_accessor :maximum_attempts
|
63
|
+
self.maximum_attempts = 3
|
64
|
+
|
65
|
+
mattr_accessor :lock_strategy
|
66
|
+
self.lock_strategy = :none
|
67
|
+
|
68
|
+
mattr_accessor :unlock_strategy
|
69
|
+
self.unlock_strategy = :time
|
70
|
+
|
71
|
+
mattr_accessor :unlock_in
|
72
|
+
self.unlock_in = 60.minutes
|
73
|
+
|
74
|
+
mattr_accessor :reset_attempts_in
|
75
|
+
self.unlock_in = 60.minutes
|
76
|
+
|
77
|
+
mattr_accessor :unlock_url
|
78
|
+
self.unlock_url = nil
|
79
|
+
|
62
80
|
def self.model
|
63
81
|
model_name.constantize
|
64
82
|
end
|
@@ -96,4 +114,9 @@ module RailsJwtAuth
|
|
96
114
|
|
97
115
|
field_name
|
98
116
|
end
|
117
|
+
|
118
|
+
def self.send_email(method, user)
|
119
|
+
mailer = RailsJwtAuth::Mailer.with(user_id: user.id.to_s).public_send(method)
|
120
|
+
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
121
|
+
end
|
99
122
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_jwt_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3
|
4
|
+
version: 1.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rjurado
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt
|
@@ -45,9 +45,6 @@ dependencies:
|
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '5.0'
|
48
|
-
- - "<"
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
version: '6.1'
|
51
48
|
type: :runtime
|
52
49
|
prerelease: false
|
53
50
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -55,11 +52,7 @@ dependencies:
|
|
55
52
|
- - ">="
|
56
53
|
- !ruby/object:Gem::Version
|
57
54
|
version: '5.0'
|
58
|
-
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '6.1'
|
61
|
-
description: Rails authentication solution based on Warden and JWT and inspired by
|
62
|
-
Devise.
|
55
|
+
description: Rails-API authentication solution based on JWT and inspired by Devise.
|
63
56
|
email:
|
64
57
|
- rjurado@openmailbox.org
|
65
58
|
executables: []
|
@@ -77,17 +70,20 @@ files:
|
|
77
70
|
- app/controllers/rails_jwt_auth/passwords_controller.rb
|
78
71
|
- app/controllers/rails_jwt_auth/registrations_controller.rb
|
79
72
|
- app/controllers/rails_jwt_auth/sessions_controller.rb
|
73
|
+
- app/controllers/rails_jwt_auth/unlocks_controller.rb
|
80
74
|
- app/controllers/unauthorized_controller.rb
|
81
75
|
- app/mailers/rails_jwt_auth/mailer.rb
|
82
76
|
- app/models/concerns/rails_jwt_auth/authenticatable.rb
|
83
77
|
- app/models/concerns/rails_jwt_auth/confirmable.rb
|
84
78
|
- app/models/concerns/rails_jwt_auth/invitable.rb
|
79
|
+
- app/models/concerns/rails_jwt_auth/lockable.rb
|
85
80
|
- app/models/concerns/rails_jwt_auth/recoverable.rb
|
86
81
|
- app/models/concerns/rails_jwt_auth/trackable.rb
|
87
82
|
- app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb
|
88
83
|
- app/views/rails_jwt_auth/mailer/email_changed.html.erb
|
89
84
|
- app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb
|
90
85
|
- app/views/rails_jwt_auth/mailer/send_invitation.html.erb
|
86
|
+
- app/views/rails_jwt_auth/mailer/send_unlock_instructions.html.erb
|
91
87
|
- app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb
|
92
88
|
- config/locales/en.yml
|
93
89
|
- lib/generators/rails_jwt_auth/install_generator.rb
|
@@ -118,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
114
|
- !ruby/object:Gem::Version
|
119
115
|
version: '0'
|
120
116
|
requirements: []
|
121
|
-
|
122
|
-
rubygems_version: 2.7.3
|
117
|
+
rubygems_version: 3.1.2
|
123
118
|
signing_key:
|
124
119
|
specification_version: 4
|
125
120
|
summary: Rails jwt authentication.
|