fix: block case-insensitive duplicate usernames while preserving original casing#531
Conversation
…l casing Agent-Logs-Url: https://github.com/OGSteam/ogspy/sessions/3f68b93a-39e3-4fe3-9193-aebfb9b0835f Co-authored-by: darknoon29 <13015521+darknoon29@users.noreply.github.com>
|
There was a problem hiding this comment.
Pull request overview
This PR addresses a schema/behavior mismatch where ogspy_user.name is stored with a case-sensitive collation (utf8mb4_bin), allowing case-variant duplicate accounts (e.g., Dardar vs dardar). It updates the model-level “does this username already exist?” checks to treat usernames case-insensitively while keeping the originally-entered casing on write.
Changes:
- Update
User_Model::select_is_user_name()to normalize input withmb_strtolower()and compare viaLOWER(name)in SQL. - Update
User_Model::select_is_other_user_name()similarly for rename/“other user” duplicate checks. - Add PHPUnit coverage for case-insensitive matching behavior and for the
LOWER()predicate usage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
model/User_Model.php |
Makes duplicate-name checks case-insensitive by lowercasing input and comparing against LOWER(name) in SQL. |
tests/unit/UserModelTest.php |
Adds unit tests validating the case-insensitive duplicate detection behavior and query construction. |
| $username = mb_strtolower($username, 'UTF-8'); | ||
| $username = $this->db->sql_escape_string($username); | ||
|
|
||
| $request = "SELECT * FROM " . TABLE_USER . " WHERE `name` = '$username'"; | ||
| $request = "SELECT * FROM " . TABLE_USER . " WHERE LOWER(`name`) = '$username'"; | ||
| $result = $this->db->sql_query($request); |
There was a problem hiding this comment.
Using LOWER(name) in the predicate provides a case-insensitive check, but it also bypasses the existing UNIQUE index on name and cannot fully prevent case-variant duplicates under concurrency (two simultaneous create/rename requests can still both pass the check and insert, because the DB constraint remains case-sensitive). If the goal is to truly block case-insensitive duplicates, consider enforcing this at the database level (e.g., case-insensitive collation for name, or a generated/stored lowercase column with a UNIQUE index) and keep the query sargable.
| $username = mb_strtolower($username, 'UTF-8'); | ||
| $username = $this->db->sql_escape_string($username); | ||
| $user_id = (int) $user_id; | ||
|
|
||
| $request = "SELECT * FROM " . TABLE_USER . " WHERE `name` = '" . $username . "' AND `id` <> " . $user_id; | ||
| $request = "SELECT * FROM " . TABLE_USER . " WHERE LOWER(`name`) = '" . $username . "' AND `id` <> " . $user_id; | ||
| $result = $this->db->sql_query($request); |
There was a problem hiding this comment.
Same concern as select_is_user_name(): LOWER(name) makes the lookup non-sargable (won’t use the existing UNIQUE index on name) and still can’t guarantee case-insensitive uniqueness under concurrent updates/inserts while the DB constraint stays case-sensitive. A DB-level case-insensitive unique constraint (collation change or generated lowercase column + UNIQUE index) would make this robust and performant.


The
ogspy_usertable usesutf8mb4_bincollation, makingUNIQUE KEYonnamecase-sensitive. This allowedDardaranddardarto coexist as distinct accounts.Approach
Per @darknoon29: preserve the user's chosen casing on write, but enforce case-insensitive uniqueness on duplicate checks.
Changes
model/User_Model.php—select_is_user_name(): Applymb_strtolowerto input and useLOWER(\name`)` in the SQL predicate.model/User_Model.php—select_is_other_user_name(): Same treatment for rename duplicate checks.add_new_user()andset_user_pseudo()are unchanged — stored casing is preserved.tests/unit/UserModelTest.php: 4 new tests covering case-insensitive match detection and SQLLOWER()usage for both methods.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
https://api.github.com/repos/PHPMailer/PHPMailer/zipball/ebf1655bd5b99b3f97e1a3ec0a69e5f4cd7ea088/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/components/jquery/zipball/8edc7785239bb8c2ad2b83302b856a1d61de60e7/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/iamceege/tooltipster/zipball/0daaeffdc7da578f4048c4cf9ad2f37a474144f0/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a7de5df2e094f9a80b40a522391a7e6022df5f6/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b2429f58ae75cae980b5bb9873abe4de6aac8b58/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4/usr/bin/php8.3 /usr/bin/php8.3 -n -c /tmp/407w1F /usr/bin/composer install --no-interaction(http block)If you need me to access, download, or install something from one of these locations, you can either: