rails_jwt_auth 1.6.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +186 -87
- data/app/controllers/concerns/rails_jwt_auth/authenticable_helper.rb +15 -7
- data/app/controllers/concerns/rails_jwt_auth/params_helper.rb +19 -5
- data/app/controllers/concerns/rails_jwt_auth/render_helper.rb +10 -2
- data/app/controllers/rails_jwt_auth/confirmations_controller.rb +48 -10
- data/app/controllers/rails_jwt_auth/invitations_controller.rb +26 -9
- data/app/controllers/rails_jwt_auth/profiles_controller.rb +50 -0
- data/app/controllers/rails_jwt_auth/reset_passwords_controller.rb +65 -0
- data/app/controllers/rails_jwt_auth/sessions_controller.rb +5 -21
- data/app/controllers/rails_jwt_auth/{unlocks_controller.rb → unlock_accounts_controller.rb} +2 -2
- data/app/mailers/rails_jwt_auth/mailer.rb +29 -42
- data/app/models/concerns/rails_jwt_auth/authenticatable.rb +59 -18
- data/app/models/concerns/rails_jwt_auth/confirmable.rb +58 -33
- data/app/models/concerns/rails_jwt_auth/invitable.rb +42 -78
- data/app/models/concerns/rails_jwt_auth/lockable.rb +28 -46
- data/app/models/concerns/rails_jwt_auth/recoverable.rb +21 -31
- data/app/models/concerns/rails_jwt_auth/trackable.rb +13 -2
- data/app/views/rails_jwt_auth/mailer/confirmation_instructions.html.erb +1 -1
- data/app/views/rails_jwt_auth/mailer/{email_changed.html.erb → email_change_requested_notification.html.erb} +0 -0
- data/app/views/rails_jwt_auth/mailer/{send_invitation.html.erb → invitation_instructions.html.erb} +1 -1
- data/app/views/rails_jwt_auth/mailer/password_changed_notification.html.erb +3 -0
- data/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb +1 -1
- data/app/views/rails_jwt_auth/mailer/{send_unlock_instructions.html.erb → unlock_instructions.html.erb} +1 -1
- data/config/locales/en.yml +6 -6
- data/lib/generators/rails_jwt_auth/install_generator.rb +11 -3
- data/lib/generators/templates/initializer.rb +43 -29
- data/lib/generators/templates/migration.rb +2 -1
- data/lib/rails_jwt_auth.rb +47 -45
- data/lib/rails_jwt_auth/jwt_manager.rb +0 -4
- data/lib/rails_jwt_auth/session.rb +132 -0
- data/lib/rails_jwt_auth/version.rb +1 -1
- metadata +10 -8
- data/app/controllers/rails_jwt_auth/passwords_controller.rb +0 -28
- 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,37 +22,12 @@ 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
|
-
|
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
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
25
|
end
|
47
26
|
end
|
48
27
|
|
49
28
|
def send_confirmation_instructions
|
50
|
-
email_field = RailsJwtAuth.email_field_name!
|
51
|
-
|
52
29
|
if confirmed? && !unconfirmed_email
|
53
|
-
errors.add(
|
30
|
+
errors.add(RailsJwtAuth.email_field_name, :already_confirmed)
|
54
31
|
return false
|
55
32
|
end
|
56
33
|
|
@@ -58,8 +35,7 @@ module RailsJwtAuth
|
|
58
35
|
self.confirmation_sent_at = Time.current
|
59
36
|
return false unless save
|
60
37
|
|
61
|
-
|
62
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
38
|
+
RailsJwtAuth.send_email(:confirmation_instructions, self)
|
63
39
|
true
|
64
40
|
end
|
65
41
|
|
@@ -67,29 +43,68 @@ module RailsJwtAuth
|
|
67
43
|
confirmed_at.present?
|
68
44
|
end
|
69
45
|
|
70
|
-
def confirm
|
46
|
+
def confirm
|
71
47
|
self.confirmed_at = Time.current
|
72
48
|
self.confirmation_token = nil
|
73
49
|
|
74
50
|
if unconfirmed_email
|
75
|
-
|
76
|
-
|
51
|
+
email_field = RailsJwtAuth.email_field_name
|
52
|
+
|
53
|
+
self[email_field] = unconfirmed_email
|
77
54
|
self.unconfirmed_email = nil
|
55
|
+
|
56
|
+
# supports email confirmation attr_accessor validation
|
57
|
+
if respond_to?("#{email_field}_confirmation")
|
58
|
+
instance_variable_set("@#{email_field}_confirmation", self[email_field])
|
59
|
+
end
|
78
60
|
end
|
79
61
|
|
80
62
|
save
|
81
63
|
end
|
82
64
|
|
83
|
-
def skip_confirmation
|
65
|
+
def skip_confirmation
|
84
66
|
self.confirmed_at = Time.current
|
85
67
|
self.confirmation_token = nil
|
86
68
|
end
|
87
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 = SecureRandom.base58(24)
|
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
|
+
|
88
102
|
protected
|
89
103
|
|
90
104
|
def validate_confirmation
|
91
105
|
return true unless confirmed_at
|
92
|
-
|
106
|
+
|
107
|
+
email_field = RailsJwtAuth.email_field_name
|
93
108
|
|
94
109
|
if confirmed_at_was && !public_send("#{email_field}_changed?")
|
95
110
|
errors.add(email_field, :already_confirmed)
|
@@ -98,5 +113,15 @@ module RailsJwtAuth
|
|
98
113
|
errors.add(:confirmation_token, :expired)
|
99
114
|
end
|
100
115
|
end
|
116
|
+
|
117
|
+
def deliver_email_changed_emails
|
118
|
+
# send confirmation to new email
|
119
|
+
RailsJwtAuth.send_email(:confirmation_instructions, self)
|
120
|
+
|
121
|
+
# send notify to old email
|
122
|
+
if RailsJwtAuth.send_email_change_requested_notification
|
123
|
+
RailsJwtAuth.send_email(:email_change_requested_notification, self)
|
124
|
+
end
|
125
|
+
end
|
101
126
|
end
|
102
127
|
end
|
@@ -11,116 +11,80 @@ 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
|
-
|
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
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def invite!
|
63
|
-
self.invitation_created_at = Time.current if new_record?
|
64
|
-
|
65
|
-
unless password || password_digest
|
66
|
-
passw = SecureRandom.base58(16)
|
67
|
-
self.password = passw
|
68
|
-
self.password_confirmation = passw
|
69
|
-
end
|
70
|
-
|
71
|
-
valid?
|
72
|
-
|
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)
|
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
|
76
39
|
end
|
77
40
|
|
78
|
-
|
79
|
-
|
80
|
-
errors.add(RailsJwtAuth.auth_field_name!, :taken)
|
81
|
-
end
|
82
|
-
|
83
|
-
return self unless errors.empty?
|
84
|
-
|
85
|
-
generate_invitation_token if invitation_token.nil?
|
41
|
+
@inviting = true
|
42
|
+
self.invitation_token = RailsJwtAuth.friendly_token
|
86
43
|
self.invitation_sent_at = Time.current
|
87
44
|
|
88
|
-
|
89
|
-
self
|
90
|
-
end
|
45
|
+
return false unless save_without_password
|
91
46
|
|
92
|
-
|
93
|
-
|
47
|
+
RailsJwtAuth.send_email(:invitation_instructions, self)
|
48
|
+
true
|
49
|
+
ensure
|
50
|
+
@inviting = false
|
94
51
|
end
|
95
52
|
|
96
|
-
|
97
|
-
|
98
|
-
|
53
|
+
# Finishes invitation process setting user password
|
54
|
+
def accept_invitation(params)
|
55
|
+
return false unless invitation_token.present?
|
99
56
|
|
100
|
-
|
101
|
-
invited? && invitation_period_valid?
|
102
|
-
end
|
57
|
+
self.assign_attributes(params)
|
103
58
|
|
104
|
-
|
105
|
-
|
106
|
-
|
59
|
+
valid?
|
60
|
+
errors.add(:password, :blank) if params[:password].blank?
|
61
|
+
errors.add(:invitation_token, :expired) if expired_invitation_token?
|
62
|
+
|
63
|
+
return false unless errors.empty?
|
107
64
|
|
108
|
-
|
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
|
70
|
+
end
|
109
71
|
|
110
|
-
def
|
111
|
-
|
72
|
+
def inviting?
|
73
|
+
@inviting || false
|
112
74
|
end
|
113
75
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
|
76
|
+
def valid_for_invite?
|
77
|
+
@inviting = true
|
78
|
+
valid_without_password?
|
79
|
+
ensure
|
80
|
+
@inviting = false
|
118
81
|
end
|
119
82
|
|
120
|
-
def
|
121
|
-
time = invitation_sent_at || invitation_created_at
|
83
|
+
def expired_invitation_token?
|
122
84
|
expiration_time = RailsJwtAuth.invitation_expiration_time
|
123
|
-
|
85
|
+
return false if expiration_time.to_i.zero?
|
86
|
+
|
87
|
+
invitation_sent_at && invitation_sent_at < expiration_time.ago
|
124
88
|
end
|
125
89
|
end
|
126
90
|
end
|
@@ -13,52 +13,41 @@ module RailsJwtAuth
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def lock_access
|
17
|
-
self.locked_at = Time.
|
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
|
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
|
-
|
27
|
+
reset_attempts
|
29
28
|
end
|
30
29
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
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
|
38
|
-
|
36
|
+
def access_locked?
|
37
|
+
locked_at && !lock_expired?
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
def failed_attempt
|
41
|
+
return if access_locked?
|
42
42
|
|
43
|
-
if
|
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
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
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
|
|
@@ -68,12 +57,7 @@ module RailsJwtAuth
|
|
68
57
|
self.unlock_token = SecureRandom.base58(24)
|
69
58
|
save(validate: false)
|
70
59
|
|
71
|
-
|
72
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
73
|
-
end
|
74
|
-
|
75
|
-
def access_locked?
|
76
|
-
locked_at && !lock_expired?
|
60
|
+
RailsJwtAuth.send_email(:unlock_instructions, self)
|
77
61
|
end
|
78
62
|
|
79
63
|
def lock_expired?
|
@@ -84,21 +68,19 @@ module RailsJwtAuth
|
|
84
68
|
end
|
85
69
|
end
|
86
70
|
|
87
|
-
def
|
88
|
-
self.failed_attempts
|
89
|
-
self.
|
90
|
-
self.first_failed_attempt_at = Time.now.utc if failed_attempts == 1
|
91
|
-
save(validate: false)
|
92
|
-
end
|
93
|
-
|
94
|
-
def attempts_exceeded?
|
95
|
-
failed_attempts && failed_attempts >= RailsJwtAuth.maximum_attempts
|
71
|
+
def reset_attempts
|
72
|
+
self.failed_attempts = 0
|
73
|
+
self.first_failed_attempt_at = nil
|
96
74
|
end
|
97
75
|
|
98
76
|
def remaining_attempts
|
99
77
|
RailsJwtAuth.maximum_attempts - failed_attempts.to_i
|
100
78
|
end
|
101
79
|
|
80
|
+
def attempts_exceeded?
|
81
|
+
!remaining_attempts.positive?
|
82
|
+
end
|
83
|
+
|
102
84
|
def attempts_expired?
|
103
85
|
first_failed_attempt_at && first_failed_attempt_at < RailsJwtAuth.reset_attempts_in.ago
|
104
86
|
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
|
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,38 +27,37 @@ module RailsJwtAuth
|
|
36
27
|
return false
|
37
28
|
end
|
38
29
|
|
39
|
-
self.reset_password_token =
|
30
|
+
self.reset_password_token = RailsJwtAuth.friendly_token
|
40
31
|
self.reset_password_sent_at = Time.current
|
41
32
|
return false unless save
|
42
33
|
|
43
|
-
|
44
|
-
RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
|
34
|
+
RailsJwtAuth.send_email(:reset_password_instructions, self)
|
45
35
|
end
|
46
36
|
|
47
|
-
def
|
48
|
-
|
49
|
-
return if password.present?
|
37
|
+
def set_reset_password(params)
|
38
|
+
self.assign_attributes(params)
|
50
39
|
|
51
|
-
|
52
|
-
|
53
|
-
|
40
|
+
valid?
|
41
|
+
errors.add(:password, :blank) if params[:password].blank?
|
42
|
+
errors.add(:reset_password_token, :expired) if expired_reset_password_token?
|
54
43
|
|
55
|
-
|
56
|
-
self.reset_password_sent_at = Time.current
|
57
|
-
return false unless save
|
44
|
+
return false unless errors.empty?
|
58
45
|
|
59
|
-
|
60
|
-
|
61
|
-
|
46
|
+
clean_reset_password
|
47
|
+
self.auth_tokens = [] # reset all sessions
|
48
|
+
save
|
62
49
|
end
|
63
50
|
|
64
|
-
|
51
|
+
def expired_reset_password_token?
|
52
|
+
expiration_time = RailsJwtAuth.reset_password_expiration_time
|
53
|
+
return false if expiration_time.to_i.zero?
|
65
54
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
55
|
+
reset_password_sent_at && reset_password_sent_at < expiration_time.ago
|
56
|
+
end
|
57
|
+
|
58
|
+
def clean_reset_password
|
59
|
+
self.reset_password_sent_at = nil
|
60
|
+
self.reset_password_token = nil
|
71
61
|
end
|
72
62
|
end
|
73
63
|
end
|