rodauth 1.22.0 → 2.3.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 (198) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +190 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +210 -80
  5. data/doc/account_expiration.rdoc +12 -26
  6. data/doc/active_sessions.rdoc +49 -0
  7. data/doc/audit_logging.rdoc +44 -0
  8. data/doc/base.rdoc +75 -128
  9. data/doc/change_login.rdoc +7 -14
  10. data/doc/change_password.rdoc +9 -13
  11. data/doc/change_password_notify.rdoc +2 -2
  12. data/doc/close_account.rdoc +9 -16
  13. data/doc/confirm_password.rdoc +12 -5
  14. data/doc/create_account.rdoc +11 -22
  15. data/doc/disallow_password_reuse.rdoc +6 -13
  16. data/doc/email_auth.rdoc +15 -14
  17. data/doc/email_base.rdoc +6 -15
  18. data/doc/guides/admin_activation.rdoc +46 -0
  19. data/doc/guides/already_authenticated.rdoc +10 -0
  20. data/doc/guides/alternative_login.rdoc +46 -0
  21. data/doc/guides/create_account_programmatically.rdoc +38 -0
  22. data/doc/guides/delay_password.rdoc +25 -0
  23. data/doc/guides/email_only.rdoc +16 -0
  24. data/doc/guides/i18n.rdoc +26 -0
  25. data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
  26. data/doc/guides/links.rdoc +12 -0
  27. data/doc/guides/login_return.rdoc +37 -0
  28. data/doc/guides/password_column.rdoc +25 -0
  29. data/doc/guides/password_confirmation.rdoc +37 -0
  30. data/doc/guides/password_requirements.rdoc +30 -0
  31. data/doc/guides/paths.rdoc +36 -0
  32. data/doc/guides/query_params.rdoc +9 -0
  33. data/doc/guides/redirects.rdoc +17 -0
  34. data/doc/guides/registration_field.rdoc +68 -0
  35. data/doc/guides/require_mfa.rdoc +30 -0
  36. data/doc/guides/reset_password_autologin.rdoc +21 -0
  37. data/doc/guides/status_column.rdoc +28 -0
  38. data/doc/guides/totp_or_recovery.rdoc +16 -0
  39. data/doc/http_basic_auth.rdoc +10 -1
  40. data/doc/jwt.rdoc +22 -22
  41. data/doc/jwt_cors.rdoc +2 -3
  42. data/doc/jwt_refresh.rdoc +23 -8
  43. data/doc/lockout.rdoc +17 -15
  44. data/doc/login.rdoc +17 -2
  45. data/doc/login_password_requirements_base.rdoc +18 -37
  46. data/doc/logout.rdoc +2 -2
  47. data/doc/otp.rdoc +25 -19
  48. data/doc/password_complexity.rdoc +10 -26
  49. data/doc/password_expiration.rdoc +11 -25
  50. data/doc/password_grace_period.rdoc +16 -2
  51. data/doc/recovery_codes.rdoc +18 -12
  52. data/doc/release_notes/1.23.0.txt +32 -0
  53. data/doc/release_notes/2.0.0.txt +361 -0
  54. data/doc/release_notes/2.1.0.txt +31 -0
  55. data/doc/release_notes/2.2.0.txt +39 -0
  56. data/doc/release_notes/2.3.0.txt +37 -0
  57. data/doc/remember.rdoc +40 -64
  58. data/doc/reset_password.rdoc +12 -9
  59. data/doc/session_expiration.rdoc +1 -0
  60. data/doc/single_session.rdoc +16 -25
  61. data/doc/sms_codes.rdoc +24 -14
  62. data/doc/two_factor_base.rdoc +60 -22
  63. data/doc/verify_account.rdoc +14 -12
  64. data/doc/verify_account_grace_period.rdoc +6 -2
  65. data/doc/verify_login_change.rdoc +9 -8
  66. data/doc/webauthn.rdoc +115 -0
  67. data/doc/webauthn_login.rdoc +15 -0
  68. data/doc/webauthn_verify_account.rdoc +9 -0
  69. data/javascript/webauthn_auth.js +45 -0
  70. data/javascript/webauthn_setup.js +35 -0
  71. data/lib/roda/plugins/rodauth.rb +1 -1
  72. data/lib/rodauth.rb +36 -28
  73. data/lib/rodauth/features/account_expiration.rb +5 -5
  74. data/lib/rodauth/features/active_sessions.rb +158 -0
  75. data/lib/rodauth/features/audit_logging.rb +98 -0
  76. data/lib/rodauth/features/base.rb +144 -43
  77. data/lib/rodauth/features/change_password_notify.rb +2 -2
  78. data/lib/rodauth/features/close_account.rb +8 -6
  79. data/lib/rodauth/features/confirm_password.rb +40 -2
  80. data/lib/rodauth/features/create_account.rb +8 -13
  81. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  82. data/lib/rodauth/features/disallow_password_reuse.rb +1 -1
  83. data/lib/rodauth/features/email_auth.rb +31 -30
  84. data/lib/rodauth/features/email_base.rb +9 -4
  85. data/lib/rodauth/features/http_basic_auth.rb +55 -35
  86. data/lib/rodauth/features/jwt.rb +63 -16
  87. data/lib/rodauth/features/jwt_cors.rb +15 -15
  88. data/lib/rodauth/features/jwt_refresh.rb +42 -13
  89. data/lib/rodauth/features/lockout.rb +12 -14
  90. data/lib/rodauth/features/login.rb +64 -15
  91. data/lib/rodauth/features/login_password_requirements_base.rb +13 -8
  92. data/lib/rodauth/features/otp.rb +77 -80
  93. data/lib/rodauth/features/password_complexity.rb +8 -13
  94. data/lib/rodauth/features/password_expiration.rb +2 -2
  95. data/lib/rodauth/features/password_grace_period.rb +17 -10
  96. data/lib/rodauth/features/recovery_codes.rb +49 -53
  97. data/lib/rodauth/features/remember.rb +11 -27
  98. data/lib/rodauth/features/reset_password.rb +26 -26
  99. data/lib/rodauth/features/session_expiration.rb +7 -10
  100. data/lib/rodauth/features/single_session.rb +8 -6
  101. data/lib/rodauth/features/sms_codes.rb +62 -72
  102. data/lib/rodauth/features/two_factor_base.rb +134 -30
  103. data/lib/rodauth/features/verify_account.rb +29 -21
  104. data/lib/rodauth/features/verify_account_grace_period.rb +18 -9
  105. data/lib/rodauth/features/verify_login_change.rb +12 -11
  106. data/lib/rodauth/features/webauthn.rb +505 -0
  107. data/lib/rodauth/features/webauthn_login.rb +70 -0
  108. data/lib/rodauth/features/webauthn_verify_account.rb +46 -0
  109. data/lib/rodauth/migrations.rb +16 -5
  110. data/lib/rodauth/version.rb +2 -2
  111. data/templates/button.str +1 -3
  112. data/templates/change-login.str +1 -2
  113. data/templates/change-password.str +3 -5
  114. data/templates/close-account.str +2 -2
  115. data/templates/confirm-password.str +1 -1
  116. data/templates/create-account.str +1 -1
  117. data/templates/email-auth-request-form.str +2 -3
  118. data/templates/email-auth.str +1 -1
  119. data/templates/global-logout-field.str +6 -0
  120. data/templates/login-confirm-field.str +2 -4
  121. data/templates/login-display.str +3 -2
  122. data/templates/login-field.str +2 -4
  123. data/templates/login-form-footer.str +6 -0
  124. data/templates/login-form.str +7 -0
  125. data/templates/login.str +1 -9
  126. data/templates/logout.str +1 -1
  127. data/templates/multi-phase-login.str +3 -0
  128. data/templates/otp-auth-code-field.str +5 -3
  129. data/templates/otp-auth.str +1 -1
  130. data/templates/otp-disable.str +1 -1
  131. data/templates/otp-setup.str +3 -3
  132. data/templates/password-confirm-field.str +2 -4
  133. data/templates/password-field.str +2 -4
  134. data/templates/recovery-auth.str +3 -6
  135. data/templates/recovery-codes.str +1 -1
  136. data/templates/remember.str +15 -20
  137. data/templates/reset-password-request.str +3 -3
  138. data/templates/reset-password.str +1 -2
  139. data/templates/sms-auth.str +1 -1
  140. data/templates/sms-code-field.str +5 -3
  141. data/templates/sms-confirm.str +1 -2
  142. data/templates/sms-disable.str +1 -2
  143. data/templates/sms-request.str +1 -1
  144. data/templates/sms-setup.str +6 -4
  145. data/templates/two-factor-auth.str +5 -0
  146. data/templates/two-factor-disable.str +6 -0
  147. data/templates/two-factor-manage.str +16 -0
  148. data/templates/unlock-account-request.str +4 -4
  149. data/templates/unlock-account.str +1 -1
  150. data/templates/verify-account-resend.str +3 -3
  151. data/templates/verify-account.str +1 -2
  152. data/templates/verify-login-change.str +1 -1
  153. data/templates/webauthn-auth.str +11 -0
  154. data/templates/webauthn-remove.str +14 -0
  155. data/templates/webauthn-setup.str +12 -0
  156. metadata +94 -54
  157. data/Rakefile +0 -179
  158. data/doc/verify_change_login.rdoc +0 -11
  159. data/lib/rodauth/features/verify_change_login.rb +0 -20
  160. data/spec/account_expiration_spec.rb +0 -225
  161. data/spec/all.rb +0 -1
  162. data/spec/change_login_spec.rb +0 -156
  163. data/spec/change_password_notify_spec.rb +0 -33
  164. data/spec/change_password_spec.rb +0 -202
  165. data/spec/close_account_spec.rb +0 -162
  166. data/spec/confirm_password_spec.rb +0 -70
  167. data/spec/create_account_spec.rb +0 -127
  168. data/spec/disallow_common_passwords_spec.rb +0 -93
  169. data/spec/disallow_password_reuse_spec.rb +0 -179
  170. data/spec/email_auth_spec.rb +0 -285
  171. data/spec/http_basic_auth_spec.rb +0 -143
  172. data/spec/jwt_cors_spec.rb +0 -57
  173. data/spec/jwt_refresh_spec.rb +0 -256
  174. data/spec/jwt_spec.rb +0 -235
  175. data/spec/lockout_spec.rb +0 -250
  176. data/spec/login_spec.rb +0 -328
  177. data/spec/migrate/001_tables.rb +0 -184
  178. data/spec/migrate/002_account_password_hash_column.rb +0 -11
  179. data/spec/migrate_password/001_tables.rb +0 -73
  180. data/spec/migrate_travis/001_tables.rb +0 -141
  181. data/spec/password_complexity_spec.rb +0 -109
  182. data/spec/password_expiration_spec.rb +0 -244
  183. data/spec/password_grace_period_spec.rb +0 -93
  184. data/spec/remember_spec.rb +0 -451
  185. data/spec/reset_password_spec.rb +0 -229
  186. data/spec/rodauth_spec.rb +0 -343
  187. data/spec/session_expiration_spec.rb +0 -58
  188. data/spec/single_session_spec.rb +0 -127
  189. data/spec/spec_helper.rb +0 -327
  190. data/spec/two_factor_spec.rb +0 -1462
  191. data/spec/update_password_hash_spec.rb +0 -40
  192. data/spec/verify_account_grace_period_spec.rb +0 -171
  193. data/spec/verify_account_spec.rb +0 -240
  194. data/spec/verify_change_login_spec.rb +0 -46
  195. data/spec/verify_login_change_spec.rb +0 -232
  196. data/spec/views/layout-other.str +0 -11
  197. data/spec/views/layout.str +0 -11
  198. data/spec/views/login.str +0 -21
@@ -4,7 +4,7 @@ module Rodauth
4
4
  Feature.define(:change_password_notify, :ChangePasswordNotify) do
5
5
  depends :change_password, :email_base
6
6
 
7
- auth_value_method :password_changed_email_subject, 'Password Changed'
7
+ translatable_method :password_changed_email_subject, 'Password Changed'
8
8
 
9
9
  auth_value_methods(
10
10
  :password_changed_email_body
@@ -17,7 +17,7 @@ module Rodauth
17
17
  private
18
18
 
19
19
  def send_password_changed_email
20
- create_password_changed_email.deliver!
20
+ send_email(create_password_changed_email)
21
21
  end
22
22
 
23
23
  def create_password_changed_email
@@ -33,7 +33,11 @@ module Rodauth
33
33
  end
34
34
 
35
35
  r.post do
36
- if !close_account_requires_password? || password_match?(param(password_param))
36
+ catch_error do
37
+ if close_account_requires_password? && !password_match?(param(password_param))
38
+ throw_error_status(invalid_password_error_status, password_param, invalid_password_message)
39
+ end
40
+
37
41
  transaction do
38
42
  before_close_account
39
43
  close_account
@@ -46,12 +50,10 @@ module Rodauth
46
50
 
47
51
  set_notice_flash close_account_notice_flash
48
52
  redirect close_account_redirect
49
- else
50
- set_response_error_status(invalid_password_error_status)
51
- set_field_error(password_param, invalid_password_message)
52
- set_error_flash close_account_error_flash
53
- close_account_view
54
53
  end
54
+
55
+ set_error_flash close_account_error_flash
56
+ close_account_view
55
57
  end
56
58
  end
57
59
 
@@ -4,20 +4,26 @@ module Rodauth
4
4
  Feature.define(:confirm_password, :ConfirmPassword) do
5
5
  notice_flash "Your password has been confirmed"
6
6
  error_flash "There was an error confirming your password"
7
+ error_flash "You need to confirm your password before continuing", 'password_authentication_required'
7
8
  loaded_templates %w'confirm-password password-field'
8
9
  view 'confirm-password', 'Confirm Password'
9
10
  additional_form_tags
10
11
  button 'Confirm Password'
11
12
  before
12
13
  after
14
+ redirect(:password_authentication_required){confirm_password_path}
13
15
 
14
16
  session_key :confirm_password_redirect_session_key, :confirm_password_redirect
17
+ translatable_method :confirm_password_link_text, "Enter Password"
18
+ auth_value_method :password_authentication_required_error_status, 401
19
+
15
20
  auth_value_methods :confirm_password_redirect
16
21
 
17
22
  auth_methods :confirm_password
18
23
 
19
24
  route do |r|
20
- require_account
25
+ require_login
26
+ require_account_session
21
27
  before_confirm_password_route
22
28
 
23
29
  request.get do
@@ -42,12 +48,44 @@ module Rodauth
42
48
  end
43
49
  end
44
50
 
51
+ def require_password_authentication
52
+ require_login
53
+
54
+ if require_password_authentication? && has_password?
55
+ set_redirect_error_status(password_authentication_required_error_status)
56
+ set_redirect_error_flash password_authentication_required_error_flash
57
+ set_session_value(confirm_password_redirect_session_key, request.fullpath)
58
+ redirect password_authentication_required_redirect
59
+ end
60
+ end
61
+
45
62
  def confirm_password
63
+ authenticated_by.delete('autologin')
64
+ authenticated_by.delete('remember')
65
+ authenticated_by.delete('email_auth')
66
+ authenticated_by.delete('password')
67
+ authenticated_by.unshift("password")
68
+ remove_session_value(autologin_type_session_key)
46
69
  nil
47
70
  end
48
71
 
49
72
  def confirm_password_redirect
50
- session.delete(confirm_password_redirect_session_key) || default_redirect
73
+ remove_session_value(confirm_password_redirect_session_key) || default_redirect
74
+ end
75
+
76
+ private
77
+
78
+ def _two_factor_auth_links
79
+ links = (super if defined?(super)) || []
80
+ if authenticated_by.length == 1 && !authenticated_by.include?('password') && has_password?
81
+ links << [5, confirm_password_path, confirm_password_link_text]
82
+ end
83
+ links
84
+ end
85
+
86
+ def require_password_authentication?
87
+ return true if defined?(super) && super
88
+ !authenticated_by.include?('password')
51
89
  end
52
90
  end
53
91
  end
@@ -2,9 +2,8 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:create_account, :CreateAccount) do
5
- depends :login_password_requirements_base
5
+ depends :login, :login_password_requirements_base
6
6
 
7
- depends :login
8
7
  notice_flash 'Your account has been created'
9
8
  error_flash "There was an error creating your account"
10
9
  loaded_templates %w'create-account login-field login-confirm-field password-field password-confirm-field'
@@ -16,10 +15,9 @@ module Rodauth
16
15
  redirect
17
16
 
18
17
  auth_value_method :create_account_autologin?, true
18
+ translatable_method :create_account_link_text, "Create a New Account"
19
19
  auth_value_method :create_account_set_password?, true
20
20
 
21
- auth_value_methods :create_account_link
22
-
23
21
  auth_methods(
24
22
  :save_account,
25
23
  :set_new_account_password
@@ -32,6 +30,7 @@ module Rodauth
32
30
  route do |r|
33
31
  check_already_logged_in
34
32
  before_create_account_route
33
+ @password_field_autocomplete_value = 'new-password'
35
34
 
36
35
  r.get do
37
36
  create_account_view
@@ -76,7 +75,7 @@ module Rodauth
76
75
  end
77
76
  after_create_account
78
77
  if create_account_autologin?
79
- update_session
78
+ autologin_session('create_account')
80
79
  end
81
80
  set_notice_flash create_account_notice_flash
82
81
  redirect create_account_redirect
@@ -88,14 +87,6 @@ module Rodauth
88
87
  end
89
88
  end
90
89
 
91
- def create_account_link
92
- "<p><a href=\"#{prefix}/#{create_account_route}\">Create a New Account</a></p>"
93
- end
94
-
95
- def login_form_footer
96
- super + create_account_link
97
- end
98
-
99
90
  def set_new_account_password(password)
100
91
  account[account_password_hash_column] = password_hash(password)
101
92
  end
@@ -121,6 +112,10 @@ module Rodauth
121
112
 
122
113
  private
123
114
 
115
+ def _login_form_footer_links
116
+ super << [10, create_account_path, create_account_link_text]
117
+ end
118
+
124
119
  def _new_account(login)
125
120
  acc = {login_column=>login}
126
121
  unless skip_status_checks?
@@ -5,7 +5,7 @@ module Rodauth
5
5
  depends :login_password_requirements_base
6
6
 
7
7
  auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
8
- auth_value_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
8
+ translatable_method :password_is_one_of_the_most_common_message, "is one of the most common passwords"
9
9
  auth_value_method :most_common_passwords, nil
10
10
 
11
11
  auth_methods :password_one_of_most_common?
@@ -4,7 +4,7 @@ module Rodauth
4
4
  Feature.define(:disallow_password_reuse, :DisallowPasswordReuse) do
5
5
  depends :login_password_requirements_base
6
6
 
7
- auth_value_method :password_same_as_previous_password_message, "same as previous password"
7
+ translatable_method :password_same_as_previous_password_message, "same as previous password"
8
8
  auth_value_method :previous_password_account_id_column, :account_id
9
9
  auth_value_method :previous_password_hash_column, :password_hash
10
10
  auth_value_method :previous_password_hash_table, :account_previous_password_hashes
@@ -4,8 +4,6 @@ module Rodauth
4
4
  Feature.define(:email_auth, :EmailAuth) do
5
5
  depends :login, :email_base
6
6
 
7
- def_deprecated_alias :no_matching_email_auth_key_error_flash, :no_matching_email_auth_key_message
8
-
9
7
  notice_flash "An email has been sent to you with a link to login to your account", 'email_auth_email_sent'
10
8
  error_flash "There was an error logging you in"
11
9
  error_flash "There was an error requesting an email link to authenticate", 'email_auth_request'
@@ -23,17 +21,16 @@ module Rodauth
23
21
  redirect(:email_auth_email_recently_sent){default_post_email_redirect}
24
22
 
25
23
  auth_value_method :email_auth_deadline_column, :deadline
26
- auth_value_method :email_auth_deadline_interval, {:days=>1}
27
- auth_value_method :email_auth_email_subject, 'Login Link'
24
+ auth_value_method :email_auth_deadline_interval, {:days=>1}.freeze
25
+ translatable_method :email_auth_email_subject, 'Login Link'
28
26
  auth_value_method :email_auth_id_column, :id
29
27
  auth_value_method :email_auth_key_column, :key
30
28
  auth_value_method :email_auth_key_param, 'key'
31
29
  auth_value_method :email_auth_email_last_sent_column, :email_last_sent
32
30
  auth_value_method :email_auth_skip_resend_email_within, 300
33
31
  auth_value_method :email_auth_table, :account_email_auth_keys
32
+ auth_value_method :force_email_auth?, false
34
33
  session_key :email_auth_session_key, :email_auth_key
35
-
36
- auth_value_methods :force_email_auth?
37
34
 
38
35
  auth_methods(
39
36
  :create_email_auth_email,
@@ -74,7 +71,7 @@ module Rodauth
74
71
 
75
72
  r.get do
76
73
  if key = param_or_nil(email_auth_key_param)
77
- session[email_auth_session_key] = key
74
+ set_session_value(email_auth_session_key, key)
78
75
  redirect(r.path)
79
76
  end
80
77
 
@@ -82,7 +79,7 @@ module Rodauth
82
79
  if account_from_email_auth_key(key)
83
80
  email_auth_view
84
81
  else
85
- session[email_auth_session_key] = nil
82
+ remove_session_value(email_auth_session_key)
86
83
  set_redirect_error_flash no_matching_email_auth_key_error_flash
87
84
  redirect require_login_redirect
88
85
  end
@@ -97,7 +94,7 @@ module Rodauth
97
94
  redirect email_auth_email_sent_redirect
98
95
  end
99
96
 
100
- _login
97
+ login('email_auth')
101
98
  end
102
99
  end
103
100
 
@@ -135,7 +132,7 @@ module Rodauth
135
132
  end
136
133
 
137
134
  def send_email_auth_email
138
- create_email_auth_email.deliver!
135
+ send_email(create_email_auth_email)
139
136
  end
140
137
 
141
138
  def email_auth_email_link
@@ -148,44 +145,44 @@ module Rodauth
148
145
  ds.get(email_auth_key_column)
149
146
  end
150
147
 
151
- def login_form_footer
152
- footer = super
153
- footer += @email_auth_request_form if @email_auth_request_form
154
- footer
155
- end
156
-
157
148
  def email_auth_request_form
158
149
  render('email-auth-request-form')
159
150
  end
160
151
 
161
152
  def after_login_entered_during_multi_phase_login
162
- if force_email_auth?
163
- # If the account does not have a password hash, just send the
164
- # email link.
165
- _email_auth_request
166
- redirect email_auth_email_sent_redirect
167
- else
168
- # If the account has a password hash, allow password login, but
169
- # show form below to also login via email link.
170
- super
171
- @email_auth_request_form = email_auth_request_form
172
- end
153
+ # If forcing email auth, just send the email link.
154
+ _email_auth_request_and_redirect if force_email_auth?
155
+
156
+ super
173
157
  end
174
158
 
175
159
  def use_multi_phase_login?
176
160
  true
177
161
  end
178
162
 
179
- def force_email_auth?
180
- get_password_hash.nil?
163
+ def possible_authentication_methods
164
+ methods = super
165
+ methods << 'email_auth' if !methods.include?('password') && allow_email_auth?
166
+ methods
181
167
  end
182
168
 
183
169
  private
184
170
 
171
+ def _multi_phase_login_forms
172
+ forms = super
173
+ forms << [30, email_auth_request_form, :_email_auth_request_and_redirect] if valid_login_entered? && allow_email_auth?
174
+ forms
175
+ end
176
+
185
177
  def email_auth_email_recently_sent?
186
178
  (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
187
179
  end
188
180
 
181
+ def _email_auth_request_and_redirect
182
+ _email_auth_request
183
+ redirect email_auth_email_sent_redirect
184
+ end
185
+
189
186
  def _email_auth_request
190
187
  if email_auth_email_recently_sent?
191
188
  set_redirect_error_flash email_auth_email_recently_sent_error_flash
@@ -205,6 +202,10 @@ module Rodauth
205
202
 
206
203
  attr_reader :email_auth_key_value
207
204
 
205
+ def allow_email_auth?
206
+ defined?(super) ? super : true
207
+ end
208
+
208
209
  def after_login
209
210
  # Remove the email auth key after any login, even if
210
211
  # it is a password login. This is done to invalidate
@@ -214,7 +215,7 @@ module Rodauth
214
215
  # that allows login access to the account becomes a
215
216
  # security liability, and it is best to remove it.
216
217
  remove_email_auth_key
217
- super if defined?(super)
218
+ super
218
219
  end
219
220
 
220
221
  def after_close_account
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rodauth
4
4
  Feature.define(:email_base, :EmailBase) do
5
- auth_value_method :email_subject_prefix, nil
5
+ translatable_method :email_subject_prefix, ''
6
6
  auth_value_method :require_mail?, true
7
7
  auth_value_method :allow_raw_email_token?, false
8
8
 
@@ -14,7 +14,8 @@ module Rodauth
14
14
 
15
15
  auth_methods(
16
16
  :create_email,
17
- :email_to
17
+ :email_to,
18
+ :send_email
18
19
  )
19
20
 
20
21
  def post_configure
@@ -24,6 +25,10 @@ module Rodauth
24
25
 
25
26
  private
26
27
 
28
+ def send_email(email)
29
+ email.deliver!
30
+ end
31
+
27
32
  def create_email(subject, body)
28
33
  create_email_to(email_to, subject, body)
29
34
  end
@@ -38,7 +43,7 @@ module Rodauth
38
43
  end
39
44
 
40
45
  def email_from
41
- "webmaster@#{request.host}"
46
+ "webmaster@#{domain}"
42
47
  end
43
48
 
44
49
  def email_to
@@ -46,7 +51,7 @@ module Rodauth
46
51
  end
47
52
 
48
53
  def token_link(route, param, key)
49
- "#{request.base_url}#{prefix}/#{route}?#{param}=#{account_id}#{token_separator}#{convert_email_token_key(key)}"
54
+ route_url(route, param => "#{account_id}#{token_separator}#{convert_email_token_key(key)}")
50
55
  end
51
56
 
52
57
  def convert_email_token_key(key)
@@ -3,54 +3,74 @@
3
3
  module Rodauth
4
4
  Feature.define(:http_basic_auth, :HttpBasicAuth) do
5
5
  auth_value_method :http_basic_auth_realm, "protected"
6
- auth_value_method :require_http_basic_auth, false
6
+ auth_value_method :require_http_basic_auth?, false
7
7
 
8
- def session
9
- return @session if defined?(@session)
10
- sess = super
11
- return sess if sess[session_key]
12
- return sess unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
13
- username, password = token.unpack("m*").first.split(/:/, 2)
8
+ def logged_in?
9
+ ret = super
14
10
 
15
- if username && password
16
- catch_error do
17
- unless account_from_login(username)
18
- throw_basic_auth_error(login_param, no_matching_login_message)
19
- end
20
-
21
- before_login_attempt
22
-
23
- unless open_account?
24
- throw_basic_auth_error(login_param, no_matching_login_message)
25
- end
26
-
27
- unless password_match?(password)
28
- after_login_failure
29
- throw_basic_auth_error(password_param, invalid_password_message)
30
- end
31
-
32
- transaction do
33
- before_login
34
- sess[session_key] = account_session_value
35
- after_login
36
- end
37
- end
11
+ if !ret && !defined?(@checked_http_basic_auth)
12
+ http_basic_auth
13
+ ret = super
38
14
  end
39
15
 
40
- sess
16
+ ret
41
17
  end
42
18
 
43
- private
44
-
45
19
  def require_login
46
- if !logged_in? && require_http_basic_auth
20
+ if require_http_basic_auth?
21
+ require_http_basic_auth
22
+ end
23
+
24
+ super
25
+ end
26
+
27
+ def require_http_basic_auth
28
+ unless http_basic_auth
47
29
  set_http_basic_auth_error_response
48
30
  request.halt
49
31
  end
32
+ end
50
33
 
51
- super
34
+ def http_basic_auth
35
+ return @checked_http_basic_auth if defined?(@checked_http_basic_auth)
36
+
37
+ @checked_http_basic_auth = nil
38
+ return unless token = ((v = request.env['HTTP_AUTHORIZATION']) && v[/\A *Basic (.*)\Z/, 1])
39
+
40
+ username, password = token.unpack("m*").first.split(/:/, 2)
41
+ return unless username && password
42
+
43
+ catch_error do
44
+ unless account_from_login(username)
45
+ throw_basic_auth_error(login_param, no_matching_login_message)
46
+ end
47
+
48
+ before_login_attempt
49
+
50
+ unless open_account?
51
+ throw_basic_auth_error(login_param, no_matching_login_message)
52
+ end
53
+
54
+ unless password_match?(password)
55
+ after_login_failure
56
+ throw_basic_auth_error(password_param, invalid_password_message)
57
+ end
58
+
59
+ transaction do
60
+ before_login
61
+ login_session('password')
62
+ after_login
63
+ end
64
+
65
+ @checked_http_basic_auth = true
66
+ return true
67
+ end
68
+
69
+ nil
52
70
  end
53
71
 
72
+ private
73
+
54
74
  def set_http_basic_auth_error_response
55
75
  response.status = 401
56
76
  response.headers["WWW-Authenticate"] = "Basic realm=\"#{http_basic_auth_realm}\""