rodauth 2.3.0 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ea58deac2998a11cd0cd4b17545eab5fce6fb0acd7751416fd521cbd77d6add
4
- data.tar.gz: c113c1131f5d756fd3aa5c1dbf083f40506b8822fb1c9b67537069c9e8695fba
3
+ metadata.gz: a39430563fc9d81e610b8a28b5c044eedd7ebd361d6294f9bd02687ce2d24c27
4
+ data.tar.gz: a852e9713602b807bb7566b3db4038a6fda0ae0d2b90bf4b1ce2e45429c8efd2
5
5
  SHA512:
6
- metadata.gz: 1fa2ce1d08a9f09e21d2fb1fe5b71ea3c6fc55181452302a35929229f0fad8c3c5d3d3324d73bee9c69daf0ff7a2d14ddbb731ef994bd3317512627233a69d28
7
- data.tar.gz: 7a188457006390730f00bc4a2ea5159c002dba66b300fd993929a5b5a491f2f4be0191c7e7c8e4b9e82b2e709b1a4e8ea8f5d335613f262671e1d68507cc2e26
6
+ metadata.gz: ba8f83d3f9afc3f1bcca6f649e925b1b3f795002dc980ae5bfb219885a74c69c34bbacaccda83c153bf9f799f328970cd900a3abf5966b88956dc3476ef3f9f8
7
+ data.tar.gz: 9d2042a183a7fdc941cd2b8716aba581861b9e1831574eac3241979ab32e2fe2076baf94e3ab0782487aa34d4e7f98f063035c22dcd7088f1a4fd1f1b83a7e6d
data/CHANGELOG CHANGED
@@ -1,3 +1,45 @@
1
+ === 2.8.0 (2021-01-06)
2
+
3
+ * [SECURITY] Set HttpOnly on remember cookie by default so it cannot be accessed by Javascript (janko) (#142)
4
+
5
+ * Clear JWT session when rodauth.clear_session is called if the Roda sessions plugin is used (janko) (#140)
6
+
7
+ === 2.7.0 (2020-12-22)
8
+
9
+ * Avoid method redefinition warnings in verbose warning mode (jeremyevans)
10
+
11
+ * Return expired access token error message in the JWT refresh feature when using an expired token when it isn't allowed (AlexyMatskevich) (#133)
12
+
13
+ * Allow Rodauth features to be preloaded, instead of always trying to require them (janko) (#136)
14
+
15
+ * Use a default remember cookie path of '/', though this may cause problem with multiple Rodauth configurations on the same domain (janko) (#134)
16
+
17
+ * Add auto_remove_recovery_codes? to the recovery_codes feature, for automatically removing the codes when disabling multifactor authentication (SilasSpet, jeremyevans) (#135)
18
+
19
+ === 2.6.0 (2020-11-20)
20
+
21
+ * Avoid loading features multiple times (janko) (#131)
22
+
23
+ * Add around_rodauth method for running code around the handling of all Rodauth routes (bjeanes) (#129)
24
+
25
+ * Fix javascript for registration of multiple webauthn keys (bjeanes) (#127)
26
+
27
+ * Add allow_refresh_with_expired_jwt_access_token? configuration method to jwt_refresh feature, for allowing refresh with expired access token (jeremyevans)
28
+
29
+ * Promote setup_account_verification to public API, useful for automatically sending account verification emails (jeremyevans)
30
+
31
+ === 2.5.0 (2020-10-22)
32
+
33
+ * Add change_login_needs_verification_notice_flash for easier translation of change_login_notice_flash when using verify_login_change (bjeanes, janko, jeremyevans) (#126)
34
+
35
+ * Add login_return_to_requested_location_path for controlling path to use as the requested location (HoneyryderChuck, jeremyevans) (#122, #123)
36
+
37
+ === 2.4.0 (2020-09-21)
38
+
39
+ * Add session_key_prefix for more easily using separate session keys when using multiple configurations (janko) (#121)
40
+
41
+ * Add password_pepper feature for appending a secret key to passwords before they are hashed, supporting secret rotation (janko) (#119)
42
+
1
43
  === 2.3.0 (2020-08-21)
2
44
 
3
45
  * 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)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015-2020 Jeremy Evans
1
+ Copyright (c) 2015-2021 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -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.
@@ -21,19 +21,27 @@ a value of <tt>all</tt> as the token value.
21
21
 
22
22
  When using the refresh token, you must provide a valid access token, as that contains
23
23
  information about the current session, which is used to create the new access token.
24
+ If you change the +allow_refresh_with_expired_jwt_access_token?+ setting to +true+,
25
+ an expired but otherwise valid access token will be accepted, and Rodauth will check
26
+ that the access token was issued in the same session as the refresh token.
24
27
 
25
28
  This feature depends on the jwt feature.
26
29
 
27
30
  == Auth Value Methods
28
31
 
32
+ allow_refresh_with_expired_jwt_access_token? :: Whether refreshing should be allowed with an expired access token. Default is +false+. You must set an +hmac_secret+ if setting this value to +true+.
33
+ expired_jwt_access_token_status :: The HTTP status code to use when a access token (JWT) is expired is submitted in the Authorization header. Default is 400 for backwards compatibility, and it is recommended to set it to 401.
34
+ expired_jwt_access_token_message :: The error message to use when a access token (JWT) is expired is submitted in the Authorization header.
29
35
  jwt_access_token_key :: Name of the key in the response json holding the access token. Default is +access_token+.
30
36
  jwt_access_token_not_before_period :: How many seconds before the current time will the jwt be considered valid (to account for inaccurate clocks). Default is 5.
31
37
  jwt_access_token_period :: Validity of an access token in seconds, default is 1800 (30 minutes).
32
38
  jwt_refresh_route :: The route to the login action. Defaults to <tt>jwt-refresh</tt>.
33
39
  jwt_refresh_invalid_token_message :: Error message when the provided refresh token is non existent, invalid or expired.
34
40
  jwt_refresh_token_account_id_column :: The column name in the +jwt_refresh_token_table+ storing the account id, should be a foreign key referencing the accounts table.
41
+ jwt_refresh_token_data_session_key :: The key in the session hash storing random data, for access checking during refresh if +allow_refresh_with_expired_jwt_access_token?+ is set.
35
42
  jwt_refresh_token_deadline_column :: The column name in the +jwt_refresh_token_table+ storing the deadline after which the refresh token will no longer be valid.
36
43
  jwt_refresh_token_deadline_interval :: Validity of a refresh token. Default is 14 days.
44
+ jwt_refresh_token_hmac_session_key :: The key in the session hash storing the hmac, for access checking during refresh if +allow_refresh_with_expired_jwt_access_token?+ is set.
37
45
  jwt_refresh_token_id_column :: The column name in the refresh token keys table storing the id of each token (the primary key of the table).
38
46
  jwt_refresh_token_key :: Name of the key in the response json holding the refresh token. Default is +refresh_token+.
39
47
  jwt_refresh_token_key_column :: The column name in the +jwt_refresh_token_table+ holding the refresh token key value.
@@ -34,4 +34,5 @@ use_multi_phase_login? :: Whether to ask for login first, and only ask for passw
34
34
 
35
35
  before_login_route :: Run arbitrary code before handling a login route.
36
36
  login_view :: The HTML to use for the login form.
37
+ login_return_to_requested_location_path :: If +login_return_to_requested_location?+ is true, the path to use as the requested location. By default, uses the full path of the request for GET requests, and is nil for non-GET requests (in which case the default +login_redirect+ will be used).
37
38
  multi_phase_login_view :: The HTML to use for the login form after login has been entered when using multi phase login.
@@ -0,0 +1,44 @@
1
+ = Documentation for Password Pepper Feature
2
+
3
+ The password pepper feature appends a specified secret string to passwords
4
+ before they are hashed. This way, if the password hashes get compromised, an
5
+ attacker cannot use them to crack the passwords without also knowing the
6
+ pepper.
7
+
8
+ In the configuration block set the +password_pepper+ with your secret string.
9
+ It's recommended for the password pepper to be at last 32 characters long and
10
+ randomly generated.
11
+
12
+ password_pepper "<long secret key>"
13
+
14
+ If your database already contains password hashes that were created without a
15
+ password pepper, these will get automatically updated with a password pepper
16
+ next time the user successfully enters their password.
17
+
18
+ You can rotate the password pepper as well, just make sure to add the previous
19
+ pepper to the +previous_password_peppers+ array. Password hashes using the old
20
+ pepper will get automatically updated on the next successful password match.
21
+
22
+ password_pepper "new pepper"
23
+ previous_password_peppers ["old pepper", ""]
24
+
25
+ The empty string above ensures password hashes without pepper are handled as
26
+ well.
27
+
28
+ Note that each entry in +previous_password_peppers+ will multiply the amount of
29
+ possible password checks during login, at least for incorrect passwords.
30
+
31
+ Additionally, when using this feature with the disallow_password_reuse feature,
32
+ the number of passwords checked when changing or resetting a password will be
33
+
34
+ (previous_password_peppers.length + 1) * previous_passwords_to_check
35
+
36
+ So if you have 2 entries in +previous_password_peppers+, using the default
37
+ value of 6 for +previous_passwords_to_check+, every time a password
38
+ is changed, there will be 18 password checks done, which will be quite slow.
39
+
40
+ == Auth Value Methods
41
+
42
+ password_pepper :: The secret string appended to passwords before they are hashed.
43
+ previous_password_peppers :: An array of password peppers that will be tried on an unsuccessful password match. Defaults to <tt>[""]</tt>, which allows introducing this feature with existing passwords.
44
+ password_pepper_update? :: Whether to update password hashes that use a pepper from +previous_password_peppers+ with a new pepper. Defaults to +true+.
@@ -17,7 +17,8 @@ add_recovery_codes_error_flash :: The flash error to show when adding recovery c
17
17
  add_recovery_codes_heading :: Text to use for heading above the form to add recovery codes.
18
18
  add_recovery_codes_page_title :: The page title to use on the add recovery codes form.
19
19
  add_recovery_codes_param :: The parameter name to use for adding recovery codes.
20
- auto_add_recovery_codes? :: Whether to automatically add recovery codes (or any missing recovery codes) when another multifactor authentication type is enabled (false by default).
20
+ auto_add_recovery_codes? :: Whether to automatically add recovery codes (or any missing recovery codes) when enabling otp, webauthn, or sms authentication (false by default).
21
+ auto_remove_recovery_codes? :: Whether to automatically remove recovery codes when disabling otp, webauthn, or sms authentication and not having one of the other two authentication methods enabled (false by default).
21
22
  invalid_recovery_code_error_flash :: The flash error to show when an invalid recovery code is used.
22
23
  invalid_recovery_code_message :: The error message to show when an invalid recovery code is used.
23
24
  recovery_auth_additional_form_tags :: HTML fragment containing additional form tags when authenticating via a recovery code.
@@ -0,0 +1,22 @@
1
+ = New Features
2
+
3
+ * A password_pepper feature has been added. This allows you to use a
4
+ secret key (called a pepper) to append to passwords before hashing
5
+ and hash checking. Using this approach, if an attacker obtains the
6
+ password hash, it is unusable for cracking unless they can also
7
+ get access to the pepper.
8
+
9
+ The password_pepper feature also supports a list of previous peppers
10
+ that can be used to implement secret rotation and to support
11
+ compatibility with unpeppered passwords.
12
+
13
+ Rodauth by default uses database functions for password hash
14
+ checking on PostgreSQL, MySQL, and Microsoft SQL Server, which in
15
+ general provides more security than a password pepper, but both
16
+ approaches can be used simultaneously.
17
+
18
+ * A session_key_prefix configuration method has been added for
19
+ prefixing the values of all default session keys. This can be
20
+ useful if you are using multiple Rodauth configurations in the same
21
+ application and want to make sure the session keys for the separate
22
+ configurations do not overlap.
@@ -0,0 +1,20 @@
1
+ = New Features
2
+
3
+ * A login_return_to_requested_location_path configuration method has
4
+ been added to the login feature. This controls the path to redirect
5
+ to if using login_return_to_requested_location?. By default, this
6
+ is the same as the fullpath of the request that required login if
7
+ that request was a GET request, and nil if that request was not a
8
+ GET request. Previously, the fullpath of that request was used even
9
+ if it was not a GET request, which caused problems as browsers use a
10
+ GET request for redirects, and it is a bad idea to redirect to a path
11
+ that may not handle GET requests.
12
+
13
+ * A change_login_needs_verification_notice_flash configuration method
14
+ has been added to the verify_login_change feature, for allowing
15
+ translations when using the feature and not using the
16
+ change_login_notice_flash configuration method.
17
+
18
+ = Other Improvements
19
+
20
+ * new_password_label is now translatable.
@@ -0,0 +1,37 @@
1
+ = New Features
2
+
3
+ * An around_rodauth configuration method has been added, which is
4
+ called around all Rodauth actions. This configuration method
5
+ is passed a block, and is useful for cases where you want to wrap
6
+ Rodauth's handling of the request.
7
+
8
+ For example, if you had a method named time_block in your Roda scope
9
+ that timed block execution and added a response header, you could
10
+ time Rodauth actions using something like:
11
+
12
+ around_rodauth do |&block|
13
+ scope.time_block('Rodauth') do
14
+ super(&block)
15
+ end
16
+ end
17
+
18
+ * The allow_refresh_with_expired_jwt_access_token? configuration has
19
+ been added to the jwt_refresh feature, allowing refreshing with an
20
+ expired but otherwise valid access token. When using this method,
21
+ it is required to have an hmac_secret specified, so that Rodauth
22
+ can make sure the access token matches the refresh token.
23
+
24
+ = Other Improvements
25
+
26
+ * The javascript for setting up a WebAuthn token has been fixed to
27
+ allow it to work correctly if there is already an existing
28
+ WebAuthn token for the account.
29
+
30
+ * The rodauth.setup_account_verification method has been promoted to
31
+ public API. You can use this method for automatically sending
32
+ account verification emails when automatically creating accounts.
33
+
34
+ * Rodauth no longer loads the same feature multiple times into a
35
+ single configuration. This didn't cause any problems before, but
36
+ could result in duplicate entries when looking at the loaded
37
+ features.
@@ -0,0 +1,33 @@
1
+ = New Features
2
+
3
+ * An auto_remove_recovery_codes? configuration method has been added
4
+ to the recovery_codes feature. This will automatically remove
5
+ recovery codes when the last multifactor authentication type other
6
+ than the recovery codes has been removed.
7
+
8
+ * The jwt_access_expired_status and expired_jwt_access_token_message
9
+ configuration methods have been added to the jwt_refresh feature,
10
+ for supporting custom statuses and messages for expired tokens.
11
+
12
+ = Other Improvements
13
+
14
+ * Rodauth will no longer attempt to require a feature that has
15
+ already been required. Related to this is you can now use a
16
+ a custom Rodauth feature without a rodauth/features/*.rb file
17
+ in the Ruby library path, as long as you load the feature
18
+ manually.
19
+
20
+ * Rodauth now avoids method redefinition warnings in verbose
21
+ warning mode. As Ruby 3 is dropping uninitialized instance
22
+ variable warnings, Rodauth will be verbose warning free in
23
+ Ruby 3.
24
+
25
+ = Backwards Compatibility
26
+
27
+ * The default remember cookie path is now set to '/'. This fixes
28
+ usage in the case where rodauth is loaded under a subpath of the
29
+ application (which is not the default behavior). Unfortunately,
30
+ this change can negatively affect cases where multiple rodauth
31
+ configurations are used in separate paths on the same domain.
32
+ In these cases, you should now use remember_cookie_options and
33
+ include a :path option.
@@ -0,0 +1,20 @@
1
+ = Improvements
2
+
3
+ * HttpOnly is now set by default on the remember cookie, so it is no
4
+ longer accessible from Javascript. This is a more secure approach
5
+ that makes applications using Rodauth's remember feature less
6
+ vulnerable in case they are subject to a separate XSS attack.
7
+
8
+ * When using the jwt feature, rodauth.clear_session now clears the
9
+ JWT session even when the Roda sessions plugin was in use. In most
10
+ cases, the jwt feature is not used with the Roda sessions plugin,
11
+ but in cases where the same application serves as both an JSON API
12
+ and as a HTML site, it is possible the two may be used together.
13
+
14
+ = Backwards Compatibility
15
+
16
+ * As the default remember cookie :httponly setting is now set to true,
17
+ applications using Rodauth that expected to be able to access the
18
+ remember cookie from Javascript will no longer work by default.
19
+ In these cases, you should now use remember_cookie_options and
20
+ include a :httponly=>false option.
@@ -35,7 +35,7 @@ raw_remember_token_deadline :: A deadline before which to allow a raw remember t
35
35
  remember_additional_form_tags :: HTML fragment containing additional form tags to use on the change remember setting form.
36
36
  remember_button :: The text to use for the change remember settings button.
37
37
  remember_cookie_key :: The cookie name to use for the remember token.
38
- remember_cookie_options :: Any options to set for the remember cookie.
38
+ remember_cookie_options :: Any options to set for the remember cookie. By default, the `:path` cookie option is set to `/`, and `:httponly` is set to `true`.
39
39
  remember_deadline_column :: The column name in the +remember_table+ storing the deadline after which the token will be ignored.
40
40
  remember_deadline_interval :: The amount of time for which to remember accounts, 14 days by default. Only used if +set_deadline_values?+ is true.
41
41
  remember_disable_label :: The label for disabling remembering.
@@ -14,6 +14,7 @@ control. Depends on the change login and email base features.
14
14
  == Auth Value Methods
15
15
 
16
16
  no_matching_verify_login_change_key_error_flash :: The flash error message to show when an invalid verify login change key is used.
17
+ change_login_needs_verification_notice_flash :: The flash notice to show after changing a login when using this feature, if +change_login_notice_flash+ is not overridden.
17
18
  verify_login_change_additional_form_tags :: HTML fragment containing additional form tags to use on the verify login change form.
18
19
  verify_login_change_autologin? :: Whether to autologin the user after successful login change verification, false by default.
19
20
  verify_login_change_button :: The text to use for the verify login change button.
@@ -1,34 +1,34 @@
1
1
  (function() {
2
+ var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
3
+ var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
2
4
  var element = document.getElementById('webauthn-auth-form');
3
5
  var f = function(e) {
4
6
  //console.log(e);
5
7
  e.preventDefault();
6
8
  if (navigator.credentials) {
7
9
  var opts = JSON.parse(element.getAttribute("data-credential-options"));
8
- opts.challenge = Uint8Array.from(atob(opts.challenge.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
9
- opts.allowCredentials.forEach(function(cred) {
10
- cred.id = Uint8Array.from(atob(cred.id.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
11
- });
10
+ opts.challenge = unpack(opts.challenge);
11
+ opts.allowCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
12
12
  //console.log(opts);
13
13
  navigator.credentials.get({publicKey: opts}).
14
14
  then(function(cred){
15
15
  //console.log(cred);
16
16
  //window.cred = cred
17
17
 
18
- var rawId = btoa(String.fromCharCode.apply(null, new Uint8Array(cred.rawId))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
18
+ var rawId = pack(cred.rawId);
19
19
  var authValue = {
20
20
  type: cred.type,
21
21
  id: rawId,
22
22
  rawId: rawId,
23
23
  response: {
24
- authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.authenticatorData))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''),
25
- clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.clientDataJSON))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''),
26
- signature: btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.signature))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
24
+ authenticatorData: pack(cred.response.authenticatorData),
25
+ clientDataJSON: pack(cred.response.clientDataJSON),
26
+ signature: pack(cred.response.signature)
27
27
  }
28
28
  };
29
29
 
30
30
  if (cred.response.userHandle) {
31
- authValue.response.userHandle = btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.userHandle))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
31
+ authValue.response.userHandle = pack(cred.response.userHandle);
32
32
  }
33
33
 
34
34
  document.getElementById('webauthn-auth').value = JSON.stringify(authValue);
@@ -1,26 +1,29 @@
1
1
  (function() {
2
+ var pack = function(v) { return btoa(String.fromCharCode.apply(null, new Uint8Array(v))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); };
3
+ var unpack = function(v) { return Uint8Array.from(atob(v.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0)); };
2
4
  var element = document.getElementById('webauthn-setup-form');
3
5
  var f = function(e) {
4
6
  //console.log(e);
5
7
  e.preventDefault();
6
8
  if (navigator.credentials) {
7
9
  var opts = JSON.parse(element.getAttribute("data-credential-options"));
8
- opts.challenge = Uint8Array.from(atob(opts.challenge.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
9
- opts.user.id = Uint8Array.from(atob(opts.user.id.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
10
+ opts.challenge = unpack(opts.challenge);
11
+ opts.user.id = unpack(opts.user.id);
12
+ opts.excludeCredentials.forEach(function(cred) { cred.id = unpack(cred.id); });
10
13
  //console.log(opts);
11
14
  navigator.credentials.create({publicKey: opts}).
12
15
  then(function(cred){
13
16
  //console.log(cred);
14
17
  //window.cred = cred
15
-
16
- var rawId = btoa(String.fromCharCode.apply(null, new Uint8Array(cred.rawId))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
18
+
19
+ var rawId = pack(cred.rawId);
17
20
  document.getElementById('webauthn-setup').value = JSON.stringify({
18
21
  type: cred.type,
19
22
  id: rawId,
20
23
  rawId: rawId,
21
24
  response: {
22
- attestationObject: btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.attestationObject))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''),
23
- clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(cred.response.clientDataJSON))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
25
+ attestationObject: pack(cred.response.attestationObject),
26
+ clientDataJSON: pack(cred.response.clientDataJSON)
24
27
  }
25
28
  });
26
29
  element.removeEventListener("submit", f);
@@ -66,6 +66,7 @@ module Rodauth
66
66
  define_method(meth) do |&block|
67
67
  @auth.send(:define_method, meth, &block)
68
68
  @auth.send(:private, meth) if priv
69
+ @auth.send(:alias_method, meth, meth)
69
70
  end
70
71
  end
71
72
 
@@ -74,6 +75,7 @@ module Rodauth
74
75
  define_method(meth) do |&block|
75
76
  @auth.send(:define_method, umeth, &block)
76
77
  @auth.send(:private, umeth)
78
+ @auth.send(:alias_method, umeth, umeth)
77
79
  end
78
80
  end
79
81
 
@@ -82,6 +84,7 @@ module Rodauth
82
84
  block ||= proc{v}
83
85
  @auth.send(:define_method, meth, &block)
84
86
  @auth.send(:private, meth) if priv
87
+ @auth.send(:alias_method, meth, meth)
85
88
  end
86
89
  end
87
90
  end
@@ -120,8 +123,10 @@ module Rodauth
120
123
  define_method(handle_meth) do
121
124
  request.is send(route_meth) do
122
125
  check_csrf if check_csrf?
123
- before_rodauth
124
- send(internal_handle_meth, request)
126
+ _around_rodauth do
127
+ before_rodauth
128
+ send(internal_handle_meth, request)
129
+ end
125
130
  end
126
131
  end
127
132
 
@@ -238,6 +243,7 @@ module Rodauth
238
243
  instance_variable_set(iv, send(umeth))
239
244
  end
240
245
  end
246
+ alias_method(meth, meth)
241
247
  auth_private_methods(meth)
242
248
  end
243
249
 
@@ -288,15 +294,17 @@ module Rodauth
288
294
  end
289
295
 
290
296
  def enable(*features)
291
- new_features = features - @auth.features
292
- new_features.each{|f| load_feature(f)}
293
- @auth.features.concat(new_features)
297
+ features.each do |feature|
298
+ next if @auth.features.include?(feature)
299
+ load_feature(feature)
300
+ @auth.features << feature
301
+ end
294
302
  end
295
303
 
296
304
  private
297
305
 
298
306
  def load_feature(feature_name)
299
- require "rodauth/features/#{feature_name}"
307
+ require "rodauth/features/#{feature_name}" unless FEATURES[feature_name]
300
308
  feature = FEATURES[feature_name]
301
309
  enable(*feature.dependencies)
302
310
  extend feature.configuration
@@ -47,6 +47,7 @@ module Rodauth
47
47
  session_key :authenticated_by_session_key, :authenticated_by
48
48
  session_key :autologin_type_session_key, :autologin_type
49
49
  auth_value_method :prefix, ''
50
+ auth_value_method :session_key_prefix, nil
50
51
  auth_value_method :require_bcrypt?, true
51
52
  auth_value_method :mark_input_fields_as_required?, true
52
53
  auth_value_method :mark_input_fields_with_autocomplete?, true
@@ -101,7 +102,6 @@ module Rodauth
101
102
  :set_redirect_error_flash,
102
103
  :set_title,
103
104
  :translate,
104
- :unverified_account_message,
105
105
  :update_session
106
106
  )
107
107
 
@@ -110,7 +110,8 @@ module Rodauth
110
110
  :account_from_session,
111
111
  :field_attributes,
112
112
  :field_error_attributes,
113
- :formatted_field_error
113
+ :formatted_field_error,
114
+ :around_rodauth
114
115
  )
115
116
 
116
117
  configuration_module_eval do
@@ -259,6 +260,7 @@ module Rodauth
259
260
  @password_field_autocomplete_value || 'current-password'
260
261
  end
261
262
 
263
+ alias account_password_hash_column account_password_hash_column
262
264
  # If the account_password_hash_column is set, the password hash is verified in
263
265
  # ruby, it will not use a database function to do so, it will check the password
264
266
  # hash using bcrypt.
@@ -393,9 +395,9 @@ module Rodauth
393
395
  def password_match?(password)
394
396
  if hash = get_password_hash
395
397
  if account_password_hash_column || !use_database_authentication_functions?
396
- BCrypt::Password.new(hash) == password
398
+ password_hash_match?(hash, password)
397
399
  else
398
- db.get(Sequel.function(function_name(:rodauth_valid_password_hash), account_id, BCrypt::Engine.hash_secret(password, hash)))
400
+ database_function_password_match?(:rodauth_valid_password_hash, account_id, password, hash)
399
401
  end
400
402
  end
401
403
  end
@@ -458,6 +460,18 @@ module Rodauth
458
460
 
459
461
  private
460
462
 
463
+ def _around_rodauth
464
+ yield
465
+ end
466
+
467
+ def database_function_password_match?(name, hash_id, password, salt)
468
+ db.get(Sequel.function(function_name(name), hash_id, BCrypt::Engine.hash_secret(password, salt)))
469
+ end
470
+
471
+ def password_hash_match?(hash, password)
472
+ BCrypt::Password.new(hash) == password
473
+ end
474
+
461
475
  def convert_token_key(key)
462
476
  if key && hmac_secret
463
477
  compute_hmac(key)
@@ -493,6 +507,7 @@ module Rodauth
493
507
  end
494
508
 
495
509
  def convert_session_key(key)
510
+ key = "#{session_key_prefix}#{key}".to_sym if session_key_prefix
496
511
  scope.opts[:sessions_convert_symbols] ? key.to_s : key
497
512
  end
498
513
 
@@ -14,7 +14,7 @@ module Rodauth
14
14
  button 'Change Password'
15
15
  redirect
16
16
 
17
- auth_value_method :new_password_label, 'New Password'
17
+ translatable_method :new_password_label, 'New Password'
18
18
  auth_value_method :new_password_param, 'new-password'
19
19
 
20
20
  auth_value_methods(
@@ -26,11 +26,11 @@ module Rodauth
26
26
  require_account_session
27
27
  before_confirm_password_route
28
28
 
29
- request.get do
29
+ r.get do
30
30
  confirm_password_view
31
31
  end
32
32
 
33
- request.post do
33
+ r.post do
34
34
  if password_match?(param(password_param))
35
35
  transaction do
36
36
  before_confirm_password
@@ -51,11 +51,13 @@ module Rodauth
51
51
  return true if salts.empty?
52
52
 
53
53
  salts.any? do |hash_id, salt|
54
- db.get(Sequel.function(function_name(:rodauth_previous_password_hash_match), hash_id, BCrypt::Engine.hash_secret(password, salt)))
54
+ database_function_password_match?(:rodauth_previous_password_hash_match, hash_id, password, salt)
55
55
  end
56
56
  else
57
57
  # :nocov:
58
- previous_password_ds.select_map(previous_password_hash_column).any?{|hash| BCrypt::Password.new(hash) == password}
58
+ previous_password_ds.select_map(previous_password_hash_column).any? do |hash|
59
+ password_hash_match?(hash, password)
60
+ end
59
61
  # :nocov:
60
62
  end
61
63
 
@@ -47,7 +47,7 @@ module Rodauth
47
47
  s = {}
48
48
  if jwt_token
49
49
  unless session_data = jwt_payload
50
- json_response[json_response_error_key] = invalid_jwt_format_error_message
50
+ json_response[json_response_error_key] ||= invalid_jwt_format_error_message
51
51
  response.status ||= json_response_error_status
52
52
  response['Content-Type'] ||= json_response_content_type
53
53
  response.write(_json_response_body(json_response))
@@ -73,7 +73,10 @@ module Rodauth
73
73
 
74
74
  def clear_session
75
75
  super
76
- set_jwt if use_jwt?
76
+ if use_jwt?
77
+ session.clear
78
+ set_jwt
79
+ end
77
80
  end
78
81
 
79
82
  def set_field_error(field, message)
@@ -229,10 +232,18 @@ module Rodauth
229
232
  end
230
233
  end
231
234
 
235
+ def _jwt_decode_opts
236
+ jwt_decode_opts
237
+ end
238
+
232
239
  def jwt_payload
233
240
  return @jwt_payload if defined?(@jwt_payload)
234
- @jwt_payload = JWT.decode(jwt_token, jwt_secret, true, jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
235
- rescue JWT::DecodeError
241
+ @jwt_payload = JWT.decode(jwt_token, jwt_secret, true, _jwt_decode_opts.merge(:algorithm=>jwt_algorithm))[0]
242
+ rescue JWT::DecodeError => e
243
+ rescue_jwt_payload(e)
244
+ end
245
+
246
+ def rescue_jwt_payload(_)
236
247
  @jwt_payload = false
237
248
  end
238
249
 
@@ -7,6 +7,9 @@ module Rodauth
7
7
  after 'refresh_token'
8
8
  before 'refresh_token'
9
9
 
10
+ auth_value_method :allow_refresh_with_expired_jwt_access_token?, false
11
+ session_key :jwt_refresh_token_data_session_key, :jwt_refresh_token_data
12
+ session_key :jwt_refresh_token_hmac_session_key, :jwt_refresh_token_hash
10
13
  auth_value_method :jwt_access_token_key, 'access_token'
11
14
  auth_value_method :jwt_access_token_not_before_period, 5
12
15
  auth_value_method :jwt_access_token_period, 1800
@@ -21,12 +24,15 @@ module Rodauth
21
24
  auth_value_method :jwt_refresh_token_table, :account_jwt_refresh_keys
22
25
  translatable_method :jwt_refresh_without_access_token_message, 'no JWT access token provided during refresh'
23
26
  auth_value_method :jwt_refresh_without_access_token_status, 401
27
+ translatable_method :expired_jwt_access_token_message, "expired JWT access token"
28
+ auth_value_method :expired_jwt_access_token_status, 400
24
29
 
25
30
  auth_private_methods(
26
31
  :account_from_refresh_token
27
32
  )
28
33
 
29
34
  route do |r|
35
+ @jwt_refresh_route = true
30
36
  before_jwt_refresh_route
31
37
 
32
38
  r.post do
@@ -38,6 +44,7 @@ module Rodauth
38
44
  before_refresh_token
39
45
  formatted_token = generate_refresh_token
40
46
  remove_jwt_refresh_token_key(refresh_token)
47
+ set_jwt_refresh_token_hmac_session_key(formatted_token)
41
48
  json_response[jwt_refresh_token_key] = formatted_token
42
49
  json_response[jwt_access_token_key] = session_jwt
43
50
  after_refresh_token
@@ -58,7 +65,9 @@ module Rodauth
58
65
  # JWT login puts the access token in the header.
59
66
  # We put the refresh token in the body.
60
67
  # Note, do not put the access_token in the body here, as the access token content is not yet finalised.
61
- json_response['refresh_token'] = generate_refresh_token
68
+ token = json_response[jwt_refresh_token_key] = generate_refresh_token
69
+
70
+ set_jwt_refresh_token_hmac_session_key(token)
62
71
  end
63
72
 
64
73
  def set_jwt_token(token)
@@ -85,12 +94,33 @@ module Rodauth
85
94
 
86
95
  private
87
96
 
97
+ def rescue_jwt_payload(e)
98
+ if e.instance_of?(JWT::ExpiredSignature)
99
+ begin
100
+ # Some versions of jwt will raise JWT::ExpiredSignature even when the
101
+ # JWT is invalid for other reasons. Make sure the expiration is the
102
+ # only reason the JWT isn't valid before treating this as an expired token.
103
+ JWT.decode(jwt_token, jwt_secret, true, Hash[jwt_decode_opts].merge!(:verify_expiration=>false, :algorithm=>jwt_algorithm))[0]
104
+ rescue => e
105
+ else
106
+ json_response[json_response_error_key] = expired_jwt_access_token_message
107
+ response.status ||= expired_jwt_access_token_status
108
+ end
109
+ end
110
+
111
+ super
112
+ end
113
+
88
114
  def _account_from_refresh_token(token)
89
115
  id, token_id, key = _account_refresh_token_split(token)
90
116
 
91
- return unless key
92
- return unless actual = get_active_refresh_token(id, token_id)
93
- return unless timing_safe_eql?(key, convert_token_key(actual))
117
+ unless key &&
118
+ (id == session_value.to_s) &&
119
+ (actual = get_active_refresh_token(id, token_id)) &&
120
+ timing_safe_eql?(key, convert_token_key(actual)) &&
121
+ jwt_refresh_token_match?(key)
122
+ return
123
+ end
94
124
 
95
125
  ds = account_ds(id)
96
126
  ds = ds.where(account_status_column=>account_open_status_value) unless skip_status_checks?
@@ -107,6 +137,23 @@ module Rodauth
107
137
  [id, token_id, key]
108
138
  end
109
139
 
140
+ def _jwt_decode_opts
141
+ if allow_refresh_with_expired_jwt_access_token? && @jwt_refresh_route
142
+ Hash[super].merge!(:verify_expiration=>false)
143
+ else
144
+ super
145
+ end
146
+ end
147
+
148
+ def jwt_refresh_token_match?(key)
149
+ # We don't need to match tokens if we are requiring a valid current access token
150
+ return true unless allow_refresh_with_expired_jwt_access_token?
151
+
152
+ # If allowing with expired jwt access token, check the expired session contains
153
+ # hmac matching submitted and active refresh token.
154
+ timing_safe_eql?(compute_hmac(session[jwt_refresh_token_data_session_key].to_s + key), session[jwt_refresh_token_hmac_session_key].to_s)
155
+ end
156
+
110
157
  def get_active_refresh_token(account_id, token_id)
111
158
  jwt_refresh_token_account_ds(account_id).
112
159
  where(Sequel::CURRENT_TIMESTAMP > jwt_refresh_token_deadline_column).
@@ -130,8 +177,7 @@ module Rodauth
130
177
  end
131
178
 
132
179
  def remove_jwt_refresh_token_key(token)
133
- account_id, token = split_token(token)
134
- token_id, _ = split_token(token)
180
+ account_id, token_id, _ = _account_refresh_token_split(token)
135
181
  jwt_refresh_token_account_token_ds(account_id, token_id).delete
136
182
  end
137
183
 
@@ -146,6 +192,15 @@ module Rodauth
146
192
  hash
147
193
  end
148
194
 
195
+ def set_jwt_refresh_token_hmac_session_key(token)
196
+ if allow_refresh_with_expired_jwt_access_token?
197
+ key = _account_refresh_token_split(token).last
198
+ data = random_key
199
+ set_session_value(jwt_refresh_token_data_session_key, data)
200
+ set_session_value(jwt_refresh_token_hmac_session_key, compute_hmac(data + key))
201
+ end
202
+ end
203
+
149
204
  def before_logout
150
205
  if token = param_or_nil(jwt_refresh_token_key_param)
151
206
  if token == 'all'
@@ -154,9 +209,7 @@ module Rodauth
154
209
  id, token_id, key = _account_refresh_token_split(token)
155
210
 
156
211
  if id && token_id && key && (actual = get_active_refresh_token(session_value, token_id)) && timing_safe_eql?(key, convert_token_key(actual))
157
- jwt_refresh_token_account_ds(id).
158
- where(jwt_refresh_token_id_column=>token_id).
159
- delete
212
+ jwt_refresh_token_account_token_ds(id, token_id).delete
160
213
  end
161
214
  end
162
215
  end
@@ -23,6 +23,8 @@ module Rodauth
23
23
  auth_cached_method :login_form_footer_links
24
24
  auth_cached_method :login_form_footer
25
25
 
26
+ auth_value_methods :login_return_to_requested_location_path
27
+
26
28
  route do |r|
27
29
  check_already_logged_in
28
30
  before_login_route
@@ -85,12 +87,16 @@ module Rodauth
85
87
  end
86
88
 
87
89
  def login_required
88
- if login_return_to_requested_location?
89
- set_session_value(login_redirect_session_key, request.fullpath)
90
+ if login_return_to_requested_location? && (path = login_return_to_requested_location_path)
91
+ set_session_value(login_redirect_session_key, path)
90
92
  end
91
93
  super
92
94
  end
93
95
 
96
+ def login_return_to_requested_location_path
97
+ request.fullpath if request.get?
98
+ end
99
+
94
100
  def after_login_entered_during_multi_phase_login
95
101
  set_notice_now_flash need_password_notice_flash
96
102
  if multi_phase_login_forms.length == 1 && (meth = multi_phase_login_forms[0][2])
@@ -76,9 +76,7 @@ module Rodauth
76
76
  )
77
77
 
78
78
  auth_methods(
79
- :otp,
80
79
  :otp_exists?,
81
- :otp_key,
82
80
  :otp_last_use,
83
81
  :otp_locked_out?,
84
82
  :otp_new_secret,
@@ -0,0 +1,45 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Rodauth
4
+ Feature.define(:password_pepper, :PasswordPepper) do
5
+ depends :login_password_requirements_base
6
+
7
+ auth_value_method :password_pepper, nil
8
+ auth_value_method :previous_password_peppers, [""]
9
+ auth_value_method :password_pepper_update?, true
10
+
11
+ def password_match?(password)
12
+ if (result = super) && @previous_pepper_matched && password_pepper_update?
13
+ set_password(password)
14
+ end
15
+
16
+ result
17
+ end
18
+
19
+ private
20
+
21
+ def password_hash(password)
22
+ super(password + password_pepper.to_s)
23
+ end
24
+
25
+ def password_hash_match?(hash, password)
26
+ return super if password_pepper.nil?
27
+
28
+ return true if super(hash, password + password_pepper)
29
+
30
+ @previous_pepper_matched = previous_password_peppers.any? do |pepper|
31
+ super(hash, password + pepper)
32
+ end
33
+ end
34
+
35
+ def database_function_password_match?(name, hash_id, password, salt)
36
+ return super if password_pepper.nil?
37
+
38
+ return true if super(name, hash_id, password + password_pepper, salt)
39
+
40
+ @previous_pepper_matched = previous_password_peppers.any? do |pepper|
41
+ super(name, hash_id, password + pepper, salt)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -34,6 +34,7 @@ module Rodauth
34
34
  auth_value_method :add_recovery_codes_param, 'add'
35
35
  translatable_method :add_recovery_codes_heading, '<h2>Add Additional Recovery Codes</h2>'
36
36
  auth_value_method :auto_add_recovery_codes?, false
37
+ auth_value_method :auto_remove_recovery_codes?, false
37
38
  translatable_method :invalid_recovery_code_message, "Invalid recovery code"
38
39
  auth_value_method :recovery_codes_limit, 16
39
40
  auth_value_method :recovery_codes_column, :code
@@ -56,7 +57,6 @@ module Rodauth
56
57
  :can_add_recovery_codes?,
57
58
  :new_recovery_code,
58
59
  :recovery_code_match?,
59
- :recovery_codes
60
60
  )
61
61
 
62
62
  route(:recovery_auth) do |r|
@@ -213,6 +213,21 @@ module Rodauth
213
213
  super
214
214
  end
215
215
 
216
+ def after_otp_disable
217
+ super if defined?(super)
218
+ auto_remove_recovery_codes
219
+ end
220
+
221
+ def after_sms_disable
222
+ super if defined?(super)
223
+ auto_remove_recovery_codes
224
+ end
225
+
226
+ def after_webauthn_remove
227
+ super if defined?(super)
228
+ auto_remove_recovery_codes
229
+ end
230
+
216
231
  def new_recovery_code
217
232
  random_key
218
233
  end
@@ -227,6 +242,12 @@ module Rodauth
227
242
  end
228
243
  end
229
244
 
245
+ def auto_remove_recovery_codes
246
+ if auto_remove_recovery_codes? && (%w'totp webauthn sms_code' & possible_authentication_methods).empty?
247
+ recovery_codes_remove
248
+ end
249
+ end
250
+
230
251
  def _recovery_codes
231
252
  recovery_codes_ds.select_map(recovery_codes_column)
232
253
  end
@@ -58,7 +58,9 @@ module Rodauth
58
58
  if [remember_remember_param_value, remember_forget_param_value, remember_disable_param_value].include?(remember)
59
59
  transaction do
60
60
  before_remember
61
+ # :nocov:
61
62
  case remember
63
+ # :nocov:
62
64
  when remember_remember_param_value
63
65
  remember_login
64
66
  when remember_forget_param_value
@@ -130,11 +132,15 @@ module Rodauth
130
132
  opts = Hash[remember_cookie_options]
131
133
  opts[:value] = "#{account_id}_#{convert_token_key(remember_key_value)}"
132
134
  opts[:expires] = convert_timestamp(active_remember_key_ds.get(remember_deadline_column))
135
+ opts[:path] = "/" unless opts.key?(:path)
136
+ opts[:httponly] = true unless opts.key?(:httponly)
133
137
  ::Rack::Utils.set_cookie_header!(response.headers, remember_cookie_key, opts)
134
138
  end
135
139
 
136
140
  def forget_login
137
- ::Rack::Utils.delete_cookie_header!(response.headers, remember_cookie_key, remember_cookie_options)
141
+ opts = Hash[remember_cookie_options]
142
+ opts[:path] = "/" unless opts.key?(:path)
143
+ ::Rack::Utils.delete_cookie_header!(response.headers, remember_cookie_key, opts)
138
144
  end
139
145
 
140
146
  def get_remember_key
@@ -47,7 +47,6 @@ module Rodauth
47
47
  :get_verify_account_key,
48
48
  :get_verify_account_email_last_sent,
49
49
  :remove_verify_account_key,
50
- :resend_verify_account_view,
51
50
  :send_verify_account_email,
52
51
  :set_verify_account_email_last_sent,
53
52
  :verify_account,
@@ -245,6 +244,12 @@ module Rodauth
245
244
  end
246
245
  end
247
246
 
247
+ def setup_account_verification
248
+ generate_verify_account_key_value
249
+ create_verify_account_key
250
+ send_verify_account_email
251
+ end
252
+
248
253
  private
249
254
 
250
255
  def _login_form_footer_links
@@ -276,12 +281,6 @@ module Rodauth
276
281
  super
277
282
  end
278
283
 
279
- def setup_account_verification
280
- generate_verify_account_key_value
281
- create_verify_account_key
282
- send_verify_account_email
283
- end
284
-
285
284
  def verify_account_check_already_logged_in
286
285
  check_already_logged_in
287
286
  end
@@ -8,6 +8,7 @@ module Rodauth
8
8
  error_flash "Unable to change login as there is already an account with the new login", 'verify_login_change_duplicate_account'
9
9
  error_flash "There was an error verifying your login change: invalid verify login change key", 'no_matching_verify_login_change_key'
10
10
  notice_flash "Your login change has been verified"
11
+ notice_flash "An email has been sent to you with a link to verify your login change", 'change_login_needs_verification'
11
12
  loaded_templates %w'verify-login-change verify-login-change-email'
12
13
  view 'verify-login-change', 'Verify Login Change'
13
14
  additional_form_tags
@@ -131,7 +132,7 @@ module Rodauth
131
132
  end
132
133
 
133
134
  def change_login_notice_flash
134
- "An email has been sent to you with a link to verify your login change"
135
+ change_login_needs_verification_notice_flash
135
136
  end
136
137
 
137
138
  def verify_login_change_old_login
@@ -6,7 +6,7 @@ module Rodauth
6
6
  MAJOR = 2
7
7
 
8
8
  # The minor version of Rodauth, updated for new feature releases of Rodauth.
9
- MINOR = 3
9
+ MINOR = 8
10
10
 
11
11
  # The patch version of Rodauth, updated only for bug fixes from the last
12
12
  # feature release.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-21 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -237,28 +237,33 @@ extra_rdoc_files:
237
237
  - README.rdoc
238
238
  - CHANGELOG
239
239
  - MIT-LICENSE
240
- - doc/change_password_notify.rdoc
241
240
  - doc/account_expiration.rdoc
241
+ - doc/active_sessions.rdoc
242
+ - doc/audit_logging.rdoc
242
243
  - doc/base.rdoc
243
244
  - doc/change_login.rdoc
244
245
  - doc/change_password.rdoc
245
- - doc/confirm_password.rdoc
246
+ - doc/change_password_notify.rdoc
246
247
  - doc/close_account.rdoc
247
- - doc/http_basic_auth.rdoc
248
+ - doc/confirm_password.rdoc
248
249
  - doc/create_account.rdoc
249
- - doc/email_base.rdoc
250
250
  - doc/disallow_common_passwords.rdoc
251
251
  - doc/disallow_password_reuse.rdoc
252
- - doc/password_complexity.rdoc
252
+ - doc/email_auth.rdoc
253
+ - doc/email_base.rdoc
254
+ - doc/http_basic_auth.rdoc
253
255
  - doc/jwt.rdoc
256
+ - doc/jwt_cors.rdoc
257
+ - doc/jwt_refresh.rdoc
254
258
  - doc/lockout.rdoc
255
259
  - doc/login.rdoc
260
+ - doc/login_password_requirements_base.rdoc
256
261
  - doc/logout.rdoc
257
262
  - doc/otp.rdoc
258
- - doc/login_password_requirements_base.rdoc
259
- - doc/jwt_cors.rdoc
263
+ - doc/password_complexity.rdoc
260
264
  - doc/password_expiration.rdoc
261
265
  - doc/password_grace_period.rdoc
266
+ - doc/password_pepper.rdoc
262
267
  - doc/recovery_codes.rdoc
263
268
  - doc/remember.rdoc
264
269
  - doc/reset_password.rdoc
@@ -268,16 +273,11 @@ extra_rdoc_files:
268
273
  - doc/two_factor_base.rdoc
269
274
  - doc/update_password_hash.rdoc
270
275
  - doc/verify_account.rdoc
271
- - doc/email_auth.rdoc
272
- - doc/jwt_refresh.rdoc
273
276
  - doc/verify_account_grace_period.rdoc
274
277
  - doc/verify_login_change.rdoc
275
278
  - doc/webauthn.rdoc
276
279
  - doc/webauthn_login.rdoc
277
280
  - doc/webauthn_verify_account.rdoc
278
- - doc/active_sessions.rdoc
279
- - doc/audit_logging.rdoc
280
- - doc/release_notes/1.17.0.txt
281
281
  - doc/release_notes/1.0.0.txt
282
282
  - doc/release_notes/1.1.0.txt
283
283
  - doc/release_notes/1.10.0.txt
@@ -287,7 +287,14 @@ extra_rdoc_files:
287
287
  - doc/release_notes/1.14.0.txt
288
288
  - doc/release_notes/1.15.0.txt
289
289
  - doc/release_notes/1.16.0.txt
290
+ - doc/release_notes/1.17.0.txt
291
+ - doc/release_notes/1.18.0.txt
292
+ - doc/release_notes/1.19.0.txt
290
293
  - doc/release_notes/1.2.0.txt
294
+ - doc/release_notes/1.20.0.txt
295
+ - doc/release_notes/1.21.0.txt
296
+ - doc/release_notes/1.22.0.txt
297
+ - doc/release_notes/1.23.0.txt
291
298
  - doc/release_notes/1.3.0.txt
292
299
  - doc/release_notes/1.4.0.txt
293
300
  - doc/release_notes/1.5.0.txt
@@ -295,16 +302,15 @@ extra_rdoc_files:
295
302
  - doc/release_notes/1.7.0.txt
296
303
  - doc/release_notes/1.8.0.txt
297
304
  - doc/release_notes/1.9.0.txt
298
- - doc/release_notes/1.18.0.txt
299
- - doc/release_notes/1.19.0.txt
300
- - doc/release_notes/1.20.0.txt
301
- - doc/release_notes/1.21.0.txt
302
- - doc/release_notes/1.22.0.txt
303
- - doc/release_notes/1.23.0.txt
304
305
  - doc/release_notes/2.0.0.txt
305
306
  - doc/release_notes/2.1.0.txt
306
307
  - doc/release_notes/2.2.0.txt
307
308
  - doc/release_notes/2.3.0.txt
309
+ - doc/release_notes/2.4.0.txt
310
+ - doc/release_notes/2.5.0.txt
311
+ - doc/release_notes/2.6.0.txt
312
+ - doc/release_notes/2.7.0.txt
313
+ - doc/release_notes/2.8.0.txt
308
314
  files:
309
315
  - CHANGELOG
310
316
  - MIT-LICENSE
@@ -357,6 +363,7 @@ files:
357
363
  - doc/password_complexity.rdoc
358
364
  - doc/password_expiration.rdoc
359
365
  - doc/password_grace_period.rdoc
366
+ - doc/password_pepper.rdoc
360
367
  - doc/recovery_codes.rdoc
361
368
  - doc/release_notes/1.0.0.txt
362
369
  - doc/release_notes/1.1.0.txt
@@ -386,6 +393,11 @@ files:
386
393
  - doc/release_notes/2.1.0.txt
387
394
  - doc/release_notes/2.2.0.txt
388
395
  - doc/release_notes/2.3.0.txt
396
+ - doc/release_notes/2.4.0.txt
397
+ - doc/release_notes/2.5.0.txt
398
+ - doc/release_notes/2.6.0.txt
399
+ - doc/release_notes/2.7.0.txt
400
+ - doc/release_notes/2.8.0.txt
389
401
  - doc/remember.rdoc
390
402
  - doc/reset_password.rdoc
391
403
  - doc/session_expiration.rdoc
@@ -429,6 +441,7 @@ files:
429
441
  - lib/rodauth/features/password_complexity.rb
430
442
  - lib/rodauth/features/password_expiration.rb
431
443
  - lib/rodauth/features/password_grace_period.rb
444
+ - lib/rodauth/features/password_pepper.rb
432
445
  - lib/rodauth/features/recovery_codes.rb
433
446
  - lib/rodauth/features/remember.rb
434
447
  - lib/rodauth/features/reset_password.rb
@@ -528,7 +541,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
528
541
  - !ruby/object:Gem::Version
529
542
  version: '0'
530
543
  requirements: []
531
- rubygems_version: 3.1.2
544
+ rubygems_version: 3.2.3
532
545
  signing_key:
533
546
  specification_version: 4
534
547
  summary: Authentication and Account Management Framework for Rack Applications