rails_jwt_auth 1.7.2 → 2.0.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +188 -89
  3. data/app/controllers/concerns/rails_jwt_auth/authenticable_helper.rb +15 -7
  4. data/app/controllers/concerns/rails_jwt_auth/params_helper.rb +18 -4
  5. data/app/controllers/concerns/rails_jwt_auth/render_helper.rb +10 -2
  6. data/app/controllers/rails_jwt_auth/confirmations_controller.rb +48 -10
  7. data/app/controllers/rails_jwt_auth/invitations_controller.rb +27 -9
  8. data/app/controllers/rails_jwt_auth/profiles_controller.rb +51 -0
  9. data/app/controllers/rails_jwt_auth/reset_passwords_controller.rb +65 -0
  10. data/app/controllers/rails_jwt_auth/sessions_controller.rb +7 -22
  11. data/app/controllers/rails_jwt_auth/{unlocks_controller.rb → unlock_accounts_controller.rb} +2 -2
  12. data/app/mailers/rails_jwt_auth/mailer.rb +23 -28
  13. data/app/models/concerns/rails_jwt_auth/authenticatable.rb +60 -19
  14. data/app/models/concerns/rails_jwt_auth/confirmable.rb +49 -39
  15. data/app/models/concerns/rails_jwt_auth/invitable.rb +46 -72
  16. data/app/models/concerns/rails_jwt_auth/lockable.rb +38 -46
  17. data/app/models/concerns/rails_jwt_auth/recoverable.rb +27 -26
  18. data/app/models/concerns/rails_jwt_auth/trackable.rb +13 -2
  19. data/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb +1 -1
  20. data/app/views/rails_jwt_auth/mailer/{email_changed.html.erb → email_change_requested_notification.html.erb} +0 -0
  21. data/app/views/rails_jwt_auth/mailer/{send_invitation.html.erb → invitation_instructions.html.erb} +1 -1
  22. data/app/views/rails_jwt_auth/mailer/password_changed_notification.html.erb +3 -0
  23. data/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb +1 -1
  24. data/app/views/rails_jwt_auth/mailer/{send_unlock_instructions.html.erb → unlock_instructions.html.erb} +1 -1
  25. data/config/locales/en.yml +6 -6
  26. data/lib/generators/rails_jwt_auth/install_generator.rb +11 -3
  27. data/lib/generators/templates/initializer.rb +43 -29
  28. data/lib/generators/templates/migration.rb +2 -1
  29. data/lib/rails_jwt_auth.rb +46 -47
  30. data/lib/rails_jwt_auth/jwt_manager.rb +2 -4
  31. data/lib/rails_jwt_auth/session.rb +128 -0
  32. data/lib/rails_jwt_auth/version.rb +1 -1
  33. metadata +11 -15
  34. data/app/controllers/rails_jwt_auth/passwords_controller.rb +0 -32
  35. data/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb +0 -5
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsJwtAuth
2
4
  module Confirmable
3
5
  def self.included(base)
@@ -20,47 +22,16 @@ module RailsJwtAuth
20
22
  send_confirmation_instructions
21
23
  end
22
24
  end
23
-
24
- before_update do
25
- email_field = RailsJwtAuth.email_field_name!
26
-
27
- if public_send("#{email_field}_changed?") &&
28
- public_send("#{email_field}_was") &&
29
- !confirmed_at_changed? &&
30
- !self['invitation_token']
31
- self.unconfirmed_email = self[email_field]
32
- self[email_field] = public_send("#{email_field}_was")
33
-
34
- self.confirmation_token = SecureRandom.base58(24)
35
- self.confirmation_sent_at = Time.current
36
- end
37
- end
38
-
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
49
- end
50
- end
51
- end
52
25
  end
53
26
  end
54
27
 
55
28
  def send_confirmation_instructions
56
- email_field = RailsJwtAuth.email_field_name!
57
-
58
29
  if confirmed? && !unconfirmed_email
59
- errors.add(email_field, :already_confirmed)
30
+ errors.add(RailsJwtAuth.email_field_name, :already_confirmed)
60
31
  return false
61
32
  end
62
33
 
63
- self.confirmation_token = SecureRandom.base58(24)
34
+ self.confirmation_token = generate_confirmation_token
64
35
  self.confirmation_sent_at = Time.current
65
36
  return false unless save
66
37
 
@@ -72,12 +43,12 @@ module RailsJwtAuth
72
43
  confirmed_at.present?
73
44
  end
74
45
 
75
- def confirm!
46
+ def confirm
76
47
  self.confirmed_at = Time.current
77
48
  self.confirmation_token = nil
78
49
 
79
50
  if unconfirmed_email
80
- email_field = RailsJwtAuth.email_field_name!
51
+ email_field = RailsJwtAuth.email_field_name
81
52
 
82
53
  self[email_field] = unconfirmed_email
83
54
  self.unconfirmed_email = nil
@@ -91,17 +62,56 @@ module RailsJwtAuth
91
62
  save
92
63
  end
93
64
 
94
- def skip_confirmation!
65
+ def skip_confirmation
95
66
  self.confirmed_at = Time.current
96
67
  self.confirmation_token = nil
97
68
  end
98
69
 
70
+ def update_email(params)
71
+ email_field = RailsJwtAuth.email_field_name.to_sym
72
+ params = HashWithIndifferentAccess.new(params)
73
+
74
+ # email change must be protected by password
75
+ password_error = if (password = params[:password]).blank?
76
+ :blank
77
+ elsif !authenticate(password)
78
+ :invalid
79
+ end
80
+
81
+ self.email = params[email_field]
82
+ self.confirmation_token = generate_confirmation_token
83
+ self.confirmation_sent_at = Time.current
84
+
85
+ valid? # validates first other fields
86
+ errors.add(:password, password_error) if password_error
87
+ errors.add(email_field, :not_change) unless email_changed?
88
+
89
+ return false unless errors.empty?
90
+
91
+ # move email to unconfirmed_email field and restore
92
+ self.unconfirmed_email = email
93
+ self.email = email_was
94
+
95
+ return false unless save
96
+
97
+ deliver_email_changed_emails
98
+
99
+ true
100
+ end
101
+
99
102
  protected
100
103
 
104
+ def generate_confirmation_token
105
+ loop do
106
+ token = RailsJwtAuth.friendly_token
107
+ return token unless self.class.where(confirmation_token: token).exists?
108
+ end
109
+ end
110
+
101
111
  def validate_confirmation
102
112
  return true unless confirmed_at
103
113
 
104
- email_field = RailsJwtAuth.email_field_name!
114
+ email_field = RailsJwtAuth.email_field_name
105
115
 
106
116
  if confirmed_at_was && !public_send("#{email_field}_changed?")
107
117
  errors.add(email_field, :already_confirmed)
@@ -116,8 +126,8 @@ module RailsJwtAuth
116
126
  RailsJwtAuth.send_email(:confirmation_instructions, self)
117
127
 
118
128
  # send notify to old email
119
- if RailsJwtAuth.send_email_changed_notification
120
- RailsJwtAuth.send_email(:email_changed, self)
129
+ if RailsJwtAuth.send_email_change_requested_notification
130
+ RailsJwtAuth.send_email(:email_change_requested_notification, self)
121
131
  end
122
132
  end
123
133
  end
@@ -11,115 +11,89 @@ module RailsJwtAuth
11
11
  field :invitation_token, type: String
12
12
  field :invitation_sent_at, type: Time
13
13
  field :invitation_accepted_at, type: Time
14
- field :invitation_created_at, type: Time
15
14
  end
16
15
  end
17
16
  end
18
17
 
19
18
  module ClassMethods
20
19
  # Creates an user and sends an invitation to him.
21
- # If the user is already invited and pending of completing registration
22
- # the invitation is resent by email.
23
- # If the user is already registered, it returns the user with a
24
- # <tt>:taken</tt> on the email field.
25
- #
26
- # @param [Hash] attributes Hash containing user's attributes to be filled.
27
- # Must contain an email key.
28
- #
29
- # @return [user] The user created or found by email.
30
- def invite!(attributes={})
20
+ def invite(attributes={})
31
21
  attrs = ActiveSupport::HashWithIndifferentAccess.new(attributes.to_h)
32
- auth_field = RailsJwtAuth.auth_field_name!
22
+ auth_field = RailsJwtAuth.auth_field_name
33
23
  auth_attribute = attrs.delete(auth_field)
34
24
 
35
- raise ArgumentError unless auth_attribute
36
-
37
25
  record = RailsJwtAuth.model.find_or_initialize_by(auth_field => auth_attribute)
38
26
  record.assign_attributes(attrs)
39
27
 
40
- record.invite!
28
+ record.invite
41
29
  record
42
30
  end
43
31
  end
44
32
 
45
- # Accept an invitation by clearing token and setting invitation_accepted_at
46
- def accept_invitation
47
- self.invitation_accepted_at = Time.current
48
- self.invitation_token = nil
49
- end
50
-
51
- def accept_invitation!
52
- return unless invited?
53
-
54
- if valid_invitation?
55
- accept_invitation
56
- self.confirmed_at = Time.current if respond_to?(:confirmed_at) && confirmed_at.nil?
57
- else
58
- errors.add(:invitation_token, :invalid)
33
+ # Sends an invitation to user
34
+ # If the user has pending invitation, new one is sent
35
+ def invite
36
+ if persisted? && !invitation_token
37
+ errors.add(RailsJwtAuth.auth_field_name, :registered)
38
+ return false
59
39
  end
60
- end
61
40
 
62
- def invite!
63
- self.invitation_created_at = Time.current if new_record?
41
+ @inviting = true
42
+ self.invitation_token = generate_invitation_token
43
+ self.invitation_sent_at = Time.current
64
44
 
65
- unless password || password_digest
66
- passw = SecureRandom.base58(16)
67
- self.password = passw
68
- self.password_confirmation = passw
69
- end
45
+ return false unless save_without_password
70
46
 
71
- valid?
47
+ RailsJwtAuth.send_email(:invitation_instructions, self)
48
+ true
49
+ ensure
50
+ @inviting = false
51
+ end
72
52
 
73
- # users that are registered and were not invited are not reinvitable
74
- if !new_record? && !invited?
75
- errors.add(RailsJwtAuth.auth_field_name!, :taken)
76
- end
53
+ # Finishes invitation process setting user password
54
+ def accept_invitation(params)
55
+ return false unless invitation_token.present?
77
56
 
78
- # users that have already accepted an invitation are not reinvitable
79
- if !new_record? && invited? && invitation_accepted_at.present?
80
- errors.add(RailsJwtAuth.auth_field_name!, :taken)
81
- end
57
+ self.assign_attributes(params)
82
58
 
83
- return self unless errors.empty?
59
+ valid?
60
+ errors.add(:password, :blank) if params[:password].blank?
61
+ errors.add(:invitation_token, :expired) if expired_invitation_token?
84
62
 
85
- generate_invitation_token if invitation_token.nil?
86
- self.invitation_sent_at = Time.current
63
+ return false unless errors.empty?
87
64
 
88
- send_invitation_mail if save(validate: false)
89
- self
65
+ self.invitation_accepted_at = Time.current
66
+ self.invitation_token = nil
67
+ self.invitation_sent_at = nil
68
+ self.confirmed_at = Time.current if respond_to?(:confirmed_at) && confirmed_at.nil?
69
+ save
90
70
  end
91
71
 
92
- def invited?
93
- (persisted? && invitation_token.present?)
72
+ def inviting?
73
+ @inviting || false
94
74
  end
95
75
 
96
- def generate_invitation_token!
97
- generate_invitation_token && save(validate: false)
76
+ def valid_for_invite?
77
+ @inviting = true
78
+ valid_without_password?
79
+ ensure
80
+ @inviting = false
98
81
  end
99
82
 
100
- def valid_invitation?
101
- invited? && invitation_period_valid?
102
- end
83
+ def expired_invitation_token?
84
+ expiration_time = RailsJwtAuth.invitation_expiration_time
85
+ return false if expiration_time.to_i.zero?
103
86
 
104
- def accepted_invitation?
105
- invitation_token.nil? && invitation_accepted_at.present?
87
+ invitation_sent_at && invitation_sent_at < expiration_time.ago
106
88
  end
107
89
 
108
90
  protected
109
91
 
110
92
  def generate_invitation_token
111
- self.invitation_token = SecureRandom.base58(128)
112
- end
113
-
114
- def send_invitation_mail
115
- RailsJwtAuth.email_field_name! # ensure email field is valid
116
- RailsJwtAuth.send_email(:send_invitation, self)
117
- end
118
-
119
- def invitation_period_valid?
120
- time = invitation_sent_at || invitation_created_at
121
- expiration_time = RailsJwtAuth.invitation_expiration_time
122
- time && (expiration_time.to_i.zero? || time >= expiration_time.ago)
93
+ loop do
94
+ token = RailsJwtAuth.friendly_token
95
+ return token unless self.class.where(invitation_token: token).exists?
96
+ end
123
97
  end
124
98
  end
125
99
  end
@@ -13,66 +13,51 @@ module RailsJwtAuth
13
13
  end
14
14
  end
15
15
 
16
- def lock_access!
17
- self.locked_at = Time.now.utc
16
+ def lock_access
17
+ self.locked_at = Time.current
18
+
18
19
  save(validate: false).tap do |result|
19
20
  send_unlock_instructions if result && unlock_strategy_enabled?(:email)
20
21
  end
21
22
  end
22
23
 
23
- def unlock_access!
24
+ def clean_lock
24
25
  self.locked_at = nil
25
- self.failed_attempts = 0
26
- self.first_failed_attempt_at = nil
27
26
  self.unlock_token = nil
28
- save(validate: false)
27
+ reset_attempts
29
28
  end
30
29
 
31
- def reset_attempts!
32
- self.failed_attempts = 0
33
- self.first_failed_attempt_at = nil
34
- save(validate: false)
30
+ def unlock_access
31
+ clean_lock
32
+
33
+ save(validate: false) if changed?
35
34
  end
36
35
 
37
- def authentication?(pass)
38
- return super(pass) unless lock_strategy_enabled?(:failed_attempts)
36
+ def access_locked?
37
+ locked_at && !lock_expired?
38
+ end
39
39
 
40
- reset_attempts! if !access_locked? && attempts_expired?
41
- unlock_access! if lock_expired?
40
+ def failed_attempt
41
+ return if access_locked?
42
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
43
+ reset_attempts if attempts_expired?
54
44
 
55
- def unauthenticated_error
56
- return super unless lock_strategy_enabled?(:failed_attempts)
45
+ self.failed_attempts ||= 0
46
+ self.failed_attempts += 1
47
+ self.first_failed_attempt_at = Time.current if failed_attempts == 1
57
48
 
58
- if access_locked?
59
- {error: :locked}
60
- else
61
- {error: :invalid_session, remaining_attempts: remaining_attempts}
49
+ save(validate: false).tap do |result|
50
+ lock_access if result && attempts_exceeded?
62
51
  end
63
52
  end
64
53
 
65
54
  protected
66
55
 
67
56
  def send_unlock_instructions
68
- self.unlock_token = SecureRandom.base58(24)
57
+ self.unlock_token = generate_unlock_token
69
58
  save(validate: false)
70
59
 
71
- RailsJwtAuth.send_email(:send_unlock_instructions, self)
72
- end
73
-
74
- def access_locked?
75
- locked_at && !lock_expired?
60
+ RailsJwtAuth.send_email(:unlock_instructions, self)
76
61
  end
77
62
 
78
63
  def lock_expired?
@@ -83,25 +68,32 @@ module RailsJwtAuth
83
68
  end
84
69
  end
85
70
 
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
71
+ def reset_attempts
72
+ self.failed_attempts = 0
73
+ self.first_failed_attempt_at = nil
95
74
  end
96
75
 
97
76
  def remaining_attempts
98
77
  RailsJwtAuth.maximum_attempts - failed_attempts.to_i
99
78
  end
100
79
 
80
+ def attempts_exceeded?
81
+ !remaining_attempts.positive?
82
+ end
83
+
101
84
  def attempts_expired?
102
85
  first_failed_attempt_at && first_failed_attempt_at < RailsJwtAuth.reset_attempts_in.ago
103
86
  end
104
87
 
88
+ protected
89
+
90
+ def generate_unlock_token
91
+ loop do
92
+ token = RailsJwtAuth.friendly_token
93
+ return token unless self.class.where(unlock_token: token).exists?
94
+ end
95
+ end
96
+
105
97
  def lock_strategy_enabled?(strategy)
106
98
  RailsJwtAuth.lock_strategy == strategy
107
99
  end
@@ -10,20 +10,11 @@ module RailsJwtAuth
10
10
  field :reset_password_token, type: String
11
11
  field :reset_password_sent_at, type: Time
12
12
  end
13
-
14
- validate :validate_reset_password_token, if: :password_digest_changed?
15
-
16
- before_update do
17
- if password_digest_changed? && reset_password_token
18
- self.reset_password_token = nil
19
- self.auth_tokens = []
20
- end
21
- end
22
13
  end
23
14
  end
24
15
 
25
16
  def send_reset_password_instructions
26
- email_field = RailsJwtAuth.email_field_name! # ensure email field es valid
17
+ email_field = RailsJwtAuth.email_field_name # ensure email field es valid
27
18
 
28
19
  if self.class.ancestors.include?(RailsJwtAuth::Confirmable) && !confirmed?
29
20
  errors.add(email_field, :unconfirmed)
@@ -36,35 +27,45 @@ module RailsJwtAuth
36
27
  return false
37
28
  end
38
29
 
39
- self.reset_password_token = SecureRandom.base58(24)
30
+ self.reset_password_token = generate_reset_password_token
40
31
  self.reset_password_sent_at = Time.current
41
32
  return false unless save
42
33
 
43
34
  RailsJwtAuth.send_email(:reset_password_instructions, self)
44
35
  end
45
36
 
46
- def set_and_send_password_instructions
47
- RailsJwtAuth.email_field_name! # ensure email field es valid
48
- return if password.present?
37
+ def set_reset_password(params)
38
+ self.assign_attributes(params)
49
39
 
50
- self.password = SecureRandom.base58(48)
51
- self.password_confirmation = self.password
52
- self.skip_confirmation! if self.class.ancestors.include?(RailsJwtAuth::Confirmable)
40
+ valid?
41
+ errors.add(:password, :blank) if params[:password].blank?
42
+ errors.add(:reset_password_token, :expired) if expired_reset_password_token?
53
43
 
54
- self.reset_password_token = SecureRandom.base58(24)
55
- self.reset_password_sent_at = Time.current
56
- return false unless save
44
+ return false unless errors.empty?
45
+
46
+ clean_reset_password
47
+ self.auth_tokens = [] # reset all sessions
48
+ save
49
+ end
50
+
51
+ def expired_reset_password_token?
52
+ expiration_time = RailsJwtAuth.reset_password_expiration_time
53
+ return false if expiration_time.to_i.zero?
54
+
55
+ reset_password_sent_at && reset_password_sent_at < expiration_time.ago
56
+ end
57
57
 
58
- RailsJwtAuth.send_email(:set_password_instructions, self)
59
- true
58
+ def clean_reset_password
59
+ self.reset_password_sent_at = nil
60
+ self.reset_password_token = nil
60
61
  end
61
62
 
62
63
  protected
63
64
 
64
- def validate_reset_password_token
65
- if reset_password_sent_at &&
66
- (reset_password_sent_at < (Time.current - RailsJwtAuth.reset_password_expiration_time))
67
- errors.add(:reset_password_token, :expired)
65
+ def generate_reset_password_token
66
+ loop do
67
+ token = RailsJwtAuth.friendly_token
68
+ return token unless self.class.where(reset_password_token: token).exists?
68
69
  end
69
70
  end
70
71
  end