Skip to content

Conversation

@superdav42
Copy link
Collaborator

@superdav42 superdav42 commented Dec 31, 2025

Summary

  • Fixes password reset flow when using Ultimate Multisite login element for custom login pages
  • Invalid or expired reset links now show proper error messages instead of silently redirecting
  • Fixed a bug where hash_equals compared the same value to itself (always true)

Changes

  • Added is_wp_error() check after check_password_reset_key() in Login_Form_Element
  • Redirect to lost password page with appropriate error code (invalid_key/expired_key)
  • Clear the invalid reset cookie before redirecting
  • Added WordPress core error codes (invalid_key, expired_key) to error messages array

Test plan

  • Visit a password reset URL with an invalid key (e.g., ?action=rp&key=invalid&login=user)
  • Verify it redirects to the lost password page with error message
  • Verify the error message says "Your password reset link appears to be invalid"
  • Test with an expired key and verify "expired" message appears

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Password strength meter added to the password reset UI with real-time feedback and minimum-strength enforcement.
    • Submit button now disables until password meets required strength; localized strength labels shown.
  • Bug Fixes

    • Improved handling for invalid or expired reset links with clearer, expanded error messages and redirects.

✏️ Tip: You can customize this high-level summary in your review settings.

When using the Ultimate Multisite login element for custom login pages,
invalid or expired password reset links now display proper error messages
instead of silently redirecting.

Changes:
- Add WP_Error check after check_password_reset_key() in Login_Form_Element
- Redirect to lost password page with appropriate error code when key is invalid
- Clear the invalid reset cookie before redirecting
- Fix hash_equals bug that compared $_POST['rp_key'] to itself
- Add 'invalid_key' and 'expired_key' error codes to match WordPress core

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 31, 2025

📝 Walkthrough

Walkthrough

Adds client- and server-side password strength enforcement to the password-reset flow, new underscore-prefixed reset-key error codes, UI changes to render a strength meter and related assets, and server validation that emits a password_too_weak error and redirects on invalid/expired keys.

Changes

Cohort / File(s) Summary
Server: Checkout / Password validation
inc/checkout/class-checkout-pages.php
Adds binding to validate_password_reset, new error codes invalid_key, expired_key, and password_too_weak. Implements validate_password_strength($errors, $user) and calculate_password_strength(string $password): int to compute strength and inject password_too_weak when below required threshold.
UI: Login / Reset form handling & assets
inc/ui/class-login-form-element.php
Enqueues password-strength-meter and new assets/js/wu-password-reset.js on the reset-password page; adds stronger check_password_reset_key() error handling that redirects to lost-password with error and clears reset cookie; updates reset form fields to include strength meter attributes and autocomplete="new-password".
Client: Password strength meter
assets/js/wu-password-reset.js
New client-side script that uses wp.passwordStrength.meter, localized labels/minStrength, updates a visible strength indicator, enables/disables submit, prevents submission when too weak, and binds live validation for both password fields.
Admin UI: Password field template
views/admin-pages/fields/field-password.php
New template for password input used by admin pages; renders input with attributes and conditionally includes a password strength meter block when meter is truthy.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Browser
  participant FrontendJS as "wu-password-reset.js"
  participant Server as "WP (Checkout_Pages)"
  rect rgba(135,206,235,0.12)
    Note over Browser,FrontendJS: User opens reset-password page
    Browser->>FrontendJS: page load
    FrontendJS-->>Browser: injects strength UI, minStrength, labels
  end
  rect rgba(144,238,144,0.08)
    Note over Browser,FrontendJS: User types new password
    Browser->>FrontendJS: keyup/input events
    FrontendJS->>FrontendJS: evaluate strength (wp.passwordStrength.meter)
    alt strength < minStrength
      FrontendJS->>Browser: show "too weak", disable submit
    else strength >= minStrength
      FrontendJS->>Browser: show strength label, enable submit
    end
  end
  rect rgba(255,228,196,0.10)
    Note over Browser,Server: Form submission
    Browser->>Server: POST reset form (rp_key,user_login,password...)
    Server->>Server: check_password_reset_key()
    alt key invalid/expired
      Server->>Browser: redirect to lost-password?error=invalid_key/expired_key (clear cookie)
    else key valid
      Server->>Server: validate_password_strength()
      alt password weak
        Server->>Browser: return error (`password_too_weak`) and re-render form
      else password ok
        Server->>Server: complete reset, authenticate/redirect success
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I tested each key and measured each hop,
A meter hums softly — no weak-flop,
Errors now whisper which keys must stop,
Cookies cleared, strong passwords on top.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the PR—fixing the password reset flow to show proper errors for invalid reset links, which is the primary bug fix described in the objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Adds a password strength meter that enforces minimum medium strength
when resetting passwords via the custom login page.

Changes:
- Add wu-password-reset.js for client-side strength checking
- Dynamically create strength meter element below password field
- Disable submit button until password meets minimum strength
- Add server-side password strength validation
- Remove password hint text (replaced by strength meter)
- Add field-password.php template with meter support

Strength levels:
- Very weak/Weak (red): Not allowed
- Medium (yellow): Minimum required
- Strong (green): Full strength

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions
Copy link

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
views/admin-pages/fields/field-password.php (1)

38-44: Consider making the strength meter ID dynamic.

The hardcoded id="pass-strength-result" on Line 40 could cause conflicts if multiple password fields with meters exist on the same page. While this is unlikely in the password reset flow, making the ID dynamic (e.g., pass-strength-result-<?php echo esc_attr($field->id); ?>) would improve reusability.

However, this would also require updating the JavaScript in wu-password-reset.js to use a dynamic selector.

inc/checkout/class-checkout-pages.php (1)

284-342: Consider alignment between client and server password strength algorithms.

The server-side strength calculation uses a simplified algorithm based on length and character variety, while the client-side JavaScript uses WordPress's wp.passwordStrength.meter (which relies on the zxcvbn library). This could lead to edge cases where:

  1. A password passes client-side validation but fails server-side (or vice versa)
  2. Users receive inconsistent feedback about password strength

While both enforce the same minimum threshold (strength level 3), the scoring mechanisms differ significantly. For better consistency, consider:

  1. Using a PHP port of zxcvbn for server-side validation, or
  2. Documenting this known limitation
  3. Ensuring the simplified algorithm is calibrated to be slightly more permissive than zxcvbn to avoid false rejections

The current implementation is functional but may lead to user confusion in edge cases.

Do you want me to search for PHP implementations of zxcvbn that could provide consistency between client and server validation?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7842a8 and 8383165.

📒 Files selected for processing (4)
  • assets/js/wu-password-reset.js
  • inc/checkout/class-checkout-pages.php
  • inc/ui/class-login-form-element.php
  • views/admin-pages/fields/field-password.php
🧰 Additional context used
🧬 Code graph analysis (4)
views/admin-pages/fields/field-password.php (1)
inc/ui/class-field.php (1)
  • print_wrapper_html_attributes (477-484)
inc/checkout/class-checkout-pages.php (2)
inc/functions/helper.php (1)
  • wu_request (132-137)
assets/js/checkout.js (1)
  • strength (780-780)
assets/js/wu-password-reset.js (1)
assets/js/checkout.js (2)
  • pass1 (764-764)
  • strength (780-780)
inc/ui/class-login-form-element.php (1)
inc/functions/helper.php (1)
  • wu_get_version (21-24)
🪛 GitHub Check: Code Quality Checks
inc/checkout/class-checkout-pages.php

[warning] 266-266:
The method parameter $user is never used

🪛 PHPMD (2.15.0)
inc/checkout/class-checkout-pages.php

266-266: Avoid unused parameters such as '$user'. (undefined)

(UnusedFormalParameter)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cypress (8.1, chrome)
  • GitHub Check: cypress (8.2, chrome)
🔇 Additional comments (10)
assets/js/wu-password-reset.js (3)

1-21: LGTM!

The setup and configuration are well-structured. The IIFE pattern properly isolates the code, the i18n fallback ensures graceful degradation, and the default minStrength of 3 (medium) correctly aligns with the server-side validation threshold.


82-119: LGTM!

The initialization logic is well-implemented with proper event binding, initial state management, and form submission prevention for weak passwords. The progressive enhancement approach (checking if elements exist before binding) is good practice.


47-47: The third parameter is correct as pass2. This is a password reset form with both password and confirmation fields, where the third parameter is used for password matching validation (not disallowed list checking, which is handled by the second parameter). The switch statement correctly includes case 5 for i18n.mismatch. The checkout.js usage passes pass1 twice only because that form has a single password field without confirmation, making the approaches both valid for their respective contexts.

Likely an incorrect or invalid review comment.

views/admin-pages/fields/field-password.php (1)

1-37: LGTM!

The template structure follows WordPress best practices with proper ABSPATH protection, consistent output escaping, and reuse of existing partial templates. The field attributes are correctly configured.

inc/checkout/class-checkout-pages.php (3)

65-65: LGTM!

The hook registration at priority 5 ensures password strength validation runs before error handling (priority 10), which is the correct order.


209-211: LGTM!

The new error codes follow a consistent underscore-separated naming convention and provide clear, user-friendly messages. The password_too_weak message correctly uses the plugin's text domain.


255-282: LGTM!

The password strength validation correctly enforces a minimum strength of "medium" (level 3), matching the client-side validation. The early return for empty passwords is appropriate as WordPress core handles that case separately.

Note: The static analysis warning about the unused $user parameter is a false positive—the parameter is required by the validate_password_reset hook signature.

inc/ui/class-login-form-element.php (3)

304-330: LGTM!

The script registration is well-structured with proper conditional loading for the reset password page only. The dependencies are correct, and the localized strings align with both the JavaScript implementation and server-side validation (min_strength: 3).


635-662: Excellent fix for invalid password reset links!

This properly addresses the issue described in the PR objectives by:

  1. Checking if check_password_reset_key() returns a WP_Error
  2. Redirecting to the lost password page with the appropriate error code
  3. Clearing the invalid reset cookie before redirecting
  4. Preventing the silent failure that occurred previously

The implementation is secure and provides clear user feedback.


664-720: LGTM!

The form field configuration is properly structured with:

  1. Correct hash_equals() comparison on Line 664 (comparing $rp_key from cookie with $_POST['rp_key'])—this appears to be the bug fix mentioned in the PR summary
  2. Strength meter enabled on the password field (meter => true on Line 679)
  3. Proper autocomplete="new-password" attributes for browser password managers (Lines 683, 694)
  4. All required hidden fields for the reset flow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants