rodauth 1.18.0 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +24 -0
  3. data/README.rdoc +20 -11
  4. data/doc/base.rdoc +2 -2
  5. data/doc/email_auth.rdoc +53 -0
  6. data/doc/email_base.rdoc +4 -0
  7. data/doc/internals.rdoc +3 -3
  8. data/doc/lockout.rdoc +28 -48
  9. data/doc/login.rdoc +4 -4
  10. data/doc/otp.rdoc +1 -3
  11. data/doc/release_notes/1.19.0.txt +116 -0
  12. data/doc/reset_password.rdoc +29 -49
  13. data/doc/verify_account.rdoc +30 -50
  14. data/doc/verify_login_change.rdoc +4 -0
  15. data/lib/rodauth/features/base.rb +0 -1
  16. data/lib/rodauth/features/change_login.rb +4 -0
  17. data/lib/rodauth/features/disallow_common_passwords.rb +1 -1
  18. data/lib/rodauth/features/email_auth.rb +253 -0
  19. data/lib/rodauth/features/email_base.rb +2 -0
  20. data/lib/rodauth/features/lockout.rb +35 -6
  21. data/lib/rodauth/features/login.rb +46 -9
  22. data/lib/rodauth/features/otp.rb +8 -4
  23. data/lib/rodauth/features/recovery_codes.rb +0 -2
  24. data/lib/rodauth/features/remember.rb +1 -1
  25. data/lib/rodauth/features/reset_password.rb +32 -4
  26. data/lib/rodauth/features/sms_codes.rb +2 -8
  27. data/lib/rodauth/features/two_factor_base.rb +22 -15
  28. data/lib/rodauth/features/verify_account.rb +27 -1
  29. data/lib/rodauth/features/verify_login_change.rb +30 -7
  30. data/lib/rodauth/migrations.rb +2 -8
  31. data/lib/rodauth/version.rb +1 -1
  32. data/spec/email_auth_spec.rb +285 -0
  33. data/spec/lockout_spec.rb +24 -2
  34. data/spec/login_spec.rb +47 -1
  35. data/spec/migrate/001_tables.rb +13 -0
  36. data/spec/migrate_travis/001_tables.rb +10 -0
  37. data/spec/reset_password_spec.rb +20 -2
  38. data/spec/two_factor_spec.rb +46 -0
  39. data/spec/verify_account_grace_period_spec.rb +1 -1
  40. data/spec/verify_account_spec.rb +33 -3
  41. data/spec/verify_login_change_spec.rb +54 -1
  42. data/templates/email-auth-email.str +5 -0
  43. data/templates/email-auth-request-form.str +7 -0
  44. data/templates/email-auth.str +5 -0
  45. data/templates/login-display.str +4 -0
  46. data/templates/login.str +2 -2
  47. data/templates/otp-setup.str +13 -11
  48. metadata +12 -2
@@ -8,73 +8,53 @@ the login feature.
8
8
 
9
9
  == Auth Value Methods
10
10
 
11
- no_matching_reset_password_key_message :: The flash error message to show if attempting
12
- to access the reset password form with an
13
- invalid key.
14
- reset_password_additional_form_tags :: HTML fragment containing additional form
15
- tags to use on the reset password form.
16
- reset_password_autologin? :: Whether to autologin the user after successfully
17
- resetting a password.
11
+ no_matching_reset_password_key_message :: The flash error message to show if attempting to access the reset password form with an invalid key.
12
+ reset_password_additional_form_tags :: HTML fragment containing additional form tags to use on the reset password form.
13
+ reset_password_autologin? :: Whether to autologin the user after successfully resetting a password.
18
14
  reset_password_button :: The text to use for the reset password button.
19
- reset_password_deadline_column :: The column name in the reset password keys table storing
20
- the deadline after which the token will be ignored.
21
- reset_password_deadline_interval :: The amount of time for which to allow users to
22
- reset their passwords, 1 day by default. Only used if
23
- set_deadline_values? is true.
24
- reset_password_email_sent_notice_flash :: The flash notice to show after a reset
25
- password email has been sent.
26
- reset_password_email_sent_redirect :: Where to redirect after sending a reset
27
- password email.
15
+ reset_password_deadline_column :: The column name in the reset password keys table storing the deadline after which the token will be ignored.
16
+ reset_password_deadline_interval :: The amount of time for which to allow users to reset their passwords, 1 day by default. Only used if set_deadline_values? is true.
17
+ reset_password_email_recently_sent_error_flash :: The flash error to show if not sending reset password email because one has been sent recently.
18
+ reset_password_email_recently_sent_redirect :: Where to redirect if not sending reset password email because one has been sent recently.
19
+ reset_password_email_sent_notice_flash :: The flash notice to show after a reset password email has been sent.
20
+ reset_password_email_sent_redirect :: Where to redirect after sending a reset password email.
28
21
  reset_password_email_subject :: The subject to use for reset password emails.
29
22
  reset_password_error_flash :: The flash error to show after resetting a password.
30
- reset_password_id_column :: The id column in the reset password keys table, should
31
- be a foreign key referencing the accounts table.
32
- reset_password_key_column :: The reset password key/token column in the reset
33
- password keys table.
23
+ reset_password_email_last_sent_column :: The email last sent column in the reset password keys table. nil by default, so a reset password email is always sent when requested by default.
24
+ reset_password_id_column :: The id column in the reset password keys table, should be a foreign key referencing the accounts table.
25
+ reset_password_key_column :: The reset password key/token column in the reset password keys table.
34
26
  reset_password_key_param :: The parameter name to use for the reset password key.
35
27
  reset_password_redirect :: Where to redirect after resetting a password.
36
- reset_password_request_additional_form_tags :: HTML fragment containing additional form
37
- tags to use on the reset password request
38
- form.
28
+ reset_password_request_additional_form_tags :: HTML fragment containing additional form tags to use on the reset password request form.
39
29
  reset_password_request_button :: The text to use for the reset password request button.
40
- reset_password_request_error_flash :: The flash error to show if not able to send a reset
41
- password email.
42
- reset_password_request_link :: The HTML to use for a link to the page to request a password
43
- reset.
44
- reset_password_request_route :: The route to the reset password request action.
45
- Defaults to +reset-password-request+.
46
- reset_password_route :: The route to the reset password action. Defaults to
47
- +reset-password+.
30
+ reset_password_request_error_flash :: The flash error to show if not able to send a reset password email.
31
+ reset_password_request_link :: The HTML to use for a link to the page to request a password reset.
32
+ reset_password_request_route :: The route to the reset password request action. Defaults to +reset-password-request+.
33
+ reset_password_route :: The route to the reset password action. Defaults to +reset-password+.
48
34
  reset_password_session_key :: The key in the session to hold the reset password key temporarily.
35
+ reset_password_skip_resend_email_within :: The number of seconds before sending another reset password email, if +reset_password_email_last_sent_column+ is set.
49
36
  reset_password_table :: The name of the reset password keys table.
50
37
 
51
38
  == Auth Methods
52
39
 
53
- account_from_reset_password_key(key) :: Retrieve the account using the given reset
54
- password key, or return nil if no account
55
- matches.
40
+ account_from_reset_password_key(key) :: Retrieve the account using the given reset password key, or return nil if no account matches.
56
41
  after_reset_password :: Run arbitrary code after successfully resetting a password.
57
- after_reset_password_request :: Run arbitrary code after sending the reset password
58
- email.
42
+ after_reset_password_request :: Run arbitrary code after sending the reset password email.
59
43
  before_reset_password :: Run arbitrary code before resetting a password.
60
- before_reset_password_request :: Run arbitrary code before sending the reset password
61
- email.
44
+ before_reset_password_request :: Run arbitrary code before sending the reset password email.
45
+ before_reset_password_request_route :: Run arbitrary code before handling a reset password request route.
62
46
  before_reset_password_route :: Run arbitrary code before handling a reset password route.
63
47
  create_reset_password_key :: Add the reset password key data to the database.
64
48
  create_reset_password_email :: A Mail::Message for the reset password email.
65
- get_reset_password_key(id) :: Get the password reset key for the given account id
66
- from the database.
67
- login_failed_reset_password_request_form :: The HTML to use for a form to request a password
68
- reset, shown on the login page after the user
69
- tries to login with an invalid password.
70
- remove_reset_password_key :: Remove the reset password key for the current account,
71
- run after successful password reset.
49
+ get_reset_password_email_last_sent :: Get the last time a reset password email is sent, or nil if there is no last sent time.
50
+ get_reset_password_key(id) :: Get the password reset key for the given account id from the database.
51
+ login_failed_reset_password_request_form :: The HTML to use for a form to request a password reset, shown on the login page after the user tries to login with an invalid password.
52
+ remove_reset_password_key :: Remove the reset password key for the current account, run after successful password reset.
72
53
  reset_password_email_body :: The body to use for the reset password email.
73
- reset_password_email_link :: The link to the reset password form in the reset
74
- password email.
75
- reset_password_key_insert_hash :: The hash to insert into the reset password keys
76
- table.
54
+ reset_password_email_link :: The link to the reset password form in the reset password email.
55
+ reset_password_key_insert_hash :: The hash to insert into the reset password keys table.
77
56
  reset_password_key_value :: The reset password key for the current account.
78
57
  reset_password_request_view :: The HTML to use for the reset password request form.
79
58
  reset_password_view :: The HTML to use for the reset password form.
80
59
  send_reset_password_email :: Send the reset password email.
60
+ set_reset_password_email_last_sent :: Set the last time a reset password email is sent.
@@ -7,76 +7,56 @@ after verifying the account. Depends on the login and create account features.
7
7
 
8
8
  == Auth Value Methods
9
9
 
10
- attempt_to_create_unverified_account_notice_message :: Message displayed when attempting to
11
- create an account awaiting verification.
12
- attempt_to_login_to_unverified_account_notice_message :: Message displayed when attempting to
13
- login to an account awaiting verification.
14
- no_matching_verify_account_key_message :: The flash error message to show when
15
- an invalid verify account key is used.
16
- verify_account_additional_form_tags :: HTML fragment containing additional form
17
- tags to use on the verify account form.
18
- verify_account_autologin? :: Whether to autologin the user after successful
19
- account verification, true by default.
10
+ attempt_to_create_unverified_account_notice_message :: Message displayed when attempting to create an account awaiting verification.
11
+ attempt_to_login_to_unverified_account_notice_message :: Message displayed when attempting to login to an account awaiting verification.
12
+ no_matching_verify_account_key_message :: The flash error message to show when an invalid verify account key is used.
13
+ verify_account_additional_form_tags :: HTML fragment containing additional form tags to use on the verify account form.
14
+ verify_account_autologin? :: Whether to autologin the user after successful account verification, true by default.
20
15
  verify_account_button :: The text to use for the verify account button.
16
+ verify_account_email_recently_sent_error_flash :: The flash error to show if not sending verify account email because one has been sent recently.
17
+ verify_account_email_recently_sent_redirect :: Where to redirect if not sending verify account email because one has been sent recently.
21
18
  verify_account_email_subject :: The subject to use for the verify account email.
22
- verify_account_email_sent_redirect :: Where to redirect after sending the verify
23
- account email.
24
- verify_account_email_sent_notice_flash :: The flash notice to set after sending
25
- the verify account email.
26
- verify_account_error_flash :: The flash error to show if no matching key is submitted
27
- when verifying an account.
28
- verify_account_id_column :: The id column in the verify account keys table, should
29
- be a foreign key referencing the accounts table.
30
- verify_account_key_column :: The verify account key/token column in the verify
31
- account keys table.
19
+ verify_account_email_sent_redirect :: Where to redirect after sending the verify account email.
20
+ verify_account_email_sent_notice_flash :: The flash notice to set after sending the verify account email.
21
+ verify_account_email_last_sent_column :: The email last sent column in the verify account keys table. nil by default, so a verify account email is always sent when requested by default.
22
+ verify_account_error_flash :: The flash error to show if no matching key is submitted when verifying an account.
23
+ verify_account_id_column :: The id column in the verify account keys table, should be a foreign key referencing the accounts table.
24
+ verify_account_key_column :: The verify account key/token column in the verify account keys table.
32
25
  verify_account_key_param :: The parameter name to use for the verify account key.
33
26
  verify_account_notice_flash :: The flash notice to show after verifying the account.
34
- verify_account_resend_additional_form_tags :: HTML fragment containing additional form
35
- tags to use on the page requesting
36
- resending the verify account email.
27
+ verify_account_resend_additional_form_tags :: HTML fragment containing additional form tags to use on the page requesting resending the verify account email.
37
28
  verify_account_resend_button :: The text to use for the verify account resend button.
38
29
  verify_account_redirect :: Where to redirect after verifying the account.
39
- verify_account_resend_error_flash :: The flash error to show if unable to resend a
40
- verify account email.
41
- verify_account_resend_link :: The HTML to use for a link to the page to request
42
- the account verification email be resent.
43
- verify_account_resend_route :: The route to the verify account resend action.
44
- Defaults to +verify-account-resend+.
45
- verify_account_route :: The route to the verify account action. Defaults to
46
- +verify-account+.
30
+ verify_account_resend_error_flash :: The flash error to show if unable to resend a verify account email.
31
+ verify_account_resend_link :: The HTML to use for a link to the page to request the account verification email be resent.
32
+ verify_account_resend_route :: The route to the verify account resend action. Defaults to +verify-account-resend+.
33
+ verify_account_route :: The route to the verify account action. Defaults to +verify-account+.
47
34
  verify_account_session_key :: The key in the session to hold the verify account key temporarily.
48
- verify_account_set_password? :: Whether to ask for a password to be set on the verify account
49
- form. Defaults to false. If set to true, will automatically
50
- stop asking for passwords to be set on the create account form.
35
+ verify_account_set_password? :: Whether to ask for a password to be set on the verify account form. Defaults to false. If set to true, will automatically stop asking for passwords to be set on the create account form.
36
+ verify_account_skip_resend_email_within :: The number of seconds before sending another verify account email, if +verify_account_email_last_sent_column+ is set.
51
37
  verify_account_table :: The name of the verify account keys table.
52
38
 
53
39
  == Auth Methods
54
40
 
55
- account_from_verify_account_key(key) :: Retrieve the account using the given verify
56
- account key, or return nil if no account
57
- matches.
41
+ account_from_verify_account_key(key) :: Retrieve the account using the given verify account key, or return nil if no account matches.
58
42
  after_verify_account :: Run arbitrary code after verifying the account.
59
43
  after_verify_account_resend :: Run arbitrary code after resending a verify account email.
60
- allow_resending_verify_account_email? :: Whether to allow sending the verify account email
61
- for the account, true by default only if the
62
- account has not been verified.
44
+ allow_resending_verify_account_email? :: Whether to allow sending the verify account email for the account, true by default only if the account has not been verified.
63
45
  before_verify_account :: Run arbitrary code before verifying the account.
64
46
  before_verify_account_resend :: Run arbitrary code before resending a verify account email.
47
+ before_verify_account_resend_route :: Run arbitrary code before handling a verify account resend route.
65
48
  before_verify_account_route :: Run arbitrary code before handling a verify account route.
66
49
  create_verify_account_key :: Add the verify account key data to the database.
67
50
  create_verify_account_email :: A Mail::Message for the verify account email.
68
- get_verify_account_key(id) :: Get the verify account key for the given account id
69
- from the database.
70
- remove_verify_account_key :: Remove the verify account key for the current account,
71
- run after successful account verification.
72
- resend_verify_account_view :: The HTML to use for page requesting resending the
73
- verify account email.
51
+ get_verify_account_email_last_sent :: Get the last time a verify account email is sent, or nil if there is no last sent time.
52
+ get_verify_account_key(id) :: Get the verify account key for the given account id from the database.
53
+ remove_verify_account_key :: Remove the verify account key for the current account, run after successful account verification.
54
+ resend_verify_account_view :: The HTML to use for page requesting resending the verify account email.
74
55
  send_verify_account_email :: Send the verify account email.
56
+ set_verify_account_email_last_sent :: Set the last time a verify account email is sent.
75
57
  verify_account :: Verify the account by changing the status from unverified to open.
76
58
  verify_account_email_body :: The body to use for the verify account email.
77
- verify_account_email_link :: The link to the verify account form in the verify
78
- account email.
79
- verify_account_key_insert_hash :: The hash to insert into the verify account keys
80
- table.
59
+ verify_account_email_link :: The link to the verify account form in the verify account email.
60
+ verify_account_key_insert_hash :: The hash to insert into the verify account keys table.
81
61
  verify_account_key_value :: The value of the verify account key.
82
62
  verify_account_view :: The HTML to use for the verify account form.
@@ -19,6 +19,8 @@ verify_login_change_autologin? :: Whether to autologin the user after successful
19
19
  verify_login_change_button :: The text to use for the verify login change button.
20
20
  verify_login_change_deadline_column :: The column name in the verify login change keys table storing the deadline after which the token will be ignored.
21
21
  verify_login_change_deadline_interval :: The amount of time for which to allow users to verify login changes, 1 day by default.
22
+ verify_login_change_duplicate_account_error_flash :: The flash error message to show when attempting to verify a login change when the login is already taken.
23
+ verify_login_change_duplicate_account_redirect :: Where to redirect if not changing a login during verification because the new login is already taken.
22
24
  verify_login_change_email_subject :: The subject to use for the verify login change email.
23
25
  verify_login_change_error_flash :: The flash error to show if no matching key is submitted when verifying login change.
24
26
  verify_login_change_id_column :: The id column in the verify login change keys table, should be a foreign key referencing the accounts table.
@@ -35,7 +37,9 @@ verify_login_change_table :: The name of the verify login change keys table.
35
37
 
36
38
  account_from_verify_login_change_key(key) :: Retrieve the account using the given verify account key, or return nil if no account matches. Should also override verify_login_change_new_login if overriding this method.
37
39
  after_verify_login_change :: Run arbitrary code after verifying the login change.
40
+ after_verify_login_change_email :: Run arbitrary code after sending verify login change email.
38
41
  before_verify_login_change :: Run arbitrary code before verifying the login change.
42
+ before_verify_login_change_email :: Run arbitrary code before sending verify login change email.
39
43
  before_verify_login_change_route :: Run arbitrary code before handling a verify login change route.
40
44
  create_verify_login_change_email(login) :: A Mail::Message for the verify login change email.
41
45
  create_verify_login_change_key(login) :: Add the verify login change key data to the database.
@@ -52,7 +52,6 @@ module Rodauth
52
52
 
53
53
  auth_value_methods(
54
54
  :db,
55
- :require_login_redirect,
56
55
  :set_deadline_values?,
57
56
  :use_date_arithmetic?,
58
57
  :use_database_authentication_functions?,
@@ -74,6 +74,10 @@ module Rodauth
74
74
  private
75
75
 
76
76
  def update_login(login)
77
+ _update_login(login)
78
+ end
79
+
80
+ def _update_login(login)
77
81
  updated = nil
78
82
  raised = raises_uniqueness_violation?{updated = update_account({login_column=>login}, account_ds.exclude(login_column=>login)) == 1}
79
83
  if raised
@@ -4,7 +4,7 @@ module Rodauth
4
4
  Feature.define(:disallow_common_passwords, :DisallowCommonPasswords) do
5
5
  depends :login_password_requirements_base
6
6
 
7
- auth_value_method :most_common_passwords_file, File.join(File.dirname(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__))))), 'dict', 'top-10_000-passwords.txt')
7
+ auth_value_method :most_common_passwords_file, File.expand_path('../../../../dict/top-10_000-passwords.txt', __FILE__)
8
8
  auth_value_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
 
@@ -0,0 +1,253 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:email_auth, :EmailAuth) do
5
+ depends :login, :email_base
6
+
7
+ notice_flash "An email has been sent to you with a link to login to your account", 'email_auth_email_sent'
8
+ error_flash "There was an error logging you in"
9
+ error_flash "There was an error requesting an email link to authenticate", 'email_auth_request'
10
+ error_flash "An email has recently been sent to you with a link to login", 'email_auth_email_recently_sent'
11
+ loaded_templates %w'email-auth email-auth-request-form email-auth-email'
12
+
13
+ view 'email-auth', 'Login'
14
+ additional_form_tags
15
+ additional_form_tags 'email_auth_request'
16
+ before 'email_auth_request'
17
+ after 'email_auth_request'
18
+ button 'Send Login Link Via Email', 'email_auth_request'
19
+ redirect(:email_auth_email_sent){default_post_email_redirect}
20
+ redirect(:email_auth_email_recently_sent){default_post_email_redirect}
21
+
22
+ auth_value_method :email_auth_deadline_column, :deadline
23
+ auth_value_method :email_auth_deadline_interval, {:days=>1}
24
+ auth_value_method :email_auth_email_subject, 'Login Link'
25
+ auth_value_method :email_auth_id_column, :id
26
+ auth_value_method :email_auth_key_column, :key
27
+ auth_value_method :email_auth_key_param, 'key'
28
+ auth_value_method :email_auth_email_last_sent_column, :email_last_sent
29
+ auth_value_method :email_auth_skip_resend_email_within, 300
30
+ auth_value_method :email_auth_table, :account_email_auth_keys
31
+ auth_value_method :no_matching_email_auth_key_message, "invalid email authentication key"
32
+ session_key :email_auth_session_key, :email_auth_key
33
+
34
+ auth_value_methods :force_email_auth?
35
+
36
+ auth_methods(
37
+ :create_email_auth_email,
38
+ :create_email_auth_key,
39
+ :email_auth_email_body,
40
+ :email_auth_email_link,
41
+ :email_auth_key_insert_hash,
42
+ :email_auth_key_value,
43
+ :email_auth_request_form,
44
+ :get_email_auth_key,
45
+ :get_email_auth_email_last_sent,
46
+ :remove_email_auth_key,
47
+ :send_email_auth_email,
48
+ :set_email_auth_email_last_sent
49
+ )
50
+
51
+ auth_private_methods :account_from_email_auth_key
52
+
53
+ route(:email_auth_request) do |r|
54
+ check_already_logged_in
55
+ before_email_auth_request_route
56
+
57
+ r.post do
58
+ if account_from_login(param(login_param)) && open_account?
59
+ _email_auth_request
60
+ else
61
+ set_redirect_error_status(no_matching_login_error_status)
62
+ set_redirect_error_flash email_auth_request_error_flash
63
+ end
64
+
65
+ redirect email_auth_email_sent_redirect
66
+ end
67
+ end
68
+
69
+ route do |r|
70
+ check_already_logged_in
71
+ before_email_auth_route
72
+
73
+ r.get do
74
+ if key = param_or_nil(email_auth_key_param)
75
+ session[email_auth_session_key] = key
76
+ redirect(r.path)
77
+ end
78
+
79
+ if key = session[email_auth_session_key]
80
+ if account_from_email_auth_key(key)
81
+ email_auth_view
82
+ else
83
+ session[email_auth_session_key] = nil
84
+ set_redirect_error_flash no_matching_email_auth_key_message
85
+ redirect require_login_redirect
86
+ end
87
+ end
88
+ end
89
+
90
+ r.post do
91
+ key = session[email_auth_session_key] || param(email_auth_key_param)
92
+ unless account_from_email_auth_key(key)
93
+ set_redirect_error_status(invalid_key_error_status)
94
+ set_redirect_error_flash email_auth_error_flash
95
+ redirect email_auth_email_sent_redirect
96
+ end
97
+
98
+ _login
99
+ end
100
+ end
101
+
102
+ def create_email_auth_key
103
+ transaction do
104
+ if email_auth_key_value = get_email_auth_key(account_id)
105
+ set_email_auth_email_last_sent
106
+ @email_auth_key_value = email_auth_key_value
107
+ elsif e = raised_uniqueness_violation{email_auth_ds.insert(email_auth_key_insert_hash)}
108
+ # If inserting into the email auth table causes a violation, we can pull the
109
+ # existing email auth key from the table, or reraise.
110
+ raise e unless @email_auth_key_value = get_email_auth_key(account_id)
111
+ end
112
+ end
113
+ end
114
+
115
+ def set_email_auth_email_last_sent
116
+ email_auth_ds.update(email_auth_email_last_sent_column=>Sequel::CURRENT_TIMESTAMP) if email_auth_email_last_sent_column
117
+ end
118
+
119
+ def get_email_auth_email_last_sent
120
+ if column = email_auth_email_last_sent_column
121
+ if ts = email_auth_ds.get(column)
122
+ convert_timestamp(ts)
123
+ end
124
+ end
125
+ end
126
+
127
+ def remove_email_auth_key
128
+ email_auth_ds.delete
129
+ end
130
+
131
+ def account_from_email_auth_key(key)
132
+ @account = _account_from_email_auth_key(key)
133
+ end
134
+
135
+ def send_email_auth_email
136
+ create_email_auth_email.deliver!
137
+ end
138
+
139
+ def email_auth_email_link
140
+ token_link(email_auth_route, email_auth_key_param, email_auth_key_value)
141
+ end
142
+
143
+ def get_email_auth_key(id)
144
+ ds = email_auth_ds(id)
145
+ ds.where(Sequel::CURRENT_TIMESTAMP > email_auth_deadline_column).delete
146
+ ds.get(email_auth_key_column)
147
+ end
148
+
149
+ def login_form_footer
150
+ footer = super
151
+ footer += @email_auth_request_form if @email_auth_request_form
152
+ footer
153
+ end
154
+
155
+ def email_auth_request_form
156
+ render('email-auth-request-form')
157
+ end
158
+
159
+ def after_login_entered_during_multi_phase_login
160
+ if force_email_auth?
161
+ # If the account does not have a password hash, just send the
162
+ # email link.
163
+ _email_auth_request
164
+ redirect email_auth_email_sent_redirect
165
+ else
166
+ # If the account has a password hash, allow password login, but
167
+ # show form below to also login via email link.
168
+ super
169
+ @email_auth_request_form = email_auth_request_form
170
+ end
171
+ end
172
+
173
+ def use_multi_phase_login?
174
+ true
175
+ end
176
+
177
+ def force_email_auth?
178
+ get_password_hash.nil?
179
+ end
180
+
181
+ private
182
+
183
+ def email_auth_email_recently_sent?
184
+ (email_last_sent = get_email_auth_email_last_sent) && (Time.now - email_last_sent < email_auth_skip_resend_email_within)
185
+ end
186
+
187
+ def _email_auth_request
188
+ if email_auth_email_recently_sent?
189
+ set_redirect_error_flash email_auth_email_recently_sent_error_flash
190
+ redirect email_auth_email_recently_sent_redirect
191
+ end
192
+
193
+ generate_email_auth_key_value
194
+ transaction do
195
+ before_email_auth_request
196
+ create_email_auth_key
197
+ send_email_auth_email
198
+ after_email_auth_request
199
+ end
200
+
201
+ set_notice_flash email_auth_email_sent_notice_flash
202
+ end
203
+
204
+ attr_reader :email_auth_key_value
205
+
206
+ def after_login
207
+ # Remove the email auth key after any login, even if
208
+ # it is a password login. This is done to invalidate
209
+ # the email login when a user has a password and requests
210
+ # email authentication, but then remembers their password
211
+ # and doesn't need the link. At that point, the link
212
+ # that allows login access to the account becomes a
213
+ # security liability, and it is best to remove it.
214
+ remove_email_auth_key
215
+ super if defined?(super)
216
+ end
217
+
218
+ def after_close_account
219
+ remove_email_auth_key
220
+ super if defined?(super)
221
+ end
222
+
223
+ def generate_email_auth_key_value
224
+ @email_auth_key_value = random_key
225
+ end
226
+
227
+ def create_email_auth_email
228
+ create_email(email_auth_email_subject, email_auth_email_body)
229
+ end
230
+
231
+ def email_auth_email_body
232
+ render('email-auth-email')
233
+ end
234
+
235
+ def use_date_arithmetic?
236
+ super || db.database_type == :mysql
237
+ end
238
+
239
+ def email_auth_key_insert_hash
240
+ hash = {email_auth_id_column=>account_id, email_auth_key_column=>email_auth_key_value}
241
+ set_deadline_value(hash, email_auth_deadline_column, email_auth_deadline_interval)
242
+ hash
243
+ end
244
+
245
+ def email_auth_ds(id=account_id)
246
+ db[email_auth_table].where(email_auth_id_column=>id)
247
+ end
248
+
249
+ def _account_from_email_auth_key(token)
250
+ account_from_key(token, account_open_status_value){|id| get_email_auth_key(id)}
251
+ end
252
+ end
253
+ end