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.
- checksums.yaml +4 -4
- data/README.md +188 -89
- data/app/controllers/concerns/rails_jwt_auth/authenticable_helper.rb +15 -7
- data/app/controllers/concerns/rails_jwt_auth/params_helper.rb +18 -4
- 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 +27 -9
- data/app/controllers/rails_jwt_auth/profiles_controller.rb +51 -0
- data/app/controllers/rails_jwt_auth/reset_passwords_controller.rb +65 -0
- data/app/controllers/rails_jwt_auth/sessions_controller.rb +7 -22
- data/app/controllers/rails_jwt_auth/{unlocks_controller.rb → unlock_accounts_controller.rb} +2 -2
- data/app/mailers/rails_jwt_auth/mailer.rb +23 -28
- data/app/models/concerns/rails_jwt_auth/authenticatable.rb +60 -19
- data/app/models/concerns/rails_jwt_auth/confirmable.rb +49 -39
- data/app/models/concerns/rails_jwt_auth/invitable.rb +46 -72
- data/app/models/concerns/rails_jwt_auth/lockable.rb +38 -46
- data/app/models/concerns/rails_jwt_auth/recoverable.rb +27 -26
- 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 +46 -47
- data/lib/rails_jwt_auth/jwt_manager.rb +2 -4
- data/lib/rails_jwt_auth/session.rb +128 -0
- data/lib/rails_jwt_auth/version.rb +1 -1
- metadata +11 -15
- data/app/controllers/rails_jwt_auth/passwords_controller.rb +0 -32
- 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(
|
30
|
+
errors.add(RailsJwtAuth.email_field_name, :already_confirmed)
|
60
31
|
return false
|
61
32
|
end
|
62
33
|
|
63
|
-
self.confirmation_token =
|
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.
|
120
|
-
RailsJwtAuth.send_email(:
|
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
|
-
|
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)
|
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
|
-
|
63
|
-
self.
|
41
|
+
@inviting = true
|
42
|
+
self.invitation_token = generate_invitation_token
|
43
|
+
self.invitation_sent_at = Time.current
|
64
44
|
|
65
|
-
|
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
|
-
|
47
|
+
RailsJwtAuth.send_email(:invitation_instructions, self)
|
48
|
+
true
|
49
|
+
ensure
|
50
|
+
@inviting = false
|
51
|
+
end
|
72
52
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
53
|
+
# Finishes invitation process setting user password
|
54
|
+
def accept_invitation(params)
|
55
|
+
return false unless invitation_token.present?
|
77
56
|
|
78
|
-
|
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
|
-
|
59
|
+
valid?
|
60
|
+
errors.add(:password, :blank) if params[:password].blank?
|
61
|
+
errors.add(:invitation_token, :expired) if expired_invitation_token?
|
84
62
|
|
85
|
-
|
86
|
-
self.invitation_sent_at = Time.current
|
63
|
+
return false unless errors.empty?
|
87
64
|
|
88
|
-
|
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
|
93
|
-
|
72
|
+
def inviting?
|
73
|
+
@inviting || false
|
94
74
|
end
|
95
75
|
|
96
|
-
def
|
97
|
-
|
76
|
+
def valid_for_invite?
|
77
|
+
@inviting = true
|
78
|
+
valid_without_password?
|
79
|
+
ensure
|
80
|
+
@inviting = false
|
98
81
|
end
|
99
82
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
83
|
+
def expired_invitation_token?
|
84
|
+
expiration_time = RailsJwtAuth.invitation_expiration_time
|
85
|
+
return false if expiration_time.to_i.zero?
|
103
86
|
|
104
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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.
|
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
|
|
65
54
|
protected
|
66
55
|
|
67
56
|
def send_unlock_instructions
|
68
|
-
self.unlock_token =
|
57
|
+
self.unlock_token = generate_unlock_token
|
69
58
|
save(validate: false)
|
70
59
|
|
71
|
-
RailsJwtAuth.send_email(:
|
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
|
87
|
-
self.failed_attempts
|
88
|
-
self.
|
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
|
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 =
|
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
|
47
|
-
|
48
|
-
return if password.present?
|
37
|
+
def set_reset_password(params)
|
38
|
+
self.assign_attributes(params)
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
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
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|