Skip to content

Fix password strength not enforced on checkout (minStrength hardcoded)#342

Merged
superdav42 merged 10 commits intomainfrom
fix/password-strength-enforcement
Feb 17, 2026
Merged

Fix password strength not enforced on checkout (minStrength hardcoded)#342
superdav42 merged 10 commits intomainfrom
fix/password-strength-enforcement

Conversation

@superdav42
Copy link
Collaborator

@superdav42 superdav42 commented Feb 15, 2026

checkout.js hardcoded minStrength: 3 when creating the password checker, overriding the PHP-configured minimum strength setting. This meant the "strong" (score 4) and "super_strong" settings were effectively treated as "medium" (score 3), allowing weaker passwords through.

Removed the hardcoded override so the checker reads min_strength from wu_password_strength_settings (set by PHP). Also added re-initialization in the Vue updated() hook so the password checker activates when the field appears after initial mount in multi-step checkouts.

Summary by CodeRabbit

  • New Features

    • Password strength checker now initializes for dynamically rendered password fields; strength config updated.
    • Configurable maximum for "Pay What You Want" amounts with user-facing validation.
    • Expanded translations and UI/help strings across setup, Stripe, templates, and installer flows.
  • Security

    • Payment status polling now requires authentication and nonce verification.
    • Stripe sandbox default adjusted.
  • Bug Fixes

    • Billing country remains required when payment not collected; billing address visibility handled reliably.
  • Tests

    • New E2E tests and fixtures for password strength enforcement, SSO cross-domain, and related flows.

checkout.js hardcoded minStrength: 3 when creating the password checker,
overriding the PHP-configured minimum strength setting. This meant the
"strong" (score 4) and "super_strong" settings were effectively treated
as "medium" (score 3), allowing weaker passwords through.

Removed the hardcoded override so the checker reads min_strength from
wu_password_strength_settings (set by PHP). Also added re-initialization
in the Vue updated() hook so the password checker activates when the
field appears after initial mount in multi-step checkouts.

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

coderabbitai bot commented Feb 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removed explicit minStrength from the frontend password-strength initializer and added late-mount re-initialization; added E2E fixtures and tests for password strength; added libsodium fallback and OpenSSL-first logic for credential encryption; sanitised PWYW inputs with maximum enforcement; adjusted billing rendering/validation and tightened AJAX/Stripe security; plus assorted tests, translations, and minor formatting changes.

Changes

Cohort / File(s) Summary
Password Strength UI & Tests
assets/js/checkout.js, tests/e2e/cypress/fixtures/set-password-strength.php, tests/e2e/cypress/integration/050-password-strength-enforcement.spec.js, tests/e2e/cypress/integration/000-setup.spec.js
Removed minStrength from WU_PasswordStrength init and added re-init when password field mounts late; added WP‑CLI fixture to set minimum password strength and new Cypress E2E tests plus a setup reset test.
Credential Encryption
inc/helpers/class-credential-store.php
Added SODIUM_PREFIX, prefer OpenSSL (IV) then fall back to libsodium; added sodium decryption and key derivation helpers; log and return empty on missing crypto backends.
PWYW & Cart Sanitization
inc/checkout/class-cart.php
Sanitize PWYW amounts and recurring inputs via new private helpers; enforce configurable maximum via wu_pwyw_maximum_amount and return pwyw-above-maximum on exceed.
Checkout Rendering & Billing Validation
inc/checkout/class-checkout.php, inc/checkout/signup-fields/class-signup-field-billing-address.php, assets/css/checkout.min.css
Preserve billing_country validation when payment isn't collected; switch billing_country rendering to v-if and other fields to v-show; minor CSS formatting changes.
Login Redirects & Domain Mapping
inc/checkout/class-checkout-pages.php, inc/domain-mapping/class-primary-domain.php
Preserve and reattach raw query strings for login redirects; stop parsing/propagating query args for domain-mapped redirects.
AJAX Auth & Stripe
inc/managers/class-gateway-manager.php, inc/gateways/class-base-stripe-gateway.php, inc/gateways/class-stripe-gateway.php
Restrict payment-status polling to authenticated users and verify nonce; set Stripe sandbox default to true; tighten OAuth callback with capability check, stricter state validation, and immediate state deletion.
Beta Update Flow & Duplication Guard
inc/class-wp-ultimo.php, inc/duplication/data.php
Add re-entrancy guard for beta update redirect and host whitelist for package injection; prevent double-substitution in MUCD_Data::replace.
E2E Tests & Support Changes
tests/e2e/cypress/integration/.../010-manual-checkout-flow.spec.js, .../020-free-trial-flow.spec.js, .../030-stripe-checkout-flow.spec.js, tests/e2e/cypress/support/commands/login.js, tests/e2e/cypress/fixtures/setup-sso-test.php, tests/e2e/cypress/integration/060-sso-cross-domain.spec.js
Updated test passwords, simplified billing interactions to wait-for-visibility, broadened login support to /wp-login.php or /login, added SSO setup fixture and cross-domain SSO tests.
Translations & Minor Formatting
lang/ultimate-multisite.pot, inc/stuff.php
Large POT refresh and many new translation strings (version bump); minor PHP formatting changes.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant Vue as Vue Component
    participant Strength as PasswordChecker
    participant Server as WP-CLI/Server

    User->>Browser: open registration page
    Browser->>Vue: mount component
    alt password field present at mount
        Vue->>Strength: initialize checker (options without minStrength)
    else password field appears after mount
        Browser->>Vue: detect `#field-password` added
        Vue->>Strength: initialize checker (re-init on late mount)
    end
    User->>Vue: type password
    Vue->>Strength: compute score
    Strength->>Vue: return score & validity
    Vue->>Browser: update meter & validation UI
    Note right of Server: (E2E) WP‑CLI fixture sets min strength for tests
    Browser->>Server: (E2E) assert strength enforcement
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #300 — Changes to Stripe OAuth and sandbox/default behavior in inc/gateways/class-base-stripe-gateway.php.
  • #323 — Related edits to password-strength initialization and frontend integration.
  • #333 — Related changes to credential storage encryption/decryption (OpenSSL/libsodium handling).

Poem

🐰 I nudged a flag with gentle paws,
Rebooted checks for late-night draws,
Tests hopped in to guard the gate,
Secrets wrapped where salts create,
I thumped the log — all set, hooray!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (25 files):

⚔️ assets/css/checkout.min.css (content)
⚔️ assets/js/checkout.js (content)
⚔️ assets/js/checkout.min.js (content)
⚔️ assets/js/gateways/stripe.min.js (content)
⚔️ inc/admin-pages/class-settings-admin-page.php (content)
⚔️ inc/admin-pages/class-setup-wizard-admin-page.php (content)
⚔️ inc/apis/class-settings-endpoint.php (content)
⚔️ inc/checkout/class-cart.php (content)
⚔️ inc/checkout/class-checkout-pages.php (content)
⚔️ inc/checkout/class-checkout.php (content)
⚔️ inc/checkout/signup-fields/class-signup-field-billing-address.php (content)
⚔️ inc/class-wp-ultimo.php (content)
⚔️ inc/domain-mapping/class-primary-domain.php (content)
⚔️ inc/duplication/data.php (content)
⚔️ inc/gateways/class-base-stripe-gateway.php (content)
⚔️ inc/gateways/class-stripe-gateway.php (content)
⚔️ inc/helpers/class-credential-store.php (content)
⚔️ inc/managers/class-gateway-manager.php (content)
⚔️ inc/stuff.php (content)
⚔️ lang/ultimate-multisite.pot (content)
⚔️ tests/e2e/cypress/integration/000-setup.spec.js (content)
⚔️ tests/e2e/cypress/integration/010-manual-checkout-flow.spec.js (content)
⚔️ tests/e2e/cypress/integration/020-free-trial-flow.spec.js (content)
⚔️ tests/e2e/cypress/integration/030-stripe-checkout-flow.spec.js (content)
⚔️ tests/e2e/cypress/support/commands/login.js (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ 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 clearly and accurately describes the main change: removing the hardcoded minStrength parameter that was preventing password strength enforcement on checkout, which directly addresses the core issue of the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/password-strength-enforcement

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

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.

🧹 Nitpick comments (4)
assets/js/checkout.js (1)

1080-1083: password_strength_checker is not declared in Vue's data, so it's non-reactive.

The guard !this.password_strength_checker works fine as a plain JS property check, but since it's not in initial_data, Vue won't track it reactively. This is acceptable here because you're only using it as a one-shot initialization flag inside updated(), not in templates or computed properties.

Consider declaring password_strength_checker: null in initial_data (around Line 166) for clarity and to avoid a potential pitfall if someone later references it in a template or computed.

Suggested addition to initial_data
 			valid_password: true,
+			password_strength_checker: null,
 			stored_templates: {},
tests/e2e/cypress/integration/050-password-strength-enforcement.spec.js (3)

26-26: Consider replacing fixed cy.wait(2000) with a deterministic assertion.

Fixed waits are a common source of flakiness in CI (too short → fails; too long → slow). Since the password field is already confirmed visible on Line 25, you could instead wait for Vue to finish mounting, e.g., by asserting the existence of wu_checkout_form on the window:

cy.window().its('wu_checkout_form').should('exist');

Same suggestion applies to cy.wait(500) on Line 34 — you could retry-assert the strength label text instead.


5-15: Hardcoded container path is brittle but acceptable for wp-env.

The path /var/www/html/wp-content/plugins/ultimate-multisite/... is tightly coupled to the wp-env container layout. If the plugin folder is ever renamed or the Docker setup changes, this will silently break. Consider extracting it to a constant or Cypress env variable for easier maintenance, but this is low risk for a dedicated e2e setup.


32-52: Accessing __vue__ on the DOM element assumes Vue 2.

win.document.querySelector("#wu_form").__vue__ is a Vue 2 internal. This will break if the app migrates to Vue 3 (__vue_app__ + getComponentPublicInstance). Fine for now, just worth a note if a Vue upgrade is ever planned.

Address critical, high, and medium security issues found in code audit since v2.4.10:

- Add capability check and timing-safe state comparison to Stripe OAuth callback
- Require authentication and nonce for payment status polling AJAX
- Replace base64 fallback with sodium encryption in credential store
- Add pattern-based filtering for sensitive settings in REST API
- Add PWYW maximum amount validation and input sanitization on Cart
- Restore double-replacement guard in site duplication
- Keep billing_country required on free trials for tax calculation
- Revert sandbox mode default to true for Stripe gateways
- Add GitHub domain validation for beta update package URLs
- Add re-entry guard to prevent recursive HTTP filter

Fix e2e tests:
- Handle custom login page redirect in loginByForm command
- Fix billing field visibility with v-show defaulting to visible during AJAX
- Reset password strength setting in setup to prevent cross-run failures
- Use stronger test passwords that pass all strength levels

Co-Authored-By: Claude Opus 4.6 <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: 4

🤖 Fix all issues with AI agents
In `@inc/checkout/signup-fields/class-signup-field-billing-address.php`:
- Line 271: The billing_country input is hidden with v-show but still validated
by get_validation_rules() in class-checkout.php, causing required validation
when users can't edit it; update the billing_country field handling in
class-signup-field-billing-address.php (the field with
wrapper_html_attr['v-show'] and key 'billing_country') so it either uses v-if to
remove it from the DOM when order.should_collect_payment is false, or change the
visibility condition to a separate v-show that keeps billing_country visible
when order.should_collect_payment is false; ensure the change aligns with
get_validation_rules() (the required_with rule for billing_country) so the field
is either present for users to correct or excluded from validation.

In `@inc/helpers/class-credential-store.php`:
- Around line 148-174: In decrypt_sodium(), wrap the call to
sodium_crypto_secretbox_open (the block that gets $key via
self::get_sodium_key() and calls sodium_crypto_secretbox_open($encrypted,
$nonce, $key)) in a try-catch that catches SodiumException and returns an empty
string on failure to preserve the existing graceful-empty-string contract;
ensure the function still returns '' for exceptions and unchanged behavior for
successful decryptions.
- Around line 72-80: The sodium branch currently calls random_bytes(),
get_sodium_key() and sodium_crypto_secretbox() without guarding for exceptions;
wrap the whole sodium path in a try/catch that mirrors the OpenSSL fallback so
any Throwable (or specifically SodiumException and Exception) is caught and
causes the function to return an empty string rather than letting the exception
bubble; ensure you still use self::get_sodium_key(),
SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, sodium_crypto_secretbox(), and return
self::SODIUM_PREFIX . base64_encode($nonce . $encrypted) on success, but on
failure log or ignore the exception and return '' to preserve the
graceful-fallback behavior.

In `@lang/ultimate-multisite.pot`:
- Around line 4622-4623: Fix the two typos in the user-facing string: change
"scarry" to "scary" and "specially" to "especially" in the message literal used
by the Setup_Wizard_Admin_Page (the method that renders the "Starting from
scratch..." message), then update the source PHP (class Setup_Wizard_Admin_Page)
and regenerate the POT so the corrected msgid is exported.
🧹 Nitpick comments (2)
inc/class-wp-ultimo.php (1)

1023-1044: Re-entrancy guard looks correct; consider try/finally for resilience.

The static $is_redirecting flag properly prevents infinite recursion through the http_request_argspre_http_requestwp_remote_get cycle. Currently safe since wp_remote_get doesn't throw, but wrapping in try/finally would make it resilient to future changes.

♻️ Optional: use try/finally to guarantee flag reset
 				$is_redirecting = true;
-				$beta_url       = add_query_arg('beta', '1', $request_url);
-				$result         = wp_remote_get($beta_url, $args);
-				$is_redirecting = false;
-
-				return $result;
+				try {
+					$beta_url = add_query_arg('beta', '1', $request_url);
+					return wp_remote_get($beta_url, $args);
+				} finally {
+					$is_redirecting = false;
+				}

Regarding PHPMD's warning about unused $r on line 1031: this is a false positive — $r is required by WordPress's pre_http_request filter signature (3 parameters: $pre, $parsed_args, $url).

inc/helpers/class-credential-store.php (1)

187-196: get_sodium_key() is identical to get_encryption_key() — derive distinct keys per algorithm.

Using the exact same key material for two different cryptographic primitives (AES-256-CBC and XSalsa20-Poly1305) violates the key-separation principle. Derive algorithm-specific subkeys with distinct context strings, e.g. via HKDF or simply hashing with a different domain label:

Proposed fix
 	private static function get_sodium_key(): string {
 
-		return hash('sha256', wp_salt('auth'), true);
+		return hash('sha256', 'wu_sodium_secretbox:' . wp_salt('auth'), true);
 	}

⚠️ If you adopt this, any credentials already encrypted with the current sodium key will become undecryptable. Since sodium is only a fallback (used when OpenSSL is absent), this is likely safe if no production data was encrypted with libsodium yet. Verify before merging.

Comment on lines 4622 to 4623
#: inc/admin-pages/class-setup-wizard-admin-page.php:465
msgid "Starting from scratch can be scarry, specially when first starting out. In this step, you can create default content to have a starting point for your network. Everything can be customized later."
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pre-existing typo: "scarry" → "scary", "specially" → "especially".

The msgid on line 4623 contains "scarry" (should be "scary") and "specially" (should be "especially"). While this string isn't newly introduced in this PR (only the source reference was updated), it's worth fixing in the source PHP file (class-setup-wizard-admin-page.php:465) and regenerating the POT.

🤖 Prompt for AI Agents
In `@lang/ultimate-multisite.pot` around lines 4622 - 4623, Fix the two typos in
the user-facing string: change "scarry" to "scary" and "specially" to
"especially" in the message literal used by the Setup_Wizard_Admin_Page (the
method that renders the "Starting from scratch..." message), then update the
source PHP (class Setup_Wizard_Admin_Page) and regenerate the POT so the
corrected msgid is exported.

…ndling

- Fix "scarry" → "scary" and "specially" → "especially" in setup wizard
- Use v-if instead of v-show for billing_country so it's removed from the
  DOM when payment isn't required, preventing required_with validation from
  firing on a hidden field
- Wrap sodium_crypto_secretbox_open in try-catch for SodiumException to
  preserve the graceful empty-string return contract

Co-Authored-By: Claude Opus 4.6 <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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
inc/checkout/signup-fields/class-signup-field-billing-address.php (1)

279-296: ⚠️ Potential issue | 🟠 Major

v-if for billing_country is silently overwritten when $zip_only is true.

When $zip_only is true, line 295 unconditionally sets wrapper_html_attr['v-if'], replacing the payment-collection guard set on line 280. This means billing_country will remain in the DOM (and be submitted/validated) when payment is not required, as long as the gateway isn't Stripe — exactly the bug the new v-if on line 280 was meant to fix.

For non-billing_country fields the two directives coexist (v-show + v-if), but for billing_country both are v-if, so the second write wins.

Proposed fix — combine both conditions for billing_country
 			if ($zip_only) {
-				$field['wrapper_html_attr']['v-if'] = "!(gateway && gateway.startsWith('stripe'))";
+				$stripe_condition = "!(gateway && gateway.startsWith('stripe'))";
+
+				if ('billing_country' === $field_key) {
+					// Merge the payment-collection guard with the Stripe guard
+					$field['wrapper_html_attr']['v-if'] = "(order === false || order.should_collect_payment) && {$stripe_condition}";
+				} else {
+					$field['wrapper_html_attr']['v-if'] = $stripe_condition;
+				}
 			}
🧹 Nitpick comments (1)
inc/helpers/class-credential-store.php (1)

188-202: Consider deriving distinct keys for each encryption backend.

get_sodium_key() and get_encryption_key() return identical values. While not exploitable here (the two schemes are mutually exclusive fallbacks and values are prefix-tagged), cryptographic best practice is to use distinct key material per algorithm. A simple domain-separation tweak would suffice:

 private static function get_sodium_key(): string {
-    return hash('sha256', wp_salt('auth'), true);
+    return hash('sha256', 'wu_sodium_' . wp_salt('auth'), true);
 }

Alternatively, if the duplication is intentional to allow migrating between backends transparently, at minimum add a comment clarifying this.

wp_parse_str() + add_query_arg() decodes percent-encoded values like %2F
without re-encoding them, causing parameters inside redirect_to to leak
as top-level query params. This broke WooCommerce analytics URLs that use
path=%2Fanalytics%2Foverview.

In filter_login_url(), preserve the raw query string instead of parsing
and rebuilding it. In Primary_Domain redirect, use the full redirect URL
directly since replace_url() already handles the complete URL.

Co-Authored-By: Claude Opus 4.6 <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

Tests that SSO correctly triggers when visiting a subsite through a
mapped domain (127.0.0.1 vs localhost), verifying domain mapping
resolution, SSO redirect initiation, and auto-authentication flow.

Co-Authored-By: Claude Opus 4.6 <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: 2

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@tests/e2e/cypress/fixtures/setup-sso-test.php`:
- Around line 66-74: The associative arrays passed to $wpdb->update() should be
formatted with each key/value on its own line and you must check the return
value of $wpdb->update() to handle failures; update the call in the block that
runs when $existing_domain is truthy (where $table, $mapped_domain and
$existing_domain are used) so the second and third arguments (the data and where
arrays) are multi-line entries, then capture the return value (e.g. $result =
$wpdb->update(...)) and add error handling when $result === false (log the error
and abort/return early or throw) to avoid silently continuing on update failure.
- Around line 43-48: The multi-line call to wp_json_encode violates PHPCS:
ensure the opening parenthesis follows the function name on the same line and
the closing parenthesis is on its own line; specifically reformat the echo
wp_json_encode(...) call inside the is_wp_error($site_id) block so it reads echo
wp_json_encode( [ 'error' => $site_id->get_error_message(), ] ); with the
opening parenthesis directly after wp_json_encode and the closing parenthesis on
its own line; keep is_wp_error and get_error_message usage unchanged.
- Around line 57-64: The PHPCS warning is caused by interpolating {$table}
directly into the SQL string inside the $wpdb->prepare() call (used when
fetching $existing_domain); since table names cannot be parameterized, silence
the linter by adding an inline PHPCS ignore comment for the rule that flags
interpolated variables in SQL on the line with the $wpdb->prepare(...) call
referencing $table, keeping the rest of the prepared statement and placeholder
bindings intact.
- Around line 91-100: The wp_json_encode call with the array argument is using
inconsistent multi-line formatting; update the echo wp_json_encode([...])
invocation (the echo, wp_json_encode function and the array containing 'error'
=> 'Domain insert failed: ' . $wpdb->last_error and 'site_id' => $site_id) to
follow the project's multi-line call style (either make the entire call
single-line or put the array on its own indented lines with the closing
parenthesis on its own line), ensuring consistent commas and indentation and
then keep exit(1); and $domain_id = $wpdb->insert_id; as-is.

In `@tests/e2e/cypress/integration/060-sso-cross-domain.spec.js`:
- Around line 109-128: The test "Should preserve redirect_to parameter through
SSO flow" visits a specific targetPath (const targetPath =
"/wp-admin/options-general.php") but only asserts that the final URL includes
"/wp-admin/"; update the assertion in that it block to assert the full
targetPath (e.g., cy.url(...).should("include", targetPath)) and/or add a
stronger DOM check (e.g., verify a page-specific selector present on
options-general.php) using mappedDomainUrl and targetPath, or if the environment
cannot guarantee exact path preservation, change the test title and comment to
remove the `redirect_to` claim and document the limitation instead of asserting
exact path preservation.
- Around line 24-38: The before() hook currently runs
cy.wpCliFile(...).then(result) and uses expect() to assert no "error" in
result.stdout, which can lead to unclear failures; update the then callback (the
cy.wpCliFile call and its .then handler) to parse/inspect result.stdout (or
result) and if it contains an error key or any failure text throw a new Error
including the full stdout/stderr (e.g., throw new Error(`SSO setup failed:
${output}`)) so Cypress aborts the suite at setup instead of letting downstream
tests fail; keep the existing expect checks only for additional assertions but
ensure the explicit throw triggers an immediate, clear failure when cy.wpCliFile
indicates an error.
🧹 Nitpick comments (4)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@tests/e2e/cypress/fixtures/setup-sso-test.php`:
- Around line 43-48: The multi-line call to wp_json_encode violates PHPCS:
ensure the opening parenthesis follows the function name on the same line and
the closing parenthesis is on its own line; specifically reformat the echo
wp_json_encode(...) call inside the is_wp_error($site_id) block so it reads echo
wp_json_encode( [ 'error' => $site_id->get_error_message(), ] ); with the
opening parenthesis directly after wp_json_encode and the closing parenthesis on
its own line; keep is_wp_error and get_error_message usage unchanged.
- Around line 57-64: The PHPCS warning is caused by interpolating {$table}
directly into the SQL string inside the $wpdb->prepare() call (used when
fetching $existing_domain); since table names cannot be parameterized, silence
the linter by adding an inline PHPCS ignore comment for the rule that flags
interpolated variables in SQL on the line with the $wpdb->prepare(...) call
referencing $table, keeping the rest of the prepared statement and placeholder
bindings intact.
- Around line 91-100: The wp_json_encode call with the array argument is using
inconsistent multi-line formatting; update the echo wp_json_encode([...])
invocation (the echo, wp_json_encode function and the array containing 'error'
=> 'Domain insert failed: ' . $wpdb->last_error and 'site_id' => $site_id) to
follow the project's multi-line call style (either make the entire call
single-line or put the array on its own indented lines with the closing
parenthesis on its own line), ensuring consistent commas and indentation and
then keep exit(1); and $domain_id = $wpdb->insert_id; as-is.

In `@tests/e2e/cypress/integration/060-sso-cross-domain.spec.js`:
- Around line 24-38: The before() hook currently runs
cy.wpCliFile(...).then(result) and uses expect() to assert no "error" in
result.stdout, which can lead to unclear failures; update the then callback (the
cy.wpCliFile call and its .then handler) to parse/inspect result.stdout (or
result) and if it contains an error key or any failure text throw a new Error
including the full stdout/stderr (e.g., throw new Error(`SSO setup failed:
${output}`)) so Cypress aborts the suite at setup instead of letting downstream
tests fail; keep the existing expect checks only for additional assertions but
ensure the explicit throw triggers an immediate, clear failure when cy.wpCliFile
indicates an error.
tests/e2e/cypress/fixtures/setup-sso-test.php (3)

43-48: Fix multi-line function call formatting (PHPCS failures).

Static analysis flags Lines 44 and 46: opening parenthesis must be the last content on its line, and closing parenthesis must be on its own line.

Proposed fix
 	if (is_wp_error($site_id)) {
-		echo wp_json_encode([
-			'error' => $site_id->get_error_message(),
-		]);
+		echo wp_json_encode(
+			[
+				'error' => $site_id->get_error_message(),
+			]
+		);
 		exit(1);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/fixtures/setup-sso-test.php` around lines 43 - 48, The
multi-line call to wp_json_encode violates PHPCS: ensure the opening parenthesis
follows the function name on the same line and the closing parenthesis is on its
own line; specifically reformat the echo wp_json_encode(...) call inside the
is_wp_error($site_id) block so it reads echo wp_json_encode( [ 'error' =>
$site_id->get_error_message(), ] ); with the opening parenthesis directly after
wp_json_encode and the closing parenthesis on its own line; keep is_wp_error and
get_error_message usage unchanged.

57-64: Interpolated table name in $wpdb->prepare() — acceptable here but can silence the linter.

The PHPCS rule flags {$table} inside the query string. Table names can't be bound as placeholders, so this is the standard WP pattern. You can silence the warning with an inline ignore if desired:

Proposed fix
+// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name cannot be a placeholder.
 $existing_domain = $wpdb->get_var(
 	$wpdb->prepare(
 		"SELECT id FROM {$table} WHERE domain IN (%s, %s) AND blog_id = %d LIMIT 1",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/fixtures/setup-sso-test.php` around lines 57 - 64, The
PHPCS warning is caused by interpolating {$table} directly into the SQL string
inside the $wpdb->prepare() call (used when fetching $existing_domain); since
table names cannot be parameterized, silence the linter by adding an inline
PHPCS ignore comment for the rule that flags interpolated variables in SQL on
the line with the $wpdb->prepare(...) call referencing $table, keeping the rest
of the prepared statement and placeholder bindings intact.

91-100: Same multi-line call formatting issue on Lines 93-94.

Proposed fix
 	if (! $inserted) {
-		echo wp_json_encode([
-			'error'   => 'Domain insert failed: ' . $wpdb->last_error,
-			'site_id' => $site_id,
-		]);
+		echo wp_json_encode(
+			[
+				'error'   => 'Domain insert failed: ' . $wpdb->last_error,
+				'site_id' => $site_id,
+			]
+		);
 		exit(1);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/fixtures/setup-sso-test.php` around lines 91 - 100, The
wp_json_encode call with the array argument is using inconsistent multi-line
formatting; update the echo wp_json_encode([...]) invocation (the echo,
wp_json_encode function and the array containing 'error' => 'Domain insert
failed: ' . $wpdb->last_error and 'site_id' => $site_id) to follow the project's
multi-line call style (either make the entire call single-line or put the array
on its own indented lines with the closing parenthesis on its own line),
ensuring consistent commas and indentation and then keep exit(1); and $domain_id
= $wpdb->insert_id; as-is.
tests/e2e/cypress/integration/060-sso-cross-domain.spec.js (1)

24-38: Setup failure doesn't abort the suite — downstream tests will fail with confusing errors.

If the fixture returns an error, the expect assertions will fail inside before(), but the messaging may be unclear. Consider throwing explicitly so Cypress marks the suite as failed at the setup stage:

      expect(output).to.contain("site_id");
      expect(output).to.not.contain('"error"');
+
+      // Parse and expose for debugging if needed.
+      const data = JSON.parse(output);
+      if (data.error) {
+        throw new Error(`SSO setup failed: ${data.error}`);
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/integration/060-sso-cross-domain.spec.js` around lines 24 -
38, The before() hook currently runs cy.wpCliFile(...).then(result) and uses
expect() to assert no "error" in result.stdout, which can lead to unclear
failures; update the then callback (the cy.wpCliFile call and its .then handler)
to parse/inspect result.stdout (or result) and if it contains an error key or
any failure text throw a new Error including the full stdout/stderr (e.g., throw
new Error(`SSO setup failed: ${output}`)) so Cypress aborts the suite at setup
instead of letting downstream tests fail; keep the existing expect checks only
for additional assertions but ensure the explicit throw triggers an immediate,
clear failure when cy.wpCliFile indicates an error.

Comment on lines +66 to +74
if ($existing_domain) {
// Update existing record to ensure domain includes port.
$wpdb->update(
$table,
['domain' => $mapped_domain, 'active' => 1, 'stage' => 'done'],
['id' => $existing_domain],
['%s', '%d', '%s'],
['%d']
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Formatting: multi-item associative array values should each be on their own line.

Static analysis flags Line 70. Also, the $wpdb->update() return value is not checked — a failure here would silently proceed.

Proposed fix
 if ($existing_domain) {
 	// Update existing record to ensure domain includes port.
-	$wpdb->update(
+	$updated = $wpdb->update(
 		$table,
-		['domain' => $mapped_domain, 'active' => 1, 'stage' => 'done'],
+		[
+			'domain' => $mapped_domain,
+			'active' => 1,
+			'stage'  => 'done',
+		],
 		['id' => $existing_domain],
 		['%s', '%d', '%s'],
 		['%d']
 	);
+
+	if ( false === $updated ) {
+		echo wp_json_encode([
+			'error'   => 'Domain update failed: ' . $wpdb->last_error,
+			'site_id' => $site_id,
+		]);
+		exit(1);
+	}
+
 	$domain_id = (int) $existing_domain;
🧰 Tools
🪛 GitHub Check: Code Quality Checks

[failure] 70-70:
When a multi-item array uses associative keys, each value should start on a new line.


[warning] 68-68:
Direct database call without caching detected. Consider using wp_cache_get() / wp_cache_set() or wp_cache_delete().


[warning] 68-68:
Use of a direct database call is discouraged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/fixtures/setup-sso-test.php` around lines 66 - 74, The
associative arrays passed to $wpdb->update() should be formatted with each
key/value on its own line and you must check the return value of $wpdb->update()
to handle failures; update the call in the block that runs when $existing_domain
is truthy (where $table, $mapped_domain and $existing_domain are used) so the
second and third arguments (the data and where arrays) are multi-line entries,
then capture the return value (e.g. $result = $wpdb->update(...)) and add error
handling when $result === false (log the error and abort/return early or throw)
to avoid silently continuing on update failure.

Comment on lines +109 to +128
it(
"Should preserve redirect_to parameter through SSO flow",
{ retries: 1 },
() => {
// This verifies that URL parameters survive the SSO redirect chain.
cy.loginByApi(adminUser, adminPass);

// Visit a specific wp-admin page on the mapped domain.
const targetPath = "/wp-admin/options-general.php";

cy.visit(`${mappedDomainUrl}${targetPath}`, {
failOnStatusCode: false,
});

// After SSO, the user should land on the requested page (or wp-admin).
cy.url({ timeout: 60000 }).should("include", "/wp-admin/");
cy.get("body", { timeout: 30000 }).should("have.class", "wp-admin");
cy.get("#wpadminbar").should("exist");
}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The redirect_to preservation test doesn't verify arrival at the target page.

The test visits options-general.php but only asserts /wp-admin/ in the final URL. This would pass even if redirect_to was silently dropped. Consider tightening:

      // After SSO, the user should land on the requested page (or wp-admin).
-     cy.url({ timeout: 60000 }).should("include", "/wp-admin/");
+     cy.url({ timeout: 60000 }).should("include", "options-general.php");

If the wp-env SSO flow can't reliably preserve the exact path (as noted in the environment comments), then consider documenting that limitation explicitly in the test or removing the redirect_to claim from the test name.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/cypress/integration/060-sso-cross-domain.spec.js` around lines 109
- 128, The test "Should preserve redirect_to parameter through SSO flow" visits
a specific targetPath (const targetPath = "/wp-admin/options-general.php") but
only asserts that the final URL includes "/wp-admin/"; update the assertion in
that it block to assert the full targetPath (e.g., cy.url(...).should("include",
targetPath)) and/or add a stronger DOM check (e.g., verify a page-specific
selector present on options-general.php) using mappedDomainUrl and targetPath,
or if the environment cannot guarantee exact path preservation, change the test
title and comment to remove the `redirect_to` claim and document the limitation
instead of asserting exact path preservation.

@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

…ng emails

The inline login prompt on the register page had several issues: it would
disappear when interacting with other form fields because check_user_exists
shared global state across email/username fields, and the DOM-cloning pattern
in setup_inline_login_handlers conflicted with browser autofill. Now only
email changes can dismiss the email prompt, handlers attach once via a data
attribute flag, the close button is removed, and a validation error is shown
under the email field when the address already exists.

Co-Authored-By: Claude Opus 4.6 <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

@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

- Fix undefined $hook_lines in generate-berlindb-hooks.php causing PHP warnings
- Replace misused _n() calls with conditionals to resolve makepot warnings
  about missing singular placeholders and strings without translatable content
- Add missing translators comment for Stripe webhook error string
- Standardize translator comments across hosting provider integrations
- Update POT file and readme.txt

Co-Authored-By: Claude Opus 4.6 <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

The "every" keyword only appears when duration > 1 (e.g., "every 3 months").
With duration=1, the format is "$19.99 / month" and "month" respectively.

Co-Authored-By: Claude Opus 4.6 <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

@superdav42 superdav42 merged commit b59c741 into main Feb 17, 2026
8 of 9 checks passed
@superdav42 superdav42 deleted the fix/password-strength-enforcement branch February 17, 2026 02:41
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.

1 participant