rails_jwt_auth 1.7.3 → 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/{send_invitation.html.erb → invitation_instructions.html.erb} +1 -1
  21. data/app/views/rails_jwt_auth/mailer/password_changed_notification.html.erb +3 -0
  22. data/app/views/rails_jwt_auth/mailer/reset_password_instructions.html.erb +1 -1
  23. data/app/views/rails_jwt_auth/mailer/{send_unlock_instructions.html.erb → unlock_instructions.html.erb} +1 -1
  24. data/config/locales/en.yml +6 -6
  25. data/lib/generators/rails_jwt_auth/install_generator.rb +11 -3
  26. data/lib/generators/templates/initializer.rb +43 -29
  27. data/lib/generators/templates/migration.rb +2 -1
  28. data/lib/rails_jwt_auth/jwt_manager.rb +2 -4
  29. data/lib/rails_jwt_auth/session.rb +128 -0
  30. data/lib/rails_jwt_auth/version.rb +1 -1
  31. data/lib/rails_jwt_auth.rb +46 -47
  32. metadata +11 -9
  33. data/app/controllers/rails_jwt_auth/passwords_controller.rb +0 -32
  34. data/app/views/rails_jwt_auth/mailer/set_password_instructions.html.erb +0 -5
  35. /data/app/views/rails_jwt_auth/mailer/{email_changed.html.erb → email_change_requested_notification.html.erb} +0 -0
@@ -0,0 +1,51 @@
1
+ module RailsJwtAuth
2
+ class ProfilesController < ApplicationController
3
+ include AuthenticableHelper
4
+ include ParamsHelper
5
+ include RenderHelper
6
+
7
+ PASSWORD_PARAMS = %i[current_password password password_confirmation].freeze
8
+
9
+ before_action :authenticate!
10
+
11
+ def show
12
+ render_profile current_user
13
+ end
14
+
15
+ def update
16
+ if current_user.update(profile_update_params)
17
+ render_204
18
+ else
19
+ render_422(current_user.errors.details)
20
+ end
21
+ end
22
+
23
+ def password
24
+ if current_user.update_password(update_password_params)
25
+ render_204
26
+ else
27
+ render_422(current_user.errors.details)
28
+ end
29
+ end
30
+
31
+ def email
32
+ return update unless current_user.is_a?(RailsJwtAuth::Confirmable)
33
+
34
+ if current_user.update_email(profile_update_email_params)
35
+ render_204
36
+ else
37
+ render_422(current_user.errors.details)
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def changing_password?
44
+ profile_update_params.values_at(*PASSWORD_PARAMS).any?(&:present?)
45
+ end
46
+
47
+ def update_password_params
48
+ profile_update_password_params.merge(current_auth_token: jwt_payload['auth_token'])
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,65 @@
1
+ module RailsJwtAuth
2
+ class ResetPasswordsController < ApplicationController
3
+ include ParamsHelper
4
+ include RenderHelper
5
+
6
+ before_action :set_user_from_token, only: [:show, :update]
7
+ before_action :set_user_from_email, only: [:create]
8
+
9
+ # used to verify token
10
+ def show
11
+ return render_404 unless @user
12
+
13
+ if @user.reset_password_sent_at < RailsJwtAuth.reset_password_expiration_time.ago
14
+ return render_410
15
+ end
16
+
17
+ render_204
18
+ end
19
+
20
+ # used to request restore password
21
+ def create
22
+ unless @user
23
+ if RailsJwtAuth.avoid_email_errors
24
+ return render_204
25
+ else
26
+ return render_422(RailsJwtAuth.email_field_name => [{error: :not_found}])
27
+ end
28
+ end
29
+
30
+ @user.send_reset_password_instructions ? render_204 : render_422(@user.errors.details)
31
+ end
32
+
33
+ # used to set new password
34
+ def update
35
+ return render_404 unless @user
36
+
37
+ if @user.set_reset_password(reset_password_update_params)
38
+ render_204
39
+ else
40
+ render_422(@user.errors.details)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def set_user_from_token
47
+ return if params[:id].blank?
48
+
49
+ @user = RailsJwtAuth.model.where(reset_password_token: params[:id]).first
50
+ end
51
+
52
+ def set_user_from_email
53
+ email = (reset_password_create_params[RailsJwtAuth.email_field_name] || '').strip
54
+ email.downcase! if RailsJwtAuth.downcase_auth_field
55
+
56
+ if email.blank?
57
+ return render_422(RailsJwtAuth.email_field_name => [{error: :blank}])
58
+ elsif !email.match?(RailsJwtAuth.email_regex)
59
+ return render_422(RailsJwtAuth.email_field_name => [{error: :format}])
60
+ end
61
+
62
+ @user = RailsJwtAuth.model.where(RailsJwtAuth.email_field_name => email).first
63
+ end
64
+ end
65
+ end
@@ -1,40 +1,25 @@
1
1
  module RailsJwtAuth
2
2
  class SessionsController < ApplicationController
3
+ include AuthenticableHelper
3
4
  include ParamsHelper
4
5
  include RenderHelper
5
6
 
6
7
  def create
7
- user = find_user
8
+ se = Session.new(session_create_params)
8
9
 
9
- if !user
10
- render_422 session: [{error: :invalid_session}]
11
- elsif user.respond_to?('confirmed?') && !user.confirmed?
12
- render_422 session: [{error: :unconfirmed}]
13
- elsif user.authentication?(session_create_params[:password])
14
- render_session generate_jwt(user), user
10
+ if se.generate!(request)
11
+ render_session se.jwt, se.user
15
12
  else
16
- render_422 session: [user.unauthenticated_error]
13
+ render_422 se.errors.details
17
14
  end
18
15
  end
19
16
 
20
17
  def destroy
21
- return render_404 unless RailsJwtAuth.simultaneous_sessions > 0
18
+ return render_404 unless RailsJwtAuth.simultaneous_sessions.positive?
22
19
 
23
20
  authenticate!
24
- payload = JwtManager.decode_from_request(request)&.first
25
- current_user.destroy_auth_token payload['auth_token']
21
+ current_user.destroy_auth_token @jwt_payload['auth_token']
26
22
  render_204
27
23
  end
28
-
29
- private
30
-
31
- def generate_jwt(user)
32
- JwtManager.encode(user.to_token_payload(request))
33
- end
34
-
35
- def find_user
36
- auth_field = RailsJwtAuth.auth_field_name!
37
- RailsJwtAuth.model.where(auth_field => session_create_params[auth_field]).first
38
- end
39
24
  end
40
25
  end
@@ -1,5 +1,5 @@
1
1
  module RailsJwtAuth
2
- class UnlocksController < ApplicationController
2
+ class UnlockAccountsController < ApplicationController
3
3
  include ParamsHelper
4
4
  include RenderHelper
5
5
 
@@ -8,7 +8,7 @@ module RailsJwtAuth
8
8
  params[:id] &&
9
9
  (user = RailsJwtAuth.model.where(unlock_token: params[:id]).first)
10
10
 
11
- user.unlock_access! ? render_204 : render_422(user.errors.details)
11
+ user.unlock_access ? render_204 : render_422(user.errors.details)
12
12
  end
13
13
  end
14
14
  end
@@ -4,65 +4,60 @@ if defined?(ActionMailer)
4
4
 
5
5
  before_action do
6
6
  @user = RailsJwtAuth.model.find(params[:user_id])
7
+ @to = @user[RailsJwtAuth.email_field_name]
7
8
  @subject = I18n.t("rails_jwt_auth.mailer.#{action_name}.subject")
8
9
  end
9
10
 
10
11
  def confirmation_instructions
11
- raise RailsJwtAuth::NotConfirmationsUrl unless RailsJwtAuth.confirmations_url.present?
12
+ raise RailsJwtAuth::NotConfirmationsUrl unless RailsJwtAuth.confirm_email_url.present?
12
13
 
13
- @confirmations_url = add_param_to_url(
14
- RailsJwtAuth.confirmations_url,
14
+ @confirm_email_url = add_param_to_url(
15
+ RailsJwtAuth.confirm_email_url,
15
16
  'confirmation_token',
16
17
  @user.confirmation_token
17
18
  )
18
19
 
19
- mail(to: @user.unconfirmed_email || @user[RailsJwtAuth.email_field_name], subject: @subject)
20
+ mail(to: @user.unconfirmed_email || @to, subject: @subject)
20
21
  end
21
22
 
22
- def email_changed
23
- mail(to: @user[RailsJwtAuth.email_field_name!], subject: @subject)
23
+ def email_change_requested_notification
24
+ mail(to: @to, subject: @subject)
24
25
  end
25
26
 
26
27
  def reset_password_instructions
27
- raise RailsJwtAuth::NotResetPasswordsUrl unless RailsJwtAuth.reset_passwords_url.present?
28
+ raise RailsJwtAuth::NotResetPasswordsUrl unless RailsJwtAuth.reset_password_url.present?
28
29
 
29
- @reset_passwords_url = add_param_to_url(
30
- RailsJwtAuth.reset_passwords_url,
30
+ @reset_password_url = add_param_to_url(
31
+ RailsJwtAuth.reset_password_url,
31
32
  'reset_password_token',
32
33
  @user.reset_password_token
33
34
  )
34
35
 
35
- mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
36
+ mail(to: @to, subject: @subject)
36
37
  end
37
38
 
38
- def set_password_instructions
39
- raise RailsJwtAuth::NotSetPasswordsUrl unless RailsJwtAuth.set_passwords_url.present?
40
-
41
- @reset_passwords_url = add_param_to_url(
42
- RailsJwtAuth.set_passwords_url,
43
- 'reset_password_token',
44
- @user.reset_password_token
45
- )
46
-
47
- mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
39
+ def password_changed_notification
40
+ mail(to: @to, subject: @subject)
48
41
  end
49
42
 
50
- def send_invitation
51
- raise RailsJwtAuth::NotInvitationsUrl unless RailsJwtAuth.invitations_url.present?
43
+ def invitation_instructions
44
+ raise RailsJwtAuth::NotInvitationsUrl unless RailsJwtAuth.accept_invitation_url.present?
52
45
 
53
- @invitations_url = add_param_to_url(
54
- RailsJwtAuth.invitations_url,
46
+ @accept_invitation_url = add_param_to_url(
47
+ RailsJwtAuth.accept_invitation_url,
55
48
  'invitation_token',
56
49
  @user.invitation_token
57
50
  )
58
51
 
59
- mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
52
+ mail(to: @to, subject: @subject)
60
53
  end
61
54
 
62
- def send_unlock_instructions
63
- @unlock_url = add_param_to_url(RailsJwtAuth.unlock_url, 'unlock_token', @user.unlock_token)
55
+ def unlock_instructions
56
+ raise RailsJwtAuth::NotUnlockUrl unless RailsJwtAuth.unlock_account_url.present?
57
+
58
+ @unlock_account_url = add_param_to_url(RailsJwtAuth.unlock_account_url, 'unlock_token', @user.unlock_token)
64
59
 
65
- mail(to: @user[RailsJwtAuth.email_field_name], subject: @subject)
60
+ mail(to: @to, subject: @subject)
66
61
  end
67
62
 
68
63
  protected
@@ -8,28 +8,41 @@ module RailsJwtAuth
8
8
  base.class_eval do
9
9
  if defined?(Mongoid) && ancestors.include?(Mongoid::Document)
10
10
  field :password_digest, type: String
11
- field :auth_tokens, type: Array if RailsJwtAuth.simultaneous_sessions > 0
11
+ field :auth_tokens, type: Array, default: [] if RailsJwtAuth.simultaneous_sessions > 0
12
12
  elsif defined?(ActiveRecord) && ancestors.include?(ActiveRecord::Base)
13
13
  serialize :auth_tokens, Array
14
14
  end
15
15
 
16
16
  has_secure_password
17
+
18
+ before_validation do
19
+ if RailsJwtAuth.downcase_auth_field &&
20
+ public_send("#{RailsJwtAuth.auth_field_name}_changed?")
21
+ self[RailsJwtAuth.auth_field_name]&.downcase!
22
+ end
23
+ end
17
24
  end
18
25
  end
19
26
 
20
- def regenerate_auth_token(token = nil)
27
+ def load_auth_token
21
28
  new_token = SecureRandom.base58(24)
22
29
 
23
30
  if RailsJwtAuth.simultaneous_sessions > 1
24
- tokens = ((auth_tokens || []) - [token]).last(RailsJwtAuth.simultaneous_sessions - 1)
25
- update_attribute(:auth_tokens, (tokens + [new_token]).uniq)
31
+ tokens = (auth_tokens || []).last(RailsJwtAuth.simultaneous_sessions - 1)
32
+ self.auth_tokens = (tokens + [new_token]).uniq
26
33
  else
27
- update_attribute(:auth_tokens, [new_token])
34
+ self.auth_tokens = [new_token]
28
35
  end
29
36
 
30
37
  new_token
31
38
  end
32
39
 
40
+ def regenerate_auth_token(token=nil)
41
+ self.auth_tokens -= [token] if token
42
+ token = load_auth_token
43
+ save ? token : false
44
+ end
45
+
33
46
  def destroy_auth_token(token)
34
47
  if RailsJwtAuth.simultaneous_sessions > 1
35
48
  tokens = auth_tokens || []
@@ -39,7 +52,34 @@ module RailsJwtAuth
39
52
  end
40
53
  end
41
54
 
42
- def update_with_password(params)
55
+ def to_token_payload(_request=nil)
56
+ if RailsJwtAuth.simultaneous_sessions > 0
57
+ auth_tokens&.last ? {auth_token: auth_tokens.last} : false
58
+ else
59
+ {id: id.to_s}
60
+ end
61
+ end
62
+
63
+ def save_without_password
64
+ # when set password to nil only password_digest is setted to nil
65
+ # https://github.com/rails/rails/blob/master/activemodel/lib/active_model/secure_password.rb#L97
66
+ instance_variable_set("@password", nil)
67
+ self.password_confirmation = nil
68
+ self.password_digest = nil
69
+
70
+ return false unless valid_without_password?
71
+
72
+ save(validate: false)
73
+ end
74
+
75
+ def valid_without_password?
76
+ valid?
77
+ errors.delete(:password) # allow register without pass
78
+ errors.delete(:password_confirmation)
79
+ errors.empty?
80
+ end
81
+
82
+ def update_password(params)
43
83
  current_password_error = if (current_password = params.delete(:current_password)).blank?
44
84
  'blank'
45
85
  elsif !authenticate(current_password)
@@ -51,28 +91,29 @@ module RailsJwtAuth
51
91
  self.reset_password_token = self.reset_password_sent_at = nil
52
92
  end
53
93
 
94
+ # close all sessions or other sessions when pass current_auth_token
95
+ current_auth_token = params.delete :current_auth_token
96
+ self.auth_tokens = current_auth_token ? [current_auth_token] : []
97
+
54
98
  assign_attributes(params)
55
99
  valid? # validates first other fields
56
100
  errors.add(:current_password, current_password_error) if current_password_error
57
101
  errors.add(:password, 'blank') if params[:password].blank?
58
102
 
59
- errors.empty? ? save : false
60
- end
103
+ return false unless errors.empty?
104
+ return false unless save
61
105
 
62
- def to_token_payload(_request=nil)
63
- if RailsJwtAuth.simultaneous_sessions > 0
64
- {auth_token: regenerate_auth_token}
65
- else
66
- {id: id.to_s}
67
- end
68
- end
106
+ deliver_password_changed_notification
69
107
 
70
- def authentication?(pass)
71
- authenticate(pass)
108
+ true
72
109
  end
73
110
 
74
- def unauthenticated_error
75
- {error: :invalid_session}
111
+ protected
112
+
113
+ def deliver_password_changed_notification
114
+ return unless RailsJwtAuth.send_password_changed_notification
115
+
116
+ RailsJwtAuth.send_email(:password_changed_notification, self)
76
117
  end
77
118
 
78
119
  module ClassMethods
@@ -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