rodauth 1.9.0 → 1.10.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +20 -0
  3. data/README.rdoc +14 -3
  4. data/Rakefile +2 -2
  5. data/doc/base.rdoc +1 -1
  6. data/doc/internals.rdoc +222 -0
  7. data/doc/release_notes/1.10.0.txt +80 -0
  8. data/doc/reset_password.rdoc +8 -2
  9. data/doc/verify_account.rdoc +7 -1
  10. data/doc/verify_change_login.rdoc +8 -6
  11. data/doc/verify_login_change.rdoc +52 -0
  12. data/lib/rodauth/features/account_expiration.rb +1 -1
  13. data/lib/rodauth/features/base.rb +1 -1
  14. data/lib/rodauth/features/change_login.rb +9 -2
  15. data/lib/rodauth/features/change_password.rb +1 -1
  16. data/lib/rodauth/features/close_account.rb +1 -1
  17. data/lib/rodauth/features/confirm_password.rb +1 -1
  18. data/lib/rodauth/features/create_account.rb +1 -1
  19. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  20. data/lib/rodauth/features/email_base.rb +6 -2
  21. data/lib/rodauth/features/http_basic_auth.rb +1 -1
  22. data/lib/rodauth/features/jwt.rb +1 -1
  23. data/lib/rodauth/features/lockout.rb +1 -1
  24. data/lib/rodauth/features/login.rb +1 -1
  25. data/lib/rodauth/features/login_password_requirements_base.rb +1 -1
  26. data/lib/rodauth/features/logout.rb +1 -1
  27. data/lib/rodauth/features/otp.rb +1 -1
  28. data/lib/rodauth/features/password_complexity.rb +1 -1
  29. data/lib/rodauth/features/password_expiration.rb +1 -1
  30. data/lib/rodauth/features/password_grace_period.rb +1 -1
  31. data/lib/rodauth/features/recovery_codes.rb +1 -1
  32. data/lib/rodauth/features/remember.rb +1 -1
  33. data/lib/rodauth/features/reset_password.rb +22 -4
  34. data/lib/rodauth/features/session_expiration.rb +1 -1
  35. data/lib/rodauth/features/single_session.rb +1 -1
  36. data/lib/rodauth/features/sms_codes.rb +1 -1
  37. data/lib/rodauth/features/two_factor_base.rb +1 -1
  38. data/lib/rodauth/features/update_password_hash.rb +1 -1
  39. data/lib/rodauth/features/verify_account.rb +23 -5
  40. data/lib/rodauth/features/verify_account_grace_period.rb +1 -1
  41. data/lib/rodauth/features/verify_change_login.rb +1 -1
  42. data/lib/rodauth/features/verify_login_change.rb +189 -0
  43. data/lib/rodauth/version.rb +1 -1
  44. data/lib/rodauth.rb +16 -2
  45. data/spec/migrate/001_tables.rb +10 -0
  46. data/spec/migrate_travis/001_tables.rb +7 -0
  47. data/spec/reset_password_spec.rb +8 -1
  48. data/spec/rodauth_spec.rb +27 -0
  49. data/spec/spec_helper.rb +11 -7
  50. data/spec/verify_account_grace_period_spec.rb +36 -0
  51. data/spec/verify_account_spec.rb +6 -0
  52. data/spec/verify_login_change_spec.rb +179 -0
  53. data/templates/reset-password-request.str +3 -3
  54. data/templates/verify-account-resend.str +3 -3
  55. data/templates/verify-login-change-email.str +9 -0
  56. data/templates/verify-login-change.str +5 -0
  57. metadata +12 -2
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- EmailBase = Feature.define(:email_base) do
4
+ Feature.define(:email_base, :EmailBase) do
5
5
  auth_value_method :email_subject_prefix, nil
6
6
  auth_value_method :require_mail?, true
7
7
  auth_value_method :token_separator, "_"
@@ -23,9 +23,13 @@ module Rodauth
23
23
  private
24
24
 
25
25
  def create_email(subject, body)
26
+ create_email_to(email_to, subject, body)
27
+ end
28
+
29
+ def create_email_to(to, subject, body)
26
30
  m = Mail.new
27
31
  m.from = email_from
28
- m.to = email_to
32
+ m.to = to
29
33
  m.subject = "#{email_subject_prefix}#{subject}"
30
34
  m.body = body
31
35
  m
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- HTTTBasicAuth = Feature.define(:http_basic_auth) do
4
+ Feature.define(:http_basic_auth, :HttpBasicAuth) do
5
5
  auth_value_method :http_basic_auth_realm, "protected"
6
6
 
7
7
  def session
@@ -3,7 +3,7 @@
3
3
  require 'jwt'
4
4
 
5
5
  module Rodauth
6
- Jwt = Feature.define(:jwt) do
6
+ Feature.define(:jwt, :Jwt) do
7
7
  auth_value_method :invalid_jwt_format_error_message, "invalid JWT format or claim in Authorization header"
8
8
  auth_value_method :json_non_post_error_message, 'non-POST method used in JSON API'
9
9
  auth_value_method :json_not_accepted_error_message, 'Unsupported Accept header. Must accept "application/json" or compatible content type'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- Lockout = Feature.define(:lockout) do
4
+ Feature.define(:lockout, :Lockout) do
5
5
  depends :login, :email_base
6
6
 
7
7
  loaded_templates %w'unlock-account-request unlock-account password-field unlock-account-email'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- Login = Feature.define(:login) do
4
+ Feature.define(:login, :Login) do
5
5
  notice_flash "You have been logged in"
6
6
  error_flash "There was an error logging in"
7
7
  loaded_templates %w'login login-field password-field'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- LoginPasswordRequirementsBase = Feature.define(:login_password_requirements_base) do
4
+ Feature.define(:login_password_requirements_base, :LoginPasswordRequirementsBase) do
5
5
  auth_value_method :login_confirm_param, 'login-confirm'
6
6
  auth_value_method :login_minimum_length, 3
7
7
  auth_value_method :login_maximum_length, 255
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- Logout = Feature.define(:logout) do
4
+ Feature.define(:logout, :Logout) do
5
5
  notice_flash "You have been logged out"
6
6
  loaded_templates %w'logout'
7
7
  view 'logout', 'Logout'
@@ -4,7 +4,7 @@ require 'rotp'
4
4
  require 'rqrcode'
5
5
 
6
6
  module Rodauth
7
- Otp = Feature.define(:otp) do
7
+ Feature.define(:otp, :Otp) do
8
8
  depends :two_factor_base
9
9
 
10
10
  additional_form_tags 'otp_disable'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- PasswordComplexity = Feature.define(:password_complexity) do
4
+ Feature.define(:password_complexity, :PasswordComplexity) do
5
5
  depends :login_password_requirements_base
6
6
 
7
7
  auth_value_method :password_dictionary_file, nil
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- PasswordExpiration = Feature.define(:password_expiration) do
4
+ Feature.define(:password_expiration, :PasswordExpiration) do
5
5
  depends :login, :change_password
6
6
 
7
7
  error_flash "Your password has expired and needs to be changed"
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- PasswordGracePeriod = Feature.define(:password_grace_period) do
4
+ Feature.define(:password_grace_period, :PasswordGracePeriod) do
5
5
  auth_value_method :password_grace_period, 300
6
6
  auth_value_method :last_password_entry_session_key, :last_password_entry
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- RecoveryCodes = Feature.define(:recovery_codes) do
4
+ Feature.define(:recovery_codes, :RecoveryCodes) do
5
5
  depends :two_factor_base
6
6
 
7
7
  additional_form_tags 'recovery_auth'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- Remember = Feature.define(:remember) do
4
+ Feature.define(:remember, :Remember) do
5
5
  depends :confirm_password
6
6
 
7
7
  notice_flash "Your remember setting has been updated"
@@ -1,15 +1,16 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- ResetPassword = Feature.define(:reset_password) do
4
+ Feature.define(:reset_password, :ResetPassword) do
5
5
  depends :login, :email_base, :login_password_requirements_base
6
6
 
7
7
  notice_flash "Your password has been reset"
8
8
  notice_flash "An email has been sent to you with a link to reset the password for your account", 'reset_password_email_sent'
9
9
  error_flash "There was an error resetting your password"
10
10
  error_flash "There was an error requesting a password reset", 'reset_password_request'
11
- loaded_templates %w'reset-password password-field password-confirm-field reset-password-email'
11
+ loaded_templates %w'reset-password-request reset-password password-field password-confirm-field reset-password-email'
12
12
  view 'reset-password', 'Reset Password'
13
+ view 'reset-password-request', 'Request Password Reset', 'reset_password_request'
13
14
  additional_form_tags
14
15
  additional_form_tags 'reset_password_request'
15
16
  before
@@ -32,12 +33,13 @@ module Rodauth
32
33
  auth_value_method :reset_password_key_column, :key
33
34
  auth_value_method :reset_password_session_key, :reset_password_key
34
35
 
35
- auth_value_methods :reset_password_email_sent_redirect
36
+ auth_value_methods :reset_password_email_sent_redirect, :reset_password_request_link
36
37
 
37
38
  auth_methods(
38
39
  :create_reset_password_key,
39
40
  :create_reset_password_email,
40
41
  :get_reset_password_key,
42
+ :login_failed_reset_password_request_form,
41
43
  :remove_reset_password_key,
42
44
  :reset_password_email_body,
43
45
  :reset_password_email_link,
@@ -53,6 +55,10 @@ module Rodauth
53
55
  check_already_logged_in
54
56
  before_reset_password_request_route
55
57
 
58
+ r.get do
59
+ reset_password_request_view
60
+ end
61
+
56
62
  r.post do
57
63
  if account_from_login(param(login_param)) && open_account?
58
64
  generate_reset_password_key_value
@@ -173,13 +179,21 @@ module Rodauth
173
179
  password_reset_ds(id).get(reset_password_key_column)
174
180
  end
175
181
 
182
+ def login_form_footer
183
+ super + reset_password_request_link
184
+ end
185
+
186
+ def reset_password_request_link
187
+ "<p><a href=\"#{prefix}/#{reset_password_request_route}\">Forgot Password?</a></p>"
188
+ end
189
+
176
190
  private
177
191
 
178
192
  attr_reader :reset_password_key_value
179
193
 
180
194
  def after_login_failure
181
195
  unless only_json?
182
- @login_form_header = render("reset-password-request")
196
+ @login_form_header = login_failed_reset_password_request_form
183
197
  end
184
198
  super
185
199
  end
@@ -197,6 +211,10 @@ module Rodauth
197
211
  create_email(reset_password_email_subject, reset_password_email_body)
198
212
  end
199
213
 
214
+ def login_failed_reset_password_request_form
215
+ render("reset-password-request")
216
+ end
217
+
200
218
  def reset_password_email_body
201
219
  render('reset-password-email')
202
220
  end
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- SessionExpiration = Feature.define(:session_expiration) do
4
+ Feature.define(:session_expiration, :SessionExpiration) do
5
5
  error_flash "This session has expired, please login again."
6
6
 
7
7
  auth_value_method :max_session_lifetime, 86400
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- SingleSession = Feature.define(:single_session) do
4
+ Feature.define(:single_session, :SingleSession) do
5
5
  error_flash 'This session has been logged out as another session has become active'
6
6
  redirect
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- SmsCodes = Feature.define(:sms_codes) do
4
+ Feature.define(:sms_codes, :SmsCodes) do
5
5
  depends :two_factor_base
6
6
 
7
7
  additional_form_tags 'sms_auth'
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- TwoFactorBase = Feature.define(:two_factor_base) do
4
+ Feature.define(:two_factor_base, :TwoFactorBase) do
5
5
  after :two_factor_authentication
6
6
 
7
7
  redirect :two_factor_auth
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- UpdatePasswordHash = Feature.define(:update_password_hash) do
4
+ Feature.define(:update_password_hash, :UpdatePasswordHash) do
5
5
  depends :login_password_requirements_base
6
6
 
7
7
  def password_match?(password)
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VerifyAccount = Feature.define(:verify_account) do
4
+ Feature.define(:verify_account, :VerifyAccount) do
5
5
  depends :login, :create_account, :email_base
6
6
 
7
7
  error_flash "Unable to verify account"
@@ -33,9 +33,10 @@ module Rodauth
33
33
  auth_value_method :verify_account_key_column, :key
34
34
  auth_value_method :verify_account_session_key, :verify_account_key
35
35
 
36
- auth_value_methods :verify_account_key_value
36
+ auth_value_methods :verify_account_resend_link
37
37
 
38
38
  auth_methods(
39
+ :allow_resending_verify_account_email?,
39
40
  :create_verify_account_key,
40
41
  :create_verify_account_email,
41
42
  :get_verify_account_key,
@@ -45,7 +46,8 @@ module Rodauth
45
46
  :verify_account,
46
47
  :verify_account_email_body,
47
48
  :verify_account_email_link,
48
- :verify_account_key_insert_hash
49
+ :verify_account_key_insert_hash,
50
+ :verify_account_key_value
49
51
  )
50
52
 
51
53
  auth_private_methods(
@@ -56,8 +58,12 @@ module Rodauth
56
58
  verify_account_check_already_logged_in
57
59
  before_verify_account_resend_route
58
60
 
61
+ r.get do
62
+ resend_verify_account_view
63
+ end
64
+
59
65
  r.post do
60
- if account_from_login(param(login_param)) && !open_account?
66
+ if account_from_login(param(login_param)) && allow_resending_verify_account_email?
61
67
  before_verify_account_email_resend
62
68
  if verify_account_email_resend
63
69
  after_verify_account_email_resend
@@ -119,6 +125,10 @@ module Rodauth
119
125
  end
120
126
  end
121
127
 
128
+ def allow_resending_verify_account_email?
129
+ account[account_status_column] == account_unverified_status_value
130
+ end
131
+
122
132
  def remove_verify_account_key
123
133
  verify_account_ds.delete
124
134
  end
@@ -139,7 +149,7 @@ module Rodauth
139
149
  end
140
150
 
141
151
  def new_account(login)
142
- if account_from_login(login)
152
+ if account_from_login(login) && allow_resending_verify_account_email?
143
153
  set_redirect_error_status(unopen_account_error_status)
144
154
  set_error_flash attempt_to_create_unverified_account_notice_message
145
155
  response.write resend_verify_account_view
@@ -176,6 +186,14 @@ module Rodauth
176
186
  false
177
187
  end
178
188
 
189
+ def login_form_footer
190
+ super + verify_account_resend_link
191
+ end
192
+
193
+ def verify_account_resend_link
194
+ "<p><a href=\"#{prefix}/#{verify_account_resend_route}\">Resend Verify Account Information</a></p>"
195
+ end
196
+
179
197
  private
180
198
 
181
199
  attr_reader :verify_account_key_value
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VerifyAccountGracePeriod = Feature.define(:verify_account_grace_period) do
4
+ Feature.define(:verify_account_grace_period, :VerifyAccountGracePeriod) do
5
5
  depends :verify_account
6
6
  error_flash "Cannot change login for unverified account. Please verify this account before changing the login.", "unverified_change_login"
7
7
  redirect :unverified_change_login
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VerifyChangeLogin = Feature.define(:verify_change_login) do
4
+ Feature.define(:verify_change_login, :VerifyChangeLogin) do
5
5
  depends :change_login, :verify_account_grace_period
6
6
 
7
7
  def change_login_notice_flash
@@ -0,0 +1,189 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:verify_login_change, :VerifyLoginChange) do
5
+ depends :change_login, :email_base
6
+
7
+ error_flash "Unable to verify login change"
8
+ notice_flash "Your login change has been verified"
9
+ loaded_templates %w'verify-login-change verify-login-change-email'
10
+ view 'verify-login-change', 'Verify Login Change'
11
+ additional_form_tags
12
+ after
13
+ before
14
+ button 'Verify Login Change'
15
+ redirect
16
+
17
+ auth_value_method :no_matching_verify_login_change_key_message, "invalid verify login change key"
18
+ auth_value_method :verify_login_change_autologin?, false
19
+ auth_value_method :verify_login_change_deadline_column, :deadline
20
+ auth_value_method :verify_login_change_deadline_interval, {:days=>1}
21
+ auth_value_method :verify_login_change_email_subject, 'Verify Login Change'
22
+ auth_value_method :verify_login_change_id_column, :id
23
+ auth_value_method :verify_login_change_key_column, :key
24
+ auth_value_method :verify_login_change_key_param, 'key'
25
+ auth_value_method :verify_login_change_login_column, :login
26
+ auth_value_method :verify_login_change_session_key, :verify_login_change_key
27
+ auth_value_method :verify_login_change_table, :account_login_change_keys
28
+
29
+ auth_methods(
30
+ :create_verify_login_change_email,
31
+ :create_verify_login_change_key,
32
+ :get_verify_login_change_login_and_key,
33
+ :remove_verify_login_change_key,
34
+ :send_verify_login_change_email,
35
+ :verify_login_change,
36
+ :verify_login_change_email_body,
37
+ :verify_login_change_email_link,
38
+ :verify_login_change_key_insert_hash,
39
+ :verify_login_change_key_value,
40
+ :verify_login_change_new_login,
41
+ :verify_login_change_old_login
42
+ )
43
+
44
+ auth_private_methods(
45
+ :account_from_verify_login_change_key
46
+ )
47
+
48
+ route do |r|
49
+ check_already_logged_in
50
+ before_verify_login_change_route
51
+
52
+ r.get do
53
+ if key = param_or_nil(verify_login_change_key_param)
54
+ session[verify_login_change_session_key] = key
55
+ redirect(r.path)
56
+ end
57
+
58
+ if key = session[verify_login_change_session_key]
59
+ if account_from_verify_login_change_key(key)
60
+ verify_login_change_view
61
+ else
62
+ session[verify_login_change_session_key] = nil
63
+ set_redirect_error_flash no_matching_verify_login_change_key_message
64
+ redirect require_login_redirect
65
+ end
66
+ end
67
+ end
68
+
69
+ r.post do
70
+ key = session[verify_login_change_session_key] || param(verify_login_change_key_param)
71
+ unless account_from_verify_login_change_key(key)
72
+ set_redirect_error_status(invalid_key_error_status)
73
+ set_redirect_error_flash verify_login_change_error_flash
74
+ redirect verify_login_change_redirect
75
+ end
76
+
77
+ transaction do
78
+ before_verify_login_change
79
+ verify_login_change
80
+ remove_verify_login_change_key
81
+ after_verify_login_change
82
+ end
83
+
84
+ if verify_login_change_autologin?
85
+ update_session
86
+ end
87
+
88
+ session[verify_login_change_session_key] = nil
89
+ set_notice_flash verify_login_change_notice_flash
90
+ redirect verify_login_change_redirect
91
+ end
92
+ end
93
+
94
+ def remove_verify_login_change_key
95
+ verify_login_change_ds.delete
96
+ end
97
+
98
+ def verify_login_change
99
+ update_account(login_column=>verify_login_change_new_login)
100
+ end
101
+
102
+ def account_from_verify_login_change_key(key)
103
+ @account = _account_from_verify_login_change_key(key)
104
+ end
105
+
106
+ def send_verify_login_change_email(login)
107
+ create_verify_login_change_email(login).deliver!
108
+ end
109
+
110
+ def verify_login_change_email_link
111
+ token_link(verify_login_change_route, verify_login_change_key_param, verify_login_change_key_value)
112
+ end
113
+
114
+ def get_verify_login_change_login_and_key(id)
115
+ verify_login_change_ds(id).get([verify_login_change_login_column, verify_login_change_key_column])
116
+ end
117
+
118
+ def change_login_notice_flash
119
+ "An email has been sent to you with a link to verify your login change"
120
+ end
121
+
122
+ def verify_login_change_old_login
123
+ account_ds.get(login_column)
124
+ end
125
+
126
+ attr_reader :verify_login_change_key_value
127
+ attr_reader :verify_login_change_new_login
128
+
129
+ private
130
+
131
+ def after_close_account
132
+ remove_verify_login_change_key
133
+ super if defined?(super)
134
+ end
135
+
136
+ def update_login(login)
137
+ generate_verify_login_change_key_value
138
+ @verify_login_change_new_login = login
139
+ create_verify_login_change_key(login)
140
+ send_verify_login_change_email(login)
141
+ end
142
+
143
+ def generate_verify_login_change_key_value
144
+ @verify_login_change_key_value = random_key
145
+ end
146
+
147
+ def create_verify_login_change_key(login)
148
+ ds = verify_login_change_ds
149
+ transaction do
150
+ ds.where((Sequel::CURRENT_TIMESTAMP > verify_login_change_deadline_column) | ~Sequel.expr(verify_login_change_login_column=>login)).delete
151
+ if e = raised_uniqueness_violation{ds.insert(verify_login_change_key_insert_hash(login))}
152
+ old_login, key = get_verify_login_change_login_and_key(account_id)
153
+ # If inserting into the verify account table causes a violation, we can pull the
154
+ # key from the verify login change table if the logins match, or reraise.
155
+ @verify_login_change_key_value = if old_login.downcase == login.downcase
156
+ key
157
+ end
158
+ raise e unless @verify_login_change_key_value
159
+ end
160
+ end
161
+ end
162
+
163
+ def verify_login_change_key_insert_hash(login)
164
+ hash = {verify_login_change_id_column=>account_id, verify_login_change_key_column=>verify_login_change_key_value, verify_login_change_login_column=>login}
165
+ set_deadline_value(hash, verify_login_change_deadline_column, verify_login_change_deadline_interval)
166
+ hash
167
+ end
168
+
169
+ def create_verify_login_change_email(login)
170
+ create_email_to(login, verify_login_change_email_subject, verify_login_change_email_body)
171
+ end
172
+
173
+ def verify_login_change_email_body
174
+ render('verify-login-change-email')
175
+ end
176
+
177
+ def verify_login_change_ds(id=account_id)
178
+ db[verify_login_change_table].where(verify_login_change_id_column=>id)
179
+ end
180
+
181
+ def _account_from_verify_login_change_key(token)
182
+ account_from_key(token) do |id|
183
+ @verify_login_change_new_login, key = get_verify_login_change_login_and_key(id)
184
+ key
185
+ end
186
+ end
187
+ end
188
+ end
189
+
@@ -1,7 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Rodauth
4
- VERSION = '1.9.0'.freeze
4
+ VERSION = '1.10.0'.freeze
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/rodauth.rb CHANGED
@@ -20,7 +20,14 @@ module Rodauth
20
20
 
21
21
  def self.configure(app, opts={}, &block)
22
22
  app.opts[:rodauth_json] = opts.fetch(:json, app.opts[:rodauth_json])
23
- ((app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)).configure(&block)
23
+ auth_class = (app.opts[:rodauths] ||= {})[opts[:name]] ||= Class.new(Auth)
24
+ if !auth_class.roda_class
25
+ auth_class.roda_class = app
26
+ elsif auth_class.roda_class != app
27
+ auth_class = app.opts[:rodauths][opts[:name]] = Class.new(auth_class)
28
+ auth_class.roda_class = app
29
+ end
30
+ auth_class.configure(&block)
24
31
  end
25
32
 
26
33
  FEATURES = {}
@@ -97,7 +104,7 @@ module Rodauth
97
104
  routes << handle_meth
98
105
  end
99
106
 
100
- def self.define(name, &block)
107
+ def self.define(name, constant=nil, &block)
101
108
  feature = new
102
109
  feature.dependencies = []
103
110
  feature.routes = []
@@ -105,6 +112,12 @@ module Rodauth
105
112
  configuration = feature.configuration = FeatureConfiguration.new
106
113
  feature.module_eval(&block)
107
114
  configuration.def_configuration_methods(feature)
115
+
116
+ if constant
117
+ Rodauth.const_set(constant, feature)
118
+ Rodauth::FeatureConfiguration.const_set(constant, configuration)
119
+ end
120
+
108
121
  FEATURES[name] = feature
109
122
  end
110
123
 
@@ -180,6 +193,7 @@ module Rodauth
180
193
 
181
194
  class Auth
182
195
  class << self
196
+ attr_accessor :roda_class
183
197
  attr_reader :features
184
198
  attr_reader :routes
185
199
  attr_accessor :route_hash
@@ -45,6 +45,14 @@ Sequel.migration do
45
45
  DateTime :requested_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
46
46
  end
47
47
 
48
+ # Used by the verify login change feature
49
+ create_table(:account_login_change_keys) do
50
+ foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
51
+ String :key, :null=>false
52
+ String :login, :null=>false
53
+ DateTime :deadline, deadline_opts[1]
54
+ end
55
+
48
56
  # Used by the remember me feature
49
57
  create_table(:account_remember_keys) do
50
58
  foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
@@ -121,6 +129,7 @@ Sequel.migration do
121
129
  run "GRANT ALL ON accounts TO #{user}"
122
130
  run "GRANT ALL ON account_password_reset_keys TO #{user}"
123
131
  run "GRANT ALL ON account_verification_keys TO #{user}"
132
+ run "GRANT ALL ON account_login_change_keys TO #{user}"
124
133
  run "GRANT ALL ON account_remember_keys TO #{user}"
125
134
  run "GRANT ALL ON account_login_failures TO #{user}"
126
135
  run "GRANT ALL ON account_lockouts TO #{user}"
@@ -143,6 +152,7 @@ Sequel.migration do
143
152
  :account_lockouts,
144
153
  :account_login_failures,
145
154
  :account_remember_keys,
155
+ :account_login_change_keys,
146
156
  :account_verification_keys,
147
157
  :account_password_reset_keys,
148
158
  :accounts,
@@ -52,6 +52,13 @@ Sequel.migration do
52
52
  DateTime :requested_at, :null=>false, :default=>Sequel::CURRENT_TIMESTAMP
53
53
  end
54
54
 
55
+ create_table(:account_login_change_keys) do
56
+ foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
57
+ String :key, :null=>false
58
+ String :login, :null=>false
59
+ DateTime :deadline, deadline_opts[1]
60
+ end
61
+
55
62
  create_table(:account_remember_keys) do
56
63
  foreign_key :id, :accounts, :primary_key=>true, :type=>:Bignum
57
64
  String :key, :null=>false