rails_jwt_auth 1.7.2 → 2.0.0

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 +186 -87
  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 +26 -9
  8. data/app/controllers/rails_jwt_auth/profiles_controller.rb +50 -0
  9. data/app/controllers/rails_jwt_auth/reset_passwords_controller.rb +65 -0
  10. data/app/controllers/rails_jwt_auth/sessions_controller.rb +5 -21
  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 +59 -18
  14. data/app/models/concerns/rails_jwt_auth/confirmable.rb +41 -38
  15. data/app/models/concerns/rails_jwt_auth/invitable.rb +42 -77
  16. data/app/models/concerns/rails_jwt_auth/lockable.rb +28 -45
  17. data/app/models/concerns/rails_jwt_auth/recoverable.rb +20 -28
  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 +44 -47
  30. data/lib/rails_jwt_auth/jwt_manager.rb +0 -4
  31. data/lib/rails_jwt_auth/session.rb +132 -0
  32. data/lib/rails_jwt_auth/version.rb +1 -1
  33. metadata +10 -8
  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
@@ -9,12 +9,20 @@ module RailsJwtAuth
9
9
  render json: resource, root: true, status: 201
10
10
  end
11
11
 
12
+ def render_profile(resource)
13
+ render json: resource, root: true, status: 200
14
+ end
15
+
12
16
  def render_204
13
- render json: {}, status: 204
17
+ head 204
14
18
  end
15
19
 
16
20
  def render_404
17
- render json: {}, status: 404
21
+ head 404
22
+ end
23
+
24
+ def render_410
25
+ head 410
18
26
  end
19
27
 
20
28
  def render_422(errors)
@@ -3,22 +3,60 @@ module RailsJwtAuth
3
3
  include ParamsHelper
4
4
  include RenderHelper
5
5
 
6
- def create
7
- user = RailsJwtAuth.model.where(
8
- email: confirmation_create_params[RailsJwtAuth.email_field_name]
9
- ).first
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.confirmation_sent_at < RailsJwtAuth.confirmation_expiration_time.ago
14
+ return render_410
15
+ end
16
+
17
+ render_204
18
+ end
10
19
 
11
- return render_422(email: [{error: :not_found}]) unless user
20
+ # used to resend confirmation
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
12
29
 
13
- user.send_confirmation_instructions ? render_204 : render_422(user.errors.details)
30
+ @user.send_confirmation_instructions ? render_204 : render_422(@user.errors.details)
14
31
  end
15
32
 
33
+ # used to accept confirmation
16
34
  def update
17
- return render_404 unless
18
- params[:id] &&
19
- (user = RailsJwtAuth.model.where(confirmation_token: params[:id]).first)
35
+ return render_404 unless @user
36
+
37
+ @user.confirm ? render_204 : render_422(@user.errors.details)
38
+ end
39
+
40
+ private
41
+
42
+ def set_user_from_token
43
+ return if params[:id].blank?
44
+
45
+ @user = RailsJwtAuth.model.where(confirmation_token: params[:id]).first
46
+ end
47
+
48
+
49
+ def set_user_from_email
50
+ email = (confirmation_create_params[RailsJwtAuth.email_field_name] || '').strip
51
+ email.downcase! if RailsJwtAuth.downcase_auth_field
52
+
53
+ if email.blank?
54
+ return render_422(RailsJwtAuth.email_field_name => [{error: :blank}])
55
+ elsif !email.match?(RailsJwtAuth.email_regex)
56
+ return render_422(RailsJwtAuth.email_field_name => [{error: :format}])
57
+ end
20
58
 
21
- user.confirm! ? render_204 : render_422(user.errors.details)
59
+ @user = RailsJwtAuth.model.where(RailsJwtAuth.email_field_name => email).first
22
60
  end
23
61
  end
24
62
  end
@@ -3,22 +3,39 @@ module RailsJwtAuth
3
3
  include ParamsHelper
4
4
  include RenderHelper
5
5
 
6
+ before_action :authenticate!, only: [:create]
7
+ before_action :set_user_from_token, only: [:show, :update]
8
+
9
+ # used to verify token
10
+ def show
11
+ return render_404 unless @user
12
+
13
+ @user.expired_invitation_token? ? render_410 : render_204
14
+ end
15
+
16
+ # used to invite a user, if user is invited send new invitation
6
17
  def create
7
- authenticate!
8
- user = RailsJwtAuth.model.invite!(invitation_create_params)
18
+ user = RailsJwtAuth.model.invite(invitation_create_params)
9
19
  user.errors.empty? ? render_204 : render_422(user.errors.details)
10
20
  end
11
21
 
22
+ # used to accept invitation
12
23
  def update
13
- return render_404 unless
14
- params[:id] &&
15
- (user = RailsJwtAuth.model.where(invitation_token: params[:id]).first)
24
+ return render_404 unless @user
25
+
26
+ if @user.accept_invitation(invitation_update_params)
27
+ render_204
28
+ else
29
+ render_422(@user.errors.details)
30
+ end
31
+ end
32
+
33
+ private
16
34
 
17
- user.assign_attributes invitation_update_params
18
- user.accept_invitation!
19
- return render_204 if user.errors.empty? && user.save
35
+ def set_user_from_token
36
+ return if params[:id].blank?
20
37
 
21
- render_422(user.errors.details)
38
+ @user = RailsJwtAuth.model.where(invitation_token: params[:id]).first
22
39
  end
23
40
  end
24
41
  end
@@ -0,0 +1,50 @@
1
+ module RailsJwtAuth
2
+ class ProfilesController < ApplicationController
3
+ include ParamsHelper
4
+ include RenderHelper
5
+
6
+ PASSWORD_PARAMS = %i[current_password password password_confirmation].freeze
7
+
8
+ before_action :authenticate!
9
+
10
+ def show
11
+ render_profile current_user
12
+ end
13
+
14
+ def update
15
+ if current_user.update(profile_update_params)
16
+ render_204
17
+ else
18
+ render_422(current_user.errors.details)
19
+ end
20
+ end
21
+
22
+ def password
23
+ if current_user.update_password(update_password_params)
24
+ render_204
25
+ else
26
+ render_422(current_user.errors.details)
27
+ end
28
+ end
29
+
30
+ def email
31
+ return update unless current_user.is_a?(RailsJwtAuth::Confirmable)
32
+
33
+ if current_user.update_email(profile_update_email_params)
34
+ render_204
35
+ else
36
+ render_422(current_user.errors.details)
37
+ end
38
+ end
39
+
40
+ protected
41
+
42
+ def changing_password?
43
+ profile_update_params.values_at(*PASSWORD_PARAMS).any?(&:present?)
44
+ end
45
+
46
+ def update_password_params
47
+ profile_update_password_params.merge(current_auth_token: jwt_payload['auth_token'])
48
+ end
49
+ end
50
+ 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
@@ -4,16 +4,12 @@ module RailsJwtAuth
4
4
  include RenderHelper
5
5
 
6
6
  def create
7
- user = find_user
7
+ se = Session.new(session_create_params)
8
8
 
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
9
+ if se.generate!(request)
10
+ render_session se.jwt, se.user
15
11
  else
16
- render_422 session: [user.unauthenticated_error]
12
+ render_422 se.errors.details
17
13
  end
18
14
  end
19
15
 
@@ -21,20 +17,8 @@ module RailsJwtAuth
21
17
  return render_404 unless RailsJwtAuth.simultaneous_sessions > 0
22
18
 
23
19
  authenticate!
24
- payload = JwtManager.decode_from_request(request)&.first
25
- current_user.destroy_auth_token payload['auth_token']
20
+ current_user.destroy_auth_token @jwt_payload['auth_token']
26
21
  render_204
27
22
  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
23
  end
40
24
  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
@@ -14,22 +14,35 @@ module RailsJwtAuth
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_token: auth_tokens.last}
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