rails_jwt_auth 1.7.2 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -1,24 +1,42 @@
1
1
  module RailsJwtAuth
2
2
  class InvitationsController < ApplicationController
3
+ include AuthenticableHelper
3
4
  include ParamsHelper
4
5
  include RenderHelper
5
6
 
7
+ before_action :authenticate!, only: [:create]
8
+ before_action :set_user_from_token, only: [:show, :update]
9
+
10
+ # used to verify token
11
+ def show
12
+ return render_404 unless @user
13
+
14
+ @user.expired_invitation_token? ? render_410 : render_204
15
+ end
16
+
17
+ # used to invite a user, if user is invited send new invitation
6
18
  def create
7
- authenticate!
8
- user = RailsJwtAuth.model.invite!(invitation_create_params)
19
+ user = RailsJwtAuth.model.invite(invitation_create_params)
9
20
  user.errors.empty? ? render_204 : render_422(user.errors.details)
10
21
  end
11
22
 
23
+ # used to accept invitation
12
24
  def update
13
- return render_404 unless
14
- params[:id] &&
15
- (user = RailsJwtAuth.model.where(invitation_token: params[:id]).first)
25
+ return render_404 unless @user
26
+
27
+ if @user.accept_invitation(invitation_update_params)
28
+ render_204
29
+ else
30
+ render_422(@user.errors.details)
31
+ end
32
+ end
33
+
34
+ private
16
35
 
17
- user.assign_attributes invitation_update_params
18
- user.accept_invitation!
19
- return render_204 if user.errors.empty? && user.save
36
+ def set_user_from_token
37
+ return if params[:id].blank?
20
38
 
21
- render_422(user.errors.details)
39
+ @user = RailsJwtAuth.model.where(invitation_token: params[:id]).first
22
40
  end
23
41
  end
24
42
  end
@@ -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