Skip to content

[3.0]: Two accounts can register on the same email address on PostgreSQL #9594

Description

@albertlast

Basic Information

On PostgreSQL two accounts can be registered on what is the same email address, if the capitalisation differs. On MySQL the second registration is refused.

The "is this address already taken" check compares with a plain =, and neither side is normalised. Sources/Actions/Register2.php:536-546:

$request = Db::$db->query(
    'SELECT id_member
    FROM {db_prefix}members
    WHERE email_address = {string:email_address}
        OR email_address = {string:username}
    LIMIT 1',

$reg_options['email'] is taken from the form and only run through filter_var(..., FILTER_VALIDATE_EMAIL) (Sources/Actions/Register2.php:483); it is never case-folded. smf_members.email_address is a plain varchar(255) (Sources/Db/Schema/v3_0/Members.php:144-150) and is stored exactly as typed (Sources/Actions/Register2.php:622).

That makes the check depend entirely on the collation of the column: case-insensitive on MySQL, case-sensitive on PostgreSQL. The database does not catch it either, because idx_email_address is a non-unique index, so uniqueness is enforced only by this query.

The domain part of an address is case-insensitive by definition, and in practice every mail provider treats the local part that way too, so both accounts receive their activation mail at the same mailbox.

Steps to reproduce

  1. Install SMF 3.0 on PostgreSQL.
  2. Register an account with the email address user@example.com.
  3. Register a second account, with a different username, using User@Example.com.

Expected result

The second registration is refused with "That email address is already in use", the same as on MySQL.

Actual result

The second registration is accepted and two accounts now exist on the same address.

Version/Git revision

3.0 Alpha 4 (16af1506e)

Database Engine

PostgreSQL

Database Version

PostgreSQL 17.10

PHP Version

8.4.24

Logs

# PostgreSQL 17.10
smf=> SELECT 'User@Example.com' = 'user@example.com';
 ?column?
----------
 f

# MySQL 8.4.11
mysql> SELECT 'User@Example.com' = 'user@example.com';
+-----------------------------------------+
| 'User@Example.com' = 'user@example.com' |
+-----------------------------------------+
|                                       1 |
+-----------------------------------------+

Additional Information

The username side of registration does not have this problem, because it goes through spoofdetector_name, which is folded in PHP with Utils::casefold() and compared with a plain = (Sources/Unicode/SpoofDetector.php:334-352, Sources/Tasks/UpdateSpoofDetectorNames.php:113-118). That value is identical on both engines regardless of collation. Applying the same treatment to email addresses would settle this one, and would also let idx_email_address become a real unique index.

Related: #3214, on Db::$db->case_sensitive being hardcoded per engine rather than reflecting what the column actually does.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions