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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ce38e7a38fa015a6dbf8b1504e41fd273cf3646d0b2d9053c63476d55b3c729
4
- data.tar.gz: 9581d2075661754ed5d43f3a344c8d3fd0da631b9b5c8ea8e51b65fa14bb2c33
3
+ metadata.gz: fe3b51e7a2d8f1f7f8debef824a18683878451b1623b824e4b87f7ca8eef3f97
4
+ data.tar.gz: 7f2df300a20ecd34567dc76a1c8f03bd0abe89416056d048374ee9c6d1f786b6
5
5
  SHA512:
6
- metadata.gz: f0bdc862727abcdc1db5d1a3e00bd124b7ac15e45d47e483b8487ba46854dfc68edda3a1dcef1d5cda55fb733840f7679f2bbd03996cae3ee4e775d0f12f5a5d
7
- data.tar.gz: 58cd478adf9a9145fb33e7f531e8010faa6e68f86243478515333acc9aa5578531f047ca059620ae1ce867cff8fbbf7a03de5ceb81e30ede35b9d8360e5392b6
6
+ metadata.gz: 2262fed0d629d5ce892c39e6b3285e510e5817802e38c17375cd16c20ee9f757cc96cb82a0b33e662a968386ffb3674c60e21a538693834251fc9c25f62630c9
7
+ data.tar.gz: '0228cbd71bc2cdf9e77a0c7ec9bd58593c9b9540eaef6e169e779fc7f0499668862e4b8e1b13de0d3e96a1f854ebcffd4d05bd6af9e17f93ad2c34877334a427'
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # RailsJwtAuth
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/rails_jwt_auth.svg)](https://badge.fury.io/rb/rails_jwt_auth)
3
+ ![Gem Version](https://badge.fury.io/rb/rails_jwt_auth.svg)
4
4
  ![Build Status](https://travis-ci.org/rjurado01/rails_jwt_auth.svg?branch=master)
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/auth_token_auth.rb` file created by generator.
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
- | invitationss_url | nil | Url used to create email link with invitation token |
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 5 modules:
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: "user@email.com",
200
- password: "12345678"
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: "user@email.com",
229
- password: "12345678"
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/confirmation,
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/confirmation,
283
+ url: host/confirmations,
266
284
  method: POST,
267
285
  data: {
268
286
  confirmation: {
269
- email: "user@example.com"
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/password,
301
+ url: host/passwords,
284
302
  method: POST,
285
303
  data: {
286
304
  password: {
287
- email: "user@example.com"
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/password,
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: "user@example.com",
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
- current_user.update_with_password(update_params)
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, :type => :controller
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 "blocks unauthenticated access" do
471
- expect { get :index }.to raise_error(RailsJwtAuth::Errors::NotAuthorized)
504
+ it 'blocks unauthenticated access' do
505
+ expect { get :index }.to raise_error(RailsJwtAuth::NotAuthorized)
472
506
  end
473
507
 
474
- it "allows authenticated access" do
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
- payload = RailsJwtAuth::JwtManager.decode_from_request(request).first
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(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
- payload = RailsJwtAuth::JwtManager.decode_from_request(request).first
30
- @current_user = RailsJwtAuth.model.from_token_payload(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(:email)
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(:email)
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(:email)
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(email: confirmation_create_params[:email]).first
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,6 +4,7 @@ module RailsJwtAuth
4
4
  include RenderHelper
5
5
 
6
6
  def create
7
+ authenticate!
7
8
  user = RailsJwtAuth.model.invite!(invitation_create_params)
8
9
  user.errors.empty? ? render_204 : render_422(user.errors.details)
9
10
  end
@@ -4,8 +4,17 @@ module RailsJwtAuth
4
4
  include RenderHelper
5
5
 
6
6
  def create
7
- user = RailsJwtAuth.model.where(email: password_create_params[:email].to_s.downcase).first
8
- return render_422(email: [{error: :not_found}]) unless user
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.authenticate(session_create_params[:password])
13
+ elsif user.authentication?(session_create_params[:password])
14
14
  render_session generate_jwt(user), user
15
15
  else
16
- render_422 session: [{error: :invalid_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
- def confirmation_instructions(user)
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
- url, params = RailsJwtAuth.confirmations_url.split('?')
10
- params = params ? params.split('&') : []
11
- params.push("confirmation_token=#{@user.confirmation_token}")
12
- @confirmations_url = "#{url}?#{params.join('&')}"
13
+ @confirmations_url = add_param_to_url(
14
+ RailsJwtAuth.confirmations_url,
15
+ 'confirmation_token',
16
+ @user.confirmation_token
17
+ )
13
18
 
14
- subject = I18n.t('rails_jwt_auth.mailer.confirmation_instructions.subject')
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(user)
19
- @user = 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(user)
26
+ def reset_password_instructions
25
27
  raise RailsJwtAuth::NotResetPasswordsUrl unless RailsJwtAuth.reset_passwords_url.present?
26
- @user = user
27
28
 
28
- url, params = RailsJwtAuth.reset_passwords_url.split('?')
29
- params = params ? params.split('&') : []
30
- params.push("reset_password_token=#{@user.reset_password_token}")
31
- @reset_passwords_url = "#{url}?#{params.join('&')}"
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
- subject = I18n.t('rails_jwt_auth.mailer.reset_password_instructions.subject')
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(user)
38
+ def set_password_instructions
38
39
  raise RailsJwtAuth::NotSetPasswordsUrl unless RailsJwtAuth.set_passwords_url.present?
39
- @user = user
40
40
 
41
- url, params = RailsJwtAuth.set_passwords_url.split('?')
42
- params = params ? params.split('&') : []
43
- params.push("reset_password_token=#{@user.reset_password_token}")
44
- @reset_passwords_url = "#{url}?#{params.join('&')}"
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
- subject = I18n.t('rails_jwt_auth.mailer.set_password_instructions.subject')
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(user)
50
+ def send_invitation
51
51
  raise RailsJwtAuth::NotInvitationsUrl unless RailsJwtAuth.invitations_url.present?
52
- @user = user
53
52
 
54
- url, params = RailsJwtAuth.invitations_url.split '?'
55
- params = params ? params.split('&') : []
56
- params.push("invitation_token=#{@user.invitation_token}")
57
- @invitations_url = "#{url}?#{params.join('&')}"
53
+ @invitations_url = add_param_to_url(
54
+ RailsJwtAuth.invitations_url,
55
+ 'invitation_token',
56
+ @user.invitation_token
57
+ )
58
58
 
59
- subject = I18n.t('rails_jwt_auth.mailer.send_invitation.subject')
60
- mail(to: @user[RailsJwtAuth.email_field_name], subject: subject)
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
- # abort reset password if exists to allow save
50
- self.reset_password_token = self.reset_password_sent_at = nil if reset_password_token
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
- mailer = Mailer.confirmation_instructions(self)
38
- RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
39
-
40
- if RailsJwtAuth.send_email_changed_notification
41
- mailer = Mailer.email_changed(self)
42
- RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
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
- mailer = Mailer.confirmation_instructions(self)
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
- self[RailsJwtAuth.email_field_name!] = unconfirmed_email
76
- self.email_confirmation = unconfirmed_email if respond_to?(:email_confirmation)
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 es valid
116
- mailer = Mailer.send_invitation(self)
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
- self.reset_password_token = nil if password_digest_changed? && reset_password_token
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
- mailer = Mailer.reset_password_instructions(self)
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
- mailer = Mailer.set_password_instructions(self)
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>
@@ -11,3 +11,5 @@ en:
11
11
  subject: "Someone has sent you an invitation!"
12
12
  email_changed:
13
13
  subject: "Email changed"
14
+ send_unlock_instructions:
15
+ subject: "Unlock instructions"
@@ -6,11 +6,12 @@ class RailsJwtAuth::InstallGenerator < Rails::Generators::Base
6
6
  end
7
7
 
8
8
  def create_routes
9
- route "resources :session, controller: 'rails_jwt_auth/sessions', only: [:create, :destroy]"
10
- route "resources :registration, controller: 'rails_jwt_auth/registrations', only: [:create, :update, :destroy]"
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
@@ -1,3 +1,3 @@
1
1
  module RailsJwtAuth
2
- VERSION = '1.3.1'
2
+ VERSION = '1.7.3'
3
3
  end
@@ -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.1
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: 2019-08-19 00:00:00.000000000 Z
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
- rubyforge_project:
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.