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
@@ -1,9 +1,18 @@
1
1
  module RailsJwtAuth
2
2
  module Trackable
3
- def update_tracked_fields!(request)
3
+ def track_session_info(request)
4
+ return unless request
5
+
4
6
  self.last_sign_in_at = Time.current
5
7
  self.last_sign_in_ip = request.respond_to?(:remote_ip) ? request.remote_ip : request.ip
6
- save(validate: false)
8
+ end
9
+
10
+ def update_tracked_request_info(request)
11
+ return unless request
12
+
13
+ self.last_request_at = Time.current
14
+ self.last_request_ip = request.respond_to?(:remote_ip) ? request.remote_ip : request.ip
15
+ self.save(validate: false)
7
16
  end
8
17
 
9
18
  def self.included(base)
@@ -11,6 +20,8 @@ module RailsJwtAuth
11
20
  if defined?(Mongoid) && ancestors.include?(Mongoid::Document)
12
21
  field :last_sign_in_at, type: Time
13
22
  field :last_sign_in_ip, type: String
23
+ field :last_request_at, type: Time
24
+ field :last_request_ip, type: String
14
25
  end
15
26
  end
16
27
  end
@@ -2,4 +2,4 @@
2
2
 
3
3
  <p>You can confirm your account email through the link below:</p>
4
4
 
5
- <p><%= link_to 'Confirm my account', @confirmations_url.html_safe %></p>
5
+ <p><%= link_to 'Confirm my account', @confirm_email_url.html_safe %></p>
@@ -3,4 +3,4 @@
3
3
  <p>Someone has sent you an invitation to App.</p>
4
4
  <p>To complete registration setting a password, please click the following link.</p>
5
5
 
6
- <p><%= link_to "Accept invitation", @invitations_url.html_safe %></p>
6
+ <p><%= link_to "Accept invitation", @accept_invitation_url.html_safe %></p>
@@ -0,0 +1,3 @@
1
+ <p>Hello <%= @user[RailsJwtAuth.email_field_name] %>!</p>
2
+
3
+ <p>We're contacting you to notify you that your password has been changed.</p>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p>Someone has requested a link to change your password. You can do this through the link below.</p>
4
4
 
5
- <p><%= link_to 'Change my password', @reset_passwords_url.html_safe %></p>
5
+ <p><%= link_to 'Change my password', @reset_password_url.html_safe %></p>
6
6
 
7
7
  <p>If you didn't request this, please ignore this email.</p>
8
8
  <p>Your password won't change until you access the link above and create a new one.</p>
@@ -4,4 +4,4 @@
4
4
 
5
5
  <p>Click the link below to unlock your account:</p>
6
6
 
7
- <p><%= link_to 'Unlock my account', @unlock_url.html_safe %></p>
7
+ <p><%= link_to 'Unlock my account', @unlock_account_url.html_safe %></p>
@@ -5,11 +5,11 @@ en:
5
5
  subject: "Confirmation instructions"
6
6
  reset_password_instructions:
7
7
  subject: "Reset password instructions"
8
- set_password_instructions:
9
- subject: "Set password instructions"
10
- send_invitation:
8
+ invitation_instructions:
11
9
  subject: "Someone has sent you an invitation!"
12
- email_changed:
13
- subject: "Email changed"
14
- send_unlock_instructions:
10
+ email_change_requested_notification:
11
+ subject: "Email change"
12
+ password_changed_notification:
13
+ subject: "Password changed"
14
+ unlock_instructions:
15
15
  subject: "Unlock instructions"
@@ -8,10 +8,18 @@ class RailsJwtAuth::InstallGenerator < Rails::Generators::Base
8
8
  def create_routes
9
9
  route "resource :session, controller: 'rails_jwt_auth/sessions', only: [:create, :destroy]"
10
10
  route "resource :registration, controller: 'rails_jwt_auth/registrations', only: [:create]"
11
+ route %q(
12
+ resource :profile, controller: 'rails_jwt_auth/profiles', only: %i[show update] do
13
+ collection do
14
+ put :email
15
+ put :password
16
+ end
17
+ end
18
+ )
11
19
 
12
20
  route "resources :confirmations, controller: 'rails_jwt_auth/confirmations', only: [:create, :update]"
13
- route "resources :passwords, controller: 'rails_jwt_auth/passwords', only: [:create, :update]"
14
- route "resources :invitations, controller: 'rails_jwt_auth/invitations', only: [:create, :update]"
15
- route "resources :unlocks, controller: 'rails_jwt_auth/unlocks', only: %i[update]"
21
+ route "resources :reset_passwords, controller: 'rails_jwt_auth/reset_passwords', only: [:show, :create, :update]"
22
+ route "resources :invitations, controller: 'rails_jwt_auth/invitations', only: [:show, :create, :update]"
23
+ route "resources :unlock_accounts, controller: 'rails_jwt_auth/unlock_accounts', only: %i[update]"
16
24
  end
17
25
  end
@@ -1,65 +1,79 @@
1
1
  RailsJwtAuth.setup do |config|
2
2
  # authentication model class name
3
- #config.model_name = 'User'
3
+ # config.model_name = 'User'
4
4
 
5
5
  # field name used to authentication with password
6
- #config.auth_field_name = 'email'
6
+ # config.auth_field_name = 'email'
7
7
 
8
8
  # define email field name used to send emails
9
- #config.email_field_name = 'email'
9
+ # config.email_field_name = 'email'
10
+
11
+ # Regex used to validate email input on requests like reset password
12
+ # config.email_regex = URI::MailTo::EMAIL_REGEXP
13
+
14
+ # apply downcase to auth field when save user and when init session
15
+ # config.downcase_auth_field = false
10
16
 
11
17
  # expiration time for generated tokens
12
- #config.jwt_expiration_time = 7.days
18
+ # config.jwt_expiration_time = 7.days
13
19
 
14
20
  # the "iss" (issuer) claim identifies the principal that issued the JWT
15
- #config.jwt_issuer = 'RailsJwtAuth'
21
+ # config.jwt_issuer = 'RailsJwtAuth'
16
22
 
17
23
  # number of simultaneously sessions for an user
18
- #config.simultaneous_sessions = 2
24
+ # config.simultaneous_sessions = 2
25
+
26
+ # mailer class name
27
+ # config.mailer_name = 'RailsJwtAuth::Mailer'
19
28
 
20
29
  # mailer sender
21
- #config.mailer_sender = 'initialize-mailer_sender@example.com'
30
+ # config.mailer_sender = 'initialize-mailer_sender@example.com'
31
+
32
+ # activate email notification when email is changed
33
+ # config.send_email_change_requested_notification = true
34
+
35
+ # activate email notification when password is changed
36
+ # config.send_password_changed_notification = true
22
37
 
23
38
  # expiration time for confirmation tokens
24
- #config.confirmation_expiration_time = 1.day
39
+ # config.confirmation_expiration_time = 1.day
25
40
 
26
41
  # expiration time for reset password tokens
27
- #config.reset_password_expiration_time = 1.day
42
+ # config.reset_password_expiration_time = 1.day
28
43
 
29
44
  # time an invitation is valid after sent
30
45
  # config.invitation_expiration_time = 2.days
31
46
 
32
- # url used to create email link with confirmation token
33
- #config.confirmations_url = 'http://frontend.com/confirmation'
34
-
35
- # url used to create email link with reset password token
36
- #config.reset_passwords_url = 'http://frontend.com/reset_password'
37
-
38
- # url used to create email link with set password token
39
- # by set_and_send_password_instructions method
40
- #config.set_passwords_url = 'http://frontend.com/set_password'
41
-
42
- # url used to create email link with activation token parameter to accept invitation
43
- #config.invitations_url = 'http://frontend.com/accept_invitation'
44
-
45
47
  # uses deliver_later to send emails instead of deliver method
46
- #config.deliver_later = false
48
+ # config.deliver_later = false
47
49
 
48
50
  # maximum login attempts before locking an account
49
- #config.maximum_attempts = 3
51
+ # config.maximum_attempts = 3
50
52
 
51
53
  # strategy to lock an account: :none or :failed_attempts
52
- #config.lock_strategy = :failed_attempts
54
+ # config.lock_strategy = :failed_attempts
53
55
 
54
56
  # strategy to use when unlocking accounts: :time, :email or :both
55
- #config.unlock_strategy = :time
57
+ # config.unlock_strategy = :time
56
58
 
57
59
  # interval to unlock an account if unlock_strategy is :time
58
- #config.unlock_in = 60.minutes
60
+ # config.unlock_in = 60.minutes
59
61
 
60
62
  # interval after which to reset failed attempts counter of an account
61
- #config.reset_attempts_in = 60.minutes
63
+ # config.reset_attempts_in = 60.minutes
64
+ #
65
+ # url used to create email link with confirmation token
66
+ # config.confirm_email_url = 'http://frontend.com/confirm-email'
67
+
68
+ # url used to create email link with reset password token
69
+ # config.reset_password_url = 'http://frontend.com/reset-password'
70
+
71
+ # url used to create email link with activation token parameter to accept invitation
72
+ # config.accept_invitation_url = 'http://frontend.com/accept-invitation'
62
73
 
63
74
  # url used to create email link with unlock token
64
- #config.unlock_url = 'http://frontend.com/unlock-account'
75
+ # config.unlock_account_url = 'http://frontend.com/unlock-account'
76
+
77
+ # set false to avoid giving clue about the existing emails with errors
78
+ # config.avoid_email_errors = true
65
79
  end
@@ -18,12 +18,13 @@ class Create<%= RailsJwtAuth.model_name.pluralize %> < ActiveRecord::Migration<%
18
18
  ## Trackable
19
19
  # t.string :last_sign_in_ip
20
20
  # t.datetime :last_sign_in_at
21
+ # t.string :last_request_ip
22
+ # t.datetime :last_request_at
21
23
 
22
24
  ## Invitable
23
25
  # t.string :invitation_token
24
26
  # t.datetime :invitation_sent_at
25
27
  # t.datetime :invitation_accepted_at
26
- # t.datetime :invitation_created_at
27
28
 
28
29
  ## Lockable
29
30
  # t.integer :failed_attempts
@@ -2,14 +2,13 @@ require 'bcrypt'
2
2
 
3
3
  require 'rails_jwt_auth/engine'
4
4
  require 'rails_jwt_auth/jwt_manager'
5
+ require 'rails_jwt_auth/session'
5
6
 
6
7
  module RailsJwtAuth
7
- InvalidEmailField = Class.new(StandardError)
8
- InvalidAuthField = Class.new(StandardError)
9
8
  NotConfirmationsUrl = Class.new(StandardError)
10
9
  NotInvitationsUrl = Class.new(StandardError)
11
10
  NotResetPasswordsUrl = Class.new(StandardError)
12
- NotSetPasswordsUrl = Class.new(StandardError)
11
+ NotUnlockUrl = Class.new(StandardError)
13
12
 
14
13
  mattr_accessor :model_name
15
14
  self.model_name = 'User'
@@ -20,6 +19,12 @@ module RailsJwtAuth
20
19
  mattr_accessor :email_field_name
21
20
  self.email_field_name = 'email'
22
21
 
22
+ mattr_accessor :email_regex
23
+ self.email_regex = URI::MailTo::EMAIL_REGEXP
24
+
25
+ mattr_accessor :downcase_auth_field
26
+ self.downcase_auth_field = false
27
+
23
28
  mattr_accessor :jwt_expiration_time
24
29
  self.jwt_expiration_time = 7.days
25
30
 
@@ -29,11 +34,17 @@ module RailsJwtAuth
29
34
  mattr_accessor :simultaneous_sessions
30
35
  self.simultaneous_sessions = 2
31
36
 
37
+ mattr_accessor :mailer_name
38
+ self.mailer_name = 'RailsJwtAuth::Mailer'
39
+
32
40
  mattr_accessor :mailer_sender
33
41
  self.mailer_sender = 'initialize-mailer_sender@example.com'
34
42
 
35
- mattr_accessor :send_email_changed_notification
36
- self.send_email_changed_notification = true
43
+ mattr_accessor :send_email_change_requested_notification
44
+ self.send_email_change_requested_notification = true
45
+
46
+ mattr_accessor :send_password_changed_notification
47
+ self.send_password_changed_notification = true
37
48
 
38
49
  mattr_accessor :confirmation_expiration_time
39
50
  self.confirmation_expiration_time = 1.day
@@ -44,18 +55,6 @@ module RailsJwtAuth
44
55
  mattr_accessor :invitation_expiration_time
45
56
  self.invitation_expiration_time = 2.days
46
57
 
47
- mattr_accessor :confirmations_url
48
- self.confirmations_url = nil
49
-
50
- mattr_accessor :reset_passwords_url
51
- self.reset_passwords_url = nil
52
-
53
- mattr_accessor :set_passwords_url
54
- self.set_passwords_url = nil
55
-
56
- mattr_accessor :invitations_url
57
- self.invitations_url = nil
58
-
59
58
  mattr_accessor :deliver_later
60
59
  self.deliver_later = false
61
60
 
@@ -72,51 +71,49 @@ module RailsJwtAuth
72
71
  self.unlock_in = 60.minutes
73
72
 
74
73
  mattr_accessor :reset_attempts_in
75
- self.unlock_in = 60.minutes
74
+ self.reset_attempts_in = 60.minutes
76
75
 
77
- mattr_accessor :unlock_url
78
- self.unlock_url = nil
76
+ mattr_accessor :confirm_email_url
77
+ self.confirm_email_url = nil
79
78
 
80
- def self.model
81
- model_name.constantize
82
- end
79
+ mattr_accessor :reset_password_url
80
+ self.reset_password_url = nil
83
81
 
84
- def self.table_name
85
- model_name.underscore.pluralize
86
- end
82
+ mattr_accessor :accept_invitation_url
83
+ self.accept_invitation_url = nil
84
+
85
+ mattr_accessor :unlock_account_url
86
+ self.unlock_account_url = nil
87
+
88
+ mattr_accessor :avoid_email_errors
89
+ self.avoid_email_errors = true
87
90
 
88
91
  def self.setup
89
92
  yield self
90
93
  end
91
94
 
92
- def self.auth_field_name!
93
- field_name = RailsJwtAuth.auth_field_name
94
- klass = RailsJwtAuth.model
95
-
96
- unless field_name.present? &&
97
- (klass.respond_to?(:column_names) && klass.column_names.include?(field_name) ||
98
- klass.respond_to?(:fields) && klass.fields[field_name])
99
- raise RailsJwtAuth::InvalidAuthField
100
- end
101
-
102
- field_name
95
+ def self.model
96
+ model_name.constantize
103
97
  end
104
98
 
105
- def self.email_field_name!
106
- field_name = RailsJwtAuth.email_field_name
107
- klass = RailsJwtAuth.model
99
+ def self.mailer
100
+ mailer_name.constantize
101
+ end
108
102
 
109
- unless field_name.present? &&
110
- (klass.respond_to?(:column_names) && klass.column_names.include?(field_name) ||
111
- klass.respond_to?(:fields) && klass.fields[field_name])
112
- raise RailsJwtAuth::InvalidEmailField
113
- end
103
+ def self.table_name
104
+ model_name.underscore.pluralize
105
+ end
114
106
 
115
- field_name
107
+ # Thanks to https://github.com/heartcombo/devise/blob/master/lib/devise.rb#L496
108
+ def self.friendly_token(length = 24)
109
+ # To calculate real characters, we must perform this operation.
110
+ # See SecureRandom.urlsafe_base64
111
+ rlength = (length * 3 / 4) - 1
112
+ SecureRandom.urlsafe_base64(rlength, true).tr('lIO0', 'sxyz')
116
113
  end
117
114
 
118
115
  def self.send_email(method, user)
119
- mailer = RailsJwtAuth::Mailer.with(user_id: user.id.to_s).public_send(method)
116
+ mailer = RailsJwtAuth.mailer.with(user_id: user.id.to_s).public_send(method)
120
117
  RailsJwtAuth.deliver_later ? mailer.deliver_later : mailer.deliver
121
118
  end
122
119
  end
@@ -25,9 +25,5 @@ module RailsJwtAuth
25
25
  iss: RailsJwtAuth.jwt_issuer
26
26
  }
27
27
  end
28
-
29
- def self.decode_from_request(request)
30
- decode(request.env['HTTP_AUTHORIZATION']&.split&.last)
31
- end
32
28
  end
33
29
  end
@@ -0,0 +1,132 @@
1
+ module RailsJwtAuth
2
+ class Session
3
+ attr_reader :user, :errors, :jwt
4
+
5
+ Errors = Struct.new :details # simulate ActiveModel::Errors
6
+
7
+ def initialize(params={})
8
+ @auth_field_value = (params[RailsJwtAuth.auth_field_name] || '').strip
9
+ @auth_field_value.downcase! if RailsJwtAuth.downcase_auth_field
10
+ @password = params[:password]
11
+
12
+ find_user if @auth_field_value.present?
13
+ end
14
+
15
+ def valid?
16
+ validate!
17
+
18
+ !errors?
19
+ end
20
+
21
+ def generate!(request)
22
+ if valid?
23
+ user.clean_reset_password if recoverable?
24
+ user.clean_lock if lockable?
25
+ user.track_session_info(request) if trackable?
26
+ user.load_auth_token
27
+
28
+ unless user.save
29
+ add_error(RailsJwtAuth.model_name.underscore, :invalid)
30
+
31
+ return false
32
+ end
33
+
34
+ generate_jwt(request)
35
+
36
+ true
37
+ else
38
+ user.failed_attempt if lockable?
39
+
40
+ false
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def validate!
47
+ # Can't use ActiveModel::Validations since we have dynamic fields
48
+ @errors = Errors.new({})
49
+
50
+ validate_auth_field_presence
51
+ validate_password_presence
52
+ validate_user_exist
53
+ validate_user_is_confirmed if confirmable?
54
+ validate_user_is_not_locked if lockable?
55
+ validate_user_password unless errors?
56
+ validate_custom
57
+ end
58
+
59
+ def find_user
60
+ @user = RailsJwtAuth.model.where(RailsJwtAuth.auth_field_name => @auth_field_value).first
61
+ end
62
+
63
+ def confirmable?
64
+ @user&.kind_of?(RailsJwtAuth::Confirmable)
65
+ end
66
+
67
+ def lockable?
68
+ @user&.kind_of?(RailsJwtAuth::Lockable)
69
+ end
70
+
71
+ def recoverable?
72
+ @user&.kind_of?(RailsJwtAuth::Recoverable)
73
+ end
74
+
75
+ def trackable?
76
+ @user&.kind_of?(RailsJwtAuth::Trackable)
77
+ end
78
+
79
+ def user?
80
+ @user.present?
81
+ end
82
+
83
+ def field_error(field)
84
+ RailsJwtAuth.avoid_email_errors ? :session : field
85
+ end
86
+
87
+ def validate_auth_field_presence
88
+ add_error(RailsJwtAuth.auth_field_name, :blank) if @auth_field_value.blank?
89
+ end
90
+
91
+ def validate_password_presence
92
+ add_error(:password, :blank) if @password.blank?
93
+ end
94
+
95
+ def validate_user_exist
96
+ add_error(field_error(RailsJwtAuth.auth_field_name), :invalid) unless @user
97
+ end
98
+
99
+ def validate_user_password
100
+ add_error(field_error(:password), :invalid) unless @user.authenticate(@password)
101
+ end
102
+
103
+ def validate_custom
104
+ # allow add custom validation overwriting this method
105
+ end
106
+
107
+ def validate_user_is_confirmed
108
+ add_error(RailsJwtAuth.email_field_name, :unconfirmed) unless @user.confirmed?
109
+ end
110
+
111
+ def validate_user_is_not_locked
112
+ add_error(RailsJwtAuth.email_field_name, :locked) if @user.access_locked?
113
+ end
114
+
115
+ def validate_custom
116
+ # allow add custom validations overwriting this method
117
+ end
118
+
119
+ def add_error(field, detail)
120
+ @errors.details[field.to_sym] ||= []
121
+ @errors.details[field.to_sym].push({error: detail})
122
+ end
123
+
124
+ def errors?
125
+ @errors.details.any?
126
+ end
127
+
128
+ def generate_jwt(request)
129
+ @jwt = JwtManager.encode(user.to_token_payload(request))
130
+ end
131
+ end
132
+ end