rails_jwt_auth 1.3.1 → 1.7.3

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