rodauth 1.9.0 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
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