From fa33a570d85fd6f1f13aa44216a40828810cc039 Mon Sep 17 00:00:00 2001 From: albertlast Date: Mon, 10 Aug 2026 06:52:11 +0200 Subject: [PATCH] Says a username is taken instead of erroring about it The registration form asks whether a username is free as it is typed, and the answer for almost every name anybody tries is an error page. isReservedName() hands $fatal down to the two checks that look for an existing member or membergroup of a similar name, so those die instead of returning true. 2.1 asked the members table in the same place and only ever returned. Two things follow from that: The availability check answers `?action=signup;sa=usernamecheck;xml` with the fatal error page rather than , writes a row to the error log for each one, and the callback that reads the response finds no element in it and throws. And Profile.php returns 'name_taken' when a display name is in use, which is dead code: isReservedName() has already ended the request, with the wrong words, before the return is reached. validateUsername() has the same shape one level up. $return_error is a promise to hand the problems back rather than die of them, so pass that on: without it a name on the admin's reserved list still ends the availability check with an error page. Co-Authored-By: Claude Opus 5 Signed-off-by: albertlast --- Sources/Security.php | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Sources/Security.php b/Sources/Security.php index acf7249425..2a99244e00 100644 --- a/Sources/Security.php +++ b/Sources/Security.php @@ -253,7 +253,12 @@ public static function validateUsername(int $memID, string $username, bool $retu $errors[] = ['lang', 'username_reserved', 'general', [Lang::getTxt('guest_title', file: 'General')]]; } - if ($check_reserved_name && self::isReservedName($username, $memID, false)) { + // $return_error is a promise to hand the problems back rather than die + // of them, and the caller that asks for it wants XML. isReservedName() + // dies by default, so say otherwise, or a name on the admin's reserved + // list answers the registration form's availability check with an error + // page and puts a row in the error log on the way. + if ($check_reserved_name && self::isReservedName($username, $memID, false, !$return_error)) { $errors[] = ['done', '(' . Utils::htmlspecialchars($username) . ') ' . Lang::getTxt('name_in_use', file: 'General')]; } @@ -339,13 +344,21 @@ public static function isReservedName(string $name, int $current_id_member = 0, } } + /* + * A name someone else already answers to is taken, not forbidden, and + * the two want telling apart: the checks above are about what an admin + * banned, and dying with "that name is reserved" is the right answer to + * them. These two say "somebody has this already", which is ordinary + * and is what every caller is asking about, so hand the answer back and + * let them phrase it. 2.1 asked the members table here and did the same. + */ // Check for similar existing member names. - if (Unicode\SpoofDetector::checkSimilarMemberName($name, $current_id_member, $fatal)) { + if (Unicode\SpoofDetector::checkSimilarMemberName($name, $current_id_member, false)) { return true; } // Does the name resemble a member group name? - if (Unicode\SpoofDetector::checkSimilarGroupName($name, $fatal)) { + if (Unicode\SpoofDetector::checkSimilarGroupName($name, false)) { return true; }