rodauth 2.1.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +56 -0
  3. data/README.rdoc +14 -0
  4. data/doc/base.rdoc +3 -1
  5. data/doc/guides/admin_activation.rdoc +46 -0
  6. data/doc/guides/already_authenticated.rdoc +10 -0
  7. data/doc/guides/alternative_login.rdoc +46 -0
  8. data/doc/guides/create_account_programmatically.rdoc +38 -0
  9. data/doc/guides/delay_password.rdoc +25 -0
  10. data/doc/guides/email_only.rdoc +16 -0
  11. data/doc/guides/i18n.rdoc +26 -0
  12. data/doc/{internals.rdoc → guides/internals.rdoc} +0 -0
  13. data/doc/guides/links.rdoc +12 -0
  14. data/doc/guides/login_return.rdoc +37 -0
  15. data/doc/guides/password_column.rdoc +25 -0
  16. data/doc/guides/password_confirmation.rdoc +37 -0
  17. data/doc/guides/password_requirements.rdoc +30 -0
  18. data/doc/guides/paths.rdoc +36 -0
  19. data/doc/guides/query_params.rdoc +9 -0
  20. data/doc/guides/redirects.rdoc +17 -0
  21. data/doc/guides/registration_field.rdoc +68 -0
  22. data/doc/guides/require_mfa.rdoc +30 -0
  23. data/doc/guides/reset_password_autologin.rdoc +21 -0
  24. data/doc/guides/status_column.rdoc +28 -0
  25. data/doc/guides/totp_or_recovery.rdoc +16 -0
  26. data/doc/jwt_refresh.rdoc +17 -0
  27. data/doc/login.rdoc +8 -0
  28. data/doc/login_password_requirements_base.rdoc +3 -0
  29. data/doc/otp.rdoc +1 -0
  30. data/doc/password_pepper.rdoc +44 -0
  31. data/doc/release_notes/2.2.0.txt +39 -0
  32. data/doc/release_notes/2.3.0.txt +37 -0
  33. data/doc/release_notes/2.4.0.txt +22 -0
  34. data/doc/release_notes/2.5.0.txt +20 -0
  35. data/doc/release_notes/2.6.0.txt +37 -0
  36. data/doc/verify_login_change.rdoc +1 -0
  37. data/javascript/webauthn_auth.js +9 -9
  38. data/javascript/webauthn_setup.js +9 -6
  39. data/lib/rodauth.rb +13 -9
  40. data/lib/rodauth/features/active_sessions.rb +5 -7
  41. data/lib/rodauth/features/audit_logging.rb +2 -0
  42. data/lib/rodauth/features/base.rb +18 -3
  43. data/lib/rodauth/features/change_password.rb +1 -1
  44. data/lib/rodauth/features/close_account.rb +8 -6
  45. data/lib/rodauth/features/confirm_password.rb +2 -2
  46. data/lib/rodauth/features/disallow_password_reuse.rb +4 -2
  47. data/lib/rodauth/features/email_auth.rb +2 -2
  48. data/lib/rodauth/features/jwt.rb +10 -7
  49. data/lib/rodauth/features/jwt_cors.rb +15 -15
  50. data/lib/rodauth/features/jwt_refresh.rb +76 -10
  51. data/lib/rodauth/features/login.rb +23 -12
  52. data/lib/rodauth/features/login_password_requirements_base.rb +9 -4
  53. data/lib/rodauth/features/otp.rb +5 -1
  54. data/lib/rodauth/features/password_complexity.rb +4 -2
  55. data/lib/rodauth/features/password_pepper.rb +45 -0
  56. data/lib/rodauth/features/remember.rb +2 -0
  57. data/lib/rodauth/features/session_expiration.rb +1 -6
  58. data/lib/rodauth/features/single_session.rb +1 -1
  59. data/lib/rodauth/features/sms_codes.rb +0 -1
  60. data/lib/rodauth/features/two_factor_base.rb +4 -4
  61. data/lib/rodauth/features/verify_account.rb +10 -6
  62. data/lib/rodauth/features/verify_account_grace_period.rb +2 -4
  63. data/lib/rodauth/features/verify_login_change.rb +2 -1
  64. data/lib/rodauth/features/webauthn.rb +1 -3
  65. data/lib/rodauth/features/webauthn_login.rb +1 -1
  66. data/lib/rodauth/migrations.rb +16 -5
  67. data/lib/rodauth/version.rb +1 -1
  68. metadata +37 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11417c5f63db5803f35ba8a92e98827b9f0deeb0a774071b5fbe712fa24f02fd
4
- data.tar.gz: b0ee20c0ce7af6eea42b32bcab2e476a2ee75b62b283281de7001d5ba4002597
3
+ metadata.gz: 82585797ff882cb56340e2145b05b74da1e10c428413b02e1656604fa8209c93
4
+ data.tar.gz: 7148d04c255f4310a21c6373d9ab20888e8fe46d3a07c090576385890ea82858
5
5
  SHA512:
6
- metadata.gz: 13110a40f40a08c7fb03f1ce44a723afc6e53b4f4b16f690c852dc0adfc4d1bb08b6b87f164be98d50dfc61a6c99a35759901ed3bc7b5ab3dbbcb48b00b6ec5e
7
- data.tar.gz: 2d7f46d8a9370b67af58eb60d5ac8f324f2619d3cd9bac10b6a27890c9637ceff229ce0404a6d00c9c7fdf8d796d045d3bd06e7489fe08f589580d6a20271cf4
6
+ metadata.gz: be8a3bffa982ca9730dafdefb8a8a874c2a2cb8fa62c84d4b0467928fc2164251ba28bb88948274316d7870473f0a602b8ac69eef8b48b70b27584ed7a443ded
7
+ data.tar.gz: afaf45ddda1073eaba697035700ec9108bebd1fc18998af9245fd86f532f497e6723fa532624aae426dac7e1600556af7b7369b2e7203e387be26dcbdfc22e3d
data/CHANGELOG CHANGED
@@ -1,3 +1,59 @@
1
+ === 2.6.0 (2020-11-20)
2
+
3
+ * Avoid loading features multiple times (janko) (#131)
4
+
5
+ * Add around_rodauth method for running code around the handling of all Rodauth routes (bjeanes) (#129)
6
+
7
+ * Fix javascript for registration of multiple webauthn keys (bjeanes) (#127)
8
+
9
+ * Add allow_refresh_with_expired_jwt_access_token? configuration method to jwt_refresh feature, for allowing refresh with expired access token (jeremyevans)
10
+
11
+ * Promote setup_account_verification to public API, useful for automatically sending account verification emails (jeremyevans)
12
+
13
+ === 2.5.0 (2020-10-22)
14
+
15
+ * Add change_login_needs_verification_notice_flash for easier translation of change_login_notice_flash when using verify_login_change (bjeanes, janko, jeremyevans) (#126)
16
+
17
+ * Add login_return_to_requested_location_path for controlling path to use as the requested location (HoneyryderChuck, jeremyevans) (#122, #123)
18
+
19
+ === 2.4.0 (2020-09-21)
20
+
21
+ * Add session_key_prefix for more easily using separate session keys when using multiple configurations (janko) (#121)
22
+
23
+ * Add password_pepper feature for appending a secret key to passwords before they are hashed, supporting secret rotation (janko) (#119)
24
+
25
+ === 2.3.0 (2020-08-21)
26
+
27
+ * Return an error status instead of an invalid access token when trying to refresh JWT without an access token in the jwt_refresh feature (jeremyevans)
28
+
29
+ * Allow {create,drop}_database_authentication_functions to work with UUID keys (monorkin, janko) (#117)
30
+
31
+ * Add rodauth.login('login_type') for logging in after setting a valid account (janko) (#114)
32
+
33
+ * Make new refresh token available to the after_refresh_token hook by setting it in the response first (jeremyevans)
34
+
35
+ * Make the jwt_refresh plugin call before_jwt_refresh_route hook (previously the configuration method was ignored) (AlexeyMatskevich) (#110)
36
+
37
+ * Add login_email_regexp, login_not_valid_email_message, and log_valid_email? configuration methods (janko) (#107)
38
+
39
+ === 2.2.0 (2020-07-20)
40
+
41
+ * Allow removing all jwt_refresh tokens when logging out by providing a value of "all" as the token to remove (jeremyevans)
42
+
43
+ * Allow removing specific jwt_refresh token when logging out by providing the token to remove (jeremyevans)
44
+
45
+ * Avoid NoMethodError when checking if session is authenticated when using two factor auth, verify_account_grace_period, and email_auth (jeremyevans) (#105)
46
+
47
+ * Reduce queries in #authenticated? and #require_authentication when using two factor authentication (janko) (#106)
48
+
49
+ * Treat verify_account_email_resend returning false as an error in the verify_account feature (jeremyevans)
50
+
51
+ * Fix use of password_dictionary configuration method in password_complexity feature (jeremyevans)
52
+
53
+ * Remove unnecessary conditionals (jeremyevans)
54
+
55
+ * Add otp_last_use to the otp feature, returning the time of last successful OTP use (jeremyevans) (#103)
56
+
1
57
  === 2.1.0 (2020-06-09)
2
58
 
3
59
  * Do not check CSRF tokens by default for requests using JWT (janko, jeremyevans) (#99)
@@ -44,6 +44,7 @@ HTML and JSON API for all supported features.
44
44
  * Verify Account Grace Period (Don't require verification before login)
45
45
  * Password Grace Period (Don't require password entry if recently entered)
46
46
  * Password Complexity (More sophisticated checks)
47
+ * Password Pepper
47
48
  * Disallow Password Reuse
48
49
  * Disallow Common Passwords
49
50
  * Password Expiration
@@ -881,6 +882,7 @@ view the appropriate file in the doc directory.
881
882
  * {Password Complexity}[rdoc-ref:doc/password_complexity.rdoc]
882
883
  * {Password Expiration}[rdoc-ref:doc/password_expiration.rdoc]
883
884
  * {Password Grace Period}[rdoc-ref:doc/password_grace_period.rdoc]
885
+ * {Password Pepper}[rdoc-ref:doc/password_pepper.rdoc]
884
886
  * {Recovery Codes}[rdoc-ref:doc/recovery_codes.rdoc]
885
887
  * {Remember}[rdoc-ref:doc/remember.rdoc]
886
888
  * {Reset Password}[rdoc-ref:doc/reset_password.rdoc]
@@ -1062,6 +1064,18 @@ the name as an argument to use that configuration:
1062
1064
  r.rodauth
1063
1065
  end
1064
1066
 
1067
+ By default, alternate configurations will use the same session keys as the
1068
+ primary configuration, which may be undesirable. To ensure session state is
1069
+ separated between configurations, you can set a session key prefix for
1070
+ alternate configurations. If you are using the remember feature in both
1071
+ configurations, you may also want to set a different remember key in the
1072
+ alternate configuration:
1073
+
1074
+ plugin :rodauth, :name=>:secondary do
1075
+ session_key_prefix "secondary_"
1076
+ remember_cookie_key "_secondary_remember"
1077
+ end
1078
+
1065
1079
  === With Password Hashes Inside the Accounts Table
1066
1080
 
1067
1081
  You can use Rodauth if you are storing password hashes in the same
@@ -1,7 +1,7 @@
1
1
  = Documentation for Base Feature
2
2
 
3
3
  The base feature is automatically loaded when you use Rodauth. It contains
4
- shared functionality that is used by multiple features.
4
+ shared functionality that is used by multiple features.
5
5
 
6
6
  == Auth Value Methods
7
7
 
@@ -17,6 +17,7 @@ mark_input_fields_as_required? :: Whether input fields should be marked as requi
17
17
  prefix :: The routing prefix used for Rodauth routes. If you are calling in a routing subtree, this should be set to the root path of the subtree. This should include a leading slash if set, but not a trailing slash.
18
18
  require_bcrypt? :: Set to false to not require bcrypt, useful if using custom authentication.
19
19
  session_key :: The key in the session hash storing the primary key of the logged in account.
20
+ session_key_prefix :: The string that will be prepended to the default value for all session keys.
20
21
  skip_status_checks? :: Whether status checks should be skipped for accounts. Defaults to true unless enabling the verify_account or close_account features.
21
22
  title_instance_variable :: The instance variable to set in the Roda scope with the page title. The layout should use this instance variable if available to set the title of the page. You can use +set_title+ if setting the page title is not done through an instance variable.
22
23
 
@@ -87,6 +88,7 @@ account_session_value :: The primary value of the current account to store in th
87
88
  after_login :: Run arbitrary code after a successful login.
88
89
  after_login_failure :: Run arbitrary code after a login failure due to an invalid password.
89
90
  already_logged_in :: What action to take if you are already logged in and attempt to access a page that only makes sense if you are not logged in.
91
+ around_rodauth(&block) :: Run arbitrary code around handling any rodauth route. Call <tt>super(&block)</tt> for Rodauth to handle the action.
90
92
  authenticated? :: Whether the user has been authenticated. If multifactor authentication has been enabled for the account, this is true only if the session is multifactor authenticated.
91
93
  before_login :: Run arbitrary code after password has been checked, but before updating the session.
92
94
  before_login_attempt :: Run arbitrary code after an account has been located, but before the password has been checked.
@@ -0,0 +1,46 @@
1
+ = Require account verification by admin
2
+
3
+ There are scenarios in which, instead of allowing the user to verify they have
4
+ access to the email for the account, you may want to have an admin or moderator
5
+ approve new accounts manually. One way this can be achieved by sending the
6
+ account verification email to the admin:
7
+
8
+ plugin :rodauth do
9
+ enable :login, :logout, :verify_account, :reset_password
10
+
11
+ # Send account verification email to the admin
12
+ email_to do
13
+ if account[account_status_column] == account_unverified_status_value
14
+ "admin@myapp.com"
15
+ else
16
+ super()
17
+ end
18
+ end
19
+
20
+ # Do not ask for password when creating or verifying account
21
+ verify_account_set_password? false
22
+ create_account_set_password? false
23
+
24
+ # Adjust the account verification email subject and body
25
+ verify_account_email_subject "New User Awaiting Admin Approval"
26
+ verify_account_email_body do
27
+ "The user #{account[login_column]} has created an account. Click here to approve it: #{verify_account_email_link}."
28
+ end
29
+
30
+ # Display this message to the user after they've created their account
31
+ verify_account_email_sent_notice_flash "Your account has been created and is awaiting approval"
32
+
33
+ # Prevent the admin from being logged in after confirming the account
34
+ verify_account_autologin? false
35
+ verify_account_notice_flash "The account has been approved"
36
+
37
+ # Send a reset password email after verifying the account.
38
+ # This allows the user to choose the password for the account,
39
+ # and also makes sure the user can only log in if they have
40
+ # access to the email address for the account.
41
+ after_verify_account do
42
+ generate_reset_password_key_value
43
+ create_reset_password_key
44
+ send_reset_password_email
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ = Skip login page if already authenticated
2
+
3
+ In some cases it may be useful to skip login/registration pages when the user
4
+ is already logged in. This can be achieved as follows. Note that this only
5
+ matters if the user manually navigates to the login or create account pages.
6
+
7
+ plugin :rodauth do
8
+ # Redirect logged in users to the wherever login redirects to
9
+ already_logged_in { redirect login_redirect }
10
+ end
@@ -0,0 +1,46 @@
1
+ = Use a non-email login
2
+
3
+ Rodauth's by default uses email addresses for identifying users, since that is
4
+ the most common form of identifier currently. In some cases, you might want
5
+ to allow logging in via alternative identifiers, such as a username. In this
6
+ case, it is best to choose a different column name for the login, such as
7
+ +:username+. Among other things, this also makes it so that the login field
8
+ does not expect an email address to be provided.
9
+
10
+ plugin :rodauth do
11
+ enable :login, :logout
12
+ login_column :username
13
+ end
14
+
15
+ Note that Rodauth features that require sending email need an email address, and
16
+ that defaults to the value of the login column. If you have both a username and
17
+ an email for an account, you can have the login column be the user, and use the
18
+ value of the email colummn for the email address.
19
+
20
+ plugin :rodauth do
21
+ enable :login, :logout, :reset_password
22
+
23
+ login_column :username
24
+ email_to do
25
+ account[:email]
26
+ end
27
+ end
28
+
29
+ An alternative approach would be to accept a login and automatically change it
30
+ to an email address. If you have a +username+ field on the +accounts+ table,
31
+ then you can configure Rodauth to allow entering a username instead of email
32
+ during login. See the {Adding new registration field}[rdoc-ref:doc/guides/registration_field.rdoc]
33
+ guide for instructions on requiring add an additional field during registration.
34
+
35
+ plugin :rodauth do
36
+ enable :login, :logout
37
+
38
+ account_from_login do |login|
39
+ # handle the case when login parameter is a username
40
+ unless login.include?("@")
41
+ login = db[:accounts].where(username: login).get(:email)
42
+ end
43
+
44
+ super(login)
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ = Create an account record programmatically
2
+
3
+ In some scenarios you might want to create an account records programmatically,
4
+ for example in your tests.
5
+
6
+ If you're storing passwords in a separate table, you can create an account
7
+ records as follows:
8
+
9
+ account_id = DB[:accounts].insert(
10
+ email: "name@example.com",
11
+ status_id: 2, # verified
12
+ )
13
+
14
+ DB[:account_password_hashes].insert(
15
+ id: account_id,
16
+ password_hash: BCrypt::Password.create("secret").to_s,
17
+ )
18
+
19
+ If the password is stored in a column in the accounts table:
20
+
21
+ account_id = DB[:accounts].insert(
22
+ email: "name@example.com",
23
+ password_hash: BCrypt::Password.create("secret").to_s,
24
+ status_id: 2, # verified
25
+ )
26
+
27
+ If you are creating accounts in your tests, you probably want to use
28
+ the +:cost+ option, otherwise you will have very slow tests:
29
+
30
+ account_id = DB[:accounts].insert(
31
+ email: "name@example.com",
32
+ status_id: 2, # verified
33
+ )
34
+
35
+ DB[:account_password_hashes].insert(
36
+ id: account_id,
37
+ password_hash: BCrypt::Password.create("secret", cost: BCrypt::Engine::MIN_COST).to_s,
38
+ )
@@ -0,0 +1,25 @@
1
+ = Set password when verifying account
2
+
3
+ If you want to request less information from the user on registration, you can
4
+ ask the user to set their password only when they verify their account:
5
+
6
+ plugin :rodauth do
7
+ enable :login, :logout, :verify_account
8
+ verify_account_set_password? true
9
+ end
10
+
11
+ Note that this is already the default behaviour when verify account feature is
12
+ loaded, but it's not when verify account grace period is used, because it would
13
+ prevent the account from logging in during the grace period. You can work around
14
+ this by automatically remebering their login during account creation using the
15
+ remember feature. Be aware that remembering accounts has effects beyond the
16
+ verification period, and this would only allow automatic logins from the browser
17
+ that created the account.
18
+
19
+ plugin :rodauth do
20
+ enable :login, :logout, :verify_account_grace_period, :remember
21
+ verify_account_set_password? true
22
+ after_create_account do
23
+ remember_login
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ = Allow only email authentication
2
+
3
+ When using the email authentication feature, you can avoid other authentication
4
+ mechanisms entirely as follows:
5
+
6
+ plugin :rodauth do
7
+ enable :login, :email_auth, :create_account, :verify_account
8
+
9
+ create_account_set_password? false
10
+ verify_account_set_password? false
11
+ force_email_auth? true
12
+ end
13
+
14
+ With this configuration, users won't be required to enter a password on
15
+ registration, and on login the email authentication link will automatically be
16
+ sent after the email address is entered.
@@ -0,0 +1,26 @@
1
+ = Translate with i18n gem
2
+
3
+ Rodauth allows transforming user-facing text configuration such as flash
4
+ messages, validation errors, labels etc. via the +translate+ configuration
5
+ method. This method receives a name of a configuration along with its default
6
+ value, and is expected to return the result text.
7
+
8
+ You can use this to perform translations using the
9
+ {i18n gem}[https://github.com/ruby-i18n/i18n]:
10
+
11
+ plugin :rodauth do
12
+ enable :login, :logout, :reset_password
13
+
14
+ translate do |key, default|
15
+ I18n.translate("rodauth.#{key}") || default
16
+ end
17
+ end
18
+
19
+ Your translation file may then look something like this:
20
+
21
+ en:
22
+ rodauth:
23
+ login_notice_flash: "You have been signed in"
24
+ require_login_error_flash: "Login is required for accessing this page"
25
+ no_matching_login_message: "user with this email address doesn't exist"
26
+ reset_password_email_subject: "Password Reset Instructions"
@@ -0,0 +1,12 @@
1
+ = Display authentication links
2
+
3
+ You can retrieve a relative URL to any Rodauth action by calling the
4
+ corresponding <tt>*_path</tt> method on the Rodauth instance:
5
+
6
+ <a href="<%= rodauth.login_path %>">Sign in</a>
7
+ <a href="<%= rodauth.create_account_path %>">Sign up</a>
8
+
9
+ For absolute URLs instead of paths, you can use the <tt>*_url</tt> methods:
10
+
11
+ <a href="<%= rodauth.login_url %>">Sign in</a>
12
+ <a href="<%= rodauth.create_account_url %>">Sign up</a>
@@ -0,0 +1,37 @@
1
+ = Redirect to original page after login
2
+
3
+ When the user attempts to open a page that requires authentication, Rodauth
4
+ redirects them to the login page. It can be useful to redirect them back to
5
+ the page they originally requested after successful login. Similarly, you
6
+ can do this for pages requiring multifactor authentication.
7
+
8
+ plugin :rodauth do
9
+ enable :login, :logout, :otp
10
+
11
+ # Have successful login redirect back to originally requested page
12
+ login_return_to_requested_location? true
13
+
14
+ # Have successful multifactor authentication redirect back to
15
+ # originally requested page
16
+ two_factor_auth_return_to_requested_location? true
17
+ end
18
+
19
+ You can manually set which page to redirect after login or multifactor
20
+ authentication, though it is questionable whether the user will desire
21
+ this behavior compared to the default.
22
+
23
+ route do |r|
24
+ r.rodauth
25
+
26
+ # Return the last visited path after login
27
+ if rodauth.logged_in?
28
+ # Return to the last visited page after multifactor authentication
29
+ unless rodauth.two_factor_authenticated?
30
+ session[rodauth.two_factor_auth_redirect_session_key] = request.fullpath
31
+ end
32
+ else
33
+ session[rodauth.login_redirect_session_key] = request.fullpath
34
+ end
35
+
36
+ # rest of routes
37
+ end
@@ -0,0 +1,25 @@
1
+ = Store password hash in accounts table
2
+
3
+ By default, Rodauth stores the password hash in a separate
4
+ +account_password_hashes+ table. This makes it a lot less likely that the
5
+ password hashes will be leaked, especially if you use Rodauth's default
6
+ approach of using database functions for checking the hashes.
7
+
8
+ However, if you have reasons for storing the password hashes in +accounts+
9
+ table that outweigh the security benefits of Rodauth's default approach,
10
+ Rodauth supports that.
11
+
12
+ To do this, add the password hash column to the +accounts+ table:
13
+
14
+ alter_table :accounts do
15
+ add_column :password_hash, String
16
+ end
17
+
18
+ And then tell Rodauth to use it:
19
+
20
+ plugin :rodauth do
21
+ enable :login, :logout
22
+
23
+ # Use the password_hash column in the accounts table
24
+ account_password_hash_column :password_hash
25
+ end
@@ -0,0 +1,37 @@
1
+ = Require password confirmation for certain actions
2
+
3
+ You might want to require the user to enter their password before accessing
4
+ sensitive sections of the app. This functionality is provided by the confirm
5
+ password feature, which accompanied with the password grace period feature will
6
+ remember the entered password for a period of time:
7
+
8
+ plugin :rodauth do
9
+ enable :confirm_password, :password_grace_period
10
+
11
+ # Remember the password for 1 hour
12
+ password_grace_period 60*60
13
+ end
14
+
15
+ route do |r|
16
+ r.rodauth
17
+
18
+ r.is 'some-action' do
19
+ # Require password authentication if the password has not been
20
+ # input recently.
21
+ rodauth.require_password_authentication
22
+
23
+ # ...
24
+ end
25
+ end
26
+
27
+ You can also do this for Rodauth actions that normally require a password.
28
+ Which essentially moves the password confirmation into a separate step, as
29
+ Rodauth's behavior with the password grace period feature is to ask for the
30
+ password on the same form.
31
+
32
+ plugin :rodauth do
33
+ enable :confirm_password, :password_grace_period, :change_login, :change_password
34
+
35
+ before_change_login_route { require_password_authentication }
36
+ before_change_password_route { require_password_authentication }
37
+ end