Skip to content

Users: Prevent username and email collisions without blocking existing users. - #4749

Open
adamsilverstein wants to merge 10 commits into
WordPress:trunkfrom
adamsilverstein:ticket/57394-c
Open

adamsilverstein wants to merge 10 commits into
WordPress:trunkfrom
adamsilverstein:ticket/57394-c

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jun 29, 2023 •

Copy link
Copy Markdown
Member

Reworked this after the 6.2 revert so it no longer blocks updates to existing users.

Claude did the rework here, details below:

Prevents a new user from taking another user's email address as their username, and prevents a user from changing their email address to another user's username. Either collision can log someone into the wrong account when both accounts share a password.

Trac ticket: https://core.trac.wordpress.org/ticket/57394

The original fix in r55358 / r55360 was reverted in r55584 (see https://core.trac.wordpress.org/ticket/57967) because the check ran on every save, so wp_update_user() failed for any user whose username is their own email address. The later attempts (#4257, #5032, #8885) each still re-checked data that wasn't changing, which blocks a different set of existing users.

The approach here: only check values that are new or changing.

  • The username check only runs when creating a user. wp_insert_user() never writes user_login on update, so there is nothing to check there.
  • The email check only runs on insert or when the email address changes (case-insensitive, same as the existing duplicate email check), and skips the user being saved, so changing your email to match your own username works.
  • Collisions that already exist are left alone. Saving a user whose data already collides with another account keeps working.
  • Both checks are skipped when WP_IMPORTING is defined, matching the existing duplicate email check.

The email check is mirrored in register_new_user(), edit_user(), the REST users update endpoint (400 rest_user_invalid_email) and send_confirmation_on_profile_email(), so users see a form error instead of a generic failure, and no confirmation email goes out for a blocked address.

Not changed: multisite signups already disallow @ in usernames. Creating a user over REST on single site returns the wp_insert_user() error, the same way a duplicate email already does.

Testing Instructions

Automated: npm run test:php -- --group 57394,57967

  • Data providers cover 7 insert cases and 8 update cases. Collisions are seeded directly in the database to mirror sites that predate the check.
  • Against trunk, the 8 cases that should be blocked fail and the cases that should keep working pass, so the allowed behavior is unchanged.

Manual:

  1. Create user A with username a and email a@example.com.
  2. On Users > Add New, try to create a user with username a@example.com. You should see "This username is not available."
  3. Create user B with username b@example.com and email b-other@example.com.
  4. Edit user A and change the email to b@example.com. You should see "This email address is not available."
  5. Create user C with username c@example.com and email c-other@example.com, then change C's email to c@example.com. This should save.
  6. Try registering at wp-login.php?action=register with either colliding value. Both should show an error.

AI Use

Code and description both written with 🤖 Claude Code over a couple of rounds of back and forth. I will review and test.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

The check reverted in r55584 ran on every save, so updating a user whose
username matches their own email, or who already collided with another
user, failed. Usernames can only be set on insert, and emails are now
compared against existing usernames only when they are new or changing,
excluding the user being saved. Existing collisions are left alone, so
no previously valid update starts failing.

The same email-vs-username check is mirrored in register_new_user(),
edit_user(), the REST users update endpoint and the profile email change
confirmation, so users see a form error instead of a generic failure.
Imports skip the checks, matching the existing email uniqueness check.

Claude-Session: https://claude.ai/code/session_01XXGeBhJbUuzyPyZhFKKM5T
Data providers spell out which saves must be blocked and which must keep
working, including the cases behind the r55584 revert: a user whose
username is their own email, and users whose existing data already
collides with another account. Collisions are seeded directly in the
database to mirror sites that predate the check.

Replaces the earlier single test, which covered only one update case.

Claude-Session: https://claude.ai/code/session_01XXGeBhJbUuzyPyZhFKKM5T
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props adamsilverstein.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@adamsilverstein adamsilverstein changed the title Check cases only for new users, not updates. New tests. Users: Prevent username and email collisions without blocking existing users. Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

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