Skip to content

Fix #387: Prevent password from matching username#1608

Merged
mambax7 merged 6 commits intoXOOPS:masterfrom
mambax7:fix/387-prevent-password-equals-username
Feb 23, 2026
Merged

Fix #387: Prevent password from matching username#1608
mambax7 merged 6 commits intoXOOPS:masterfrom
mambax7:fix/387-prevent-password-equals-username

Conversation

@mambax7
Copy link
Copy Markdown
Collaborator

@mambax7 mambax7 commented Feb 17, 2026

Summary

  • Adds a case-insensitive check in XoopsUserUtility::validate() that rejects a password identical to the username
  • Covers both user registration (register.php) and profile edit (edituser.php) since both call XoopsUserUtility::validate()
  • Adds language string _US_PWDEQUALSUNAME to language/english/user.php

Test plan

  • Register a new user with password = username → should be rejected
  • Edit profile and set password = username → should be rejected
  • Register/edit with password ≠ username → should succeed normally

Fixes #387

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Passwords that match the username (case-insensitive) are now rejected during account creation, profile edits, admin user updates, and password changes; users see a clear error and cannot save such passwords.
  • Localization

    • New user-facing error messages added to inform users that their password cannot be the same as their username.

Add a validation check in XoopsUserUtility::validate() that rejects
a password identical to the username (case-insensitive), covering both
registration and profile edit flows.

Add the corresponding language string _US_PWDEQUALSUNAME to
htdocs/language/english/user.php.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 17, 2026 02:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 17, 2026

Warning

Rate limit exceeded

@mambax7 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between f0d790d and 376a754.

📒 Files selected for processing (4)
  • htdocs/class/userutility.php
  • htdocs/edituser.php
  • htdocs/modules/profile/changepass.php
  • htdocs/modules/system/admin/users/main.php

Walkthrough

Adds case-insensitive validation preventing a non-empty password from being identical to the username across core validation, user edit/save, profile password change, and admin user management; also adds two corresponding language strings for the error message.

Changes

Cohort / File(s) Summary
Core validation
htdocs/class/userutility.php
Adds case-insensitive check in XoopsUserUtility::validate to reject passwords identical to username and append _US_PWDEQUALSUNAME to errors.
User edit / save flows
htdocs/edituser.php, htdocs/modules/system/admin/users/main.php
After existing password length/confirmation checks, adds case-insensitive password==username validation that records _US_PWDEQUALSUNAME and aborts save in add/edit paths.
Password change (profile)
htdocs/modules/profile/changepass.php
Adds same case-insensitive password==username check after new/confirm match; emits _US_PWDEQUALSUNAME on violation.
Language strings (frontend & admin)
htdocs/language/english/user.php, htdocs/modules/system/language/english/admin/users.php
Adds define('_US_PWDEQUALSUNAME', 'Your password cannot be the same as the username.') and define('_AM_SYSTEM_USERS_PWDEQUALSUNAME', 'Password cannot be the same as the username.') respectively.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User as User (Client)
participant Handler as Edit/Change Handler
participant Util as XoopsUserUtility
participant Lang as Language store
User->>Handler: submit username + password (create/edit/change)
Handler->>Util: validate(username, password)
Util-->>Util: check length, confirmation, then lowercase(password) == lowercase(username)
alt password == username
Util->>Handler: return error code _US_PWDEQUALSUNAME
Handler->>Lang: fetch message for _US_PWDEQUALSUNAME
Handler->>User: display error, abort save
else valid
Util->>Handler: validation OK
Handler->>User: proceed with save / success

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ 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 accurately and concisely describes the main change: adding case-insensitive validation to prevent passwords matching usernames across multiple user management flows.
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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a security enhancement to prevent users from setting their password equal to their username during registration and profile editing. The change addresses issue #387 by adding a case-insensitive validation check in the core XoopsUserUtility::validate() method.

Changes:

  • Added case-insensitive password-equals-username check in XoopsUserUtility::validate()
  • Added English language string _US_PWDEQUALSUNAME for the validation error message

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
htdocs/class/userutility.php Added validation logic to reject passwords matching username (case-insensitive)
htdocs/language/english/user.php Added error message constant for password-equals-username validation

Comment thread htdocs/class/userutility.php Outdated
Comment thread htdocs/class/userutility.php Outdated
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@htdocs/class/userutility.php`:
- Around line 223-224: The condition uses strtolower($pass) without guaranteeing
$pass is not null, causing deprecation/errors; update the conditional in the
user utility check (the branch that appends _US_PWDEQUALSUNAME to $stop) to
ensure $pass is not null (e.g., add $pass !== null) or replace the $pass check
with a null/empty-safe check like !empty($pass) before calling strtolower($pass)
so strtolower is never invoked with a null argument.

Comment thread htdocs/class/userutility.php Outdated
- userutility.php: add $pass !== null guard before strtolower($pass)
  to prevent PHP 8.1 deprecation / PHP 9.0 TypeError when $pass is null

- edituser.php: add username=password check to the inline validation
  block that bypasses XoopsUserUtility::validate() (password change
  during profile edit)

- modules/profile/changepass.php: add username=password check to the
  profile module's dedicated change-password page (also bypassed
  XoopsUserUtility::validate())

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@htdocs/class/userutility.php`:
- Around line 223-224: Add unit tests exercising the new password≠username rule
in userutility.php: write tests that call the user validation/registration
routine that uses $pass and $uname (the code path where strtolower($pass) ===
strtolower($uname) triggers) and assert that when password equals username
(case-insensitive) the validation fails and returns/contains the
_US_PWDEQUALSUNAME error, and also assert that when password and username differ
validation succeeds; place tests alongside existing registration/profile unit
tests to cover both registration and profile-update flows so this core behavior
is protected from regressions.

In `@htdocs/edituser.php`:
- Around line 64-65: The type cast "(string)" in the conditional using
strtolower((string)$xoopsUser->getVar(...)) violates PSR-12; add a space after
the cast so it reads "(string) $xoopsUser->getVar(...)" in the strtolower call
within the block that sets $errors[] = _US_PWDEQUALSUNAME; ensure the change is
applied to the conditional using strtolower and the getVar invocation.

In `@htdocs/modules/profile/changepass.php`:
- Around line 58-59: The condition in changepass.php uses a type cast without
the PSR-12 required space: replace occurrences like
(string)$GLOBALS['xoopsUser']->getVar('uname', 'n') in the password check (the
elseif that compares $password to
strtolower((string)$GLOBALS['xoopsUser']->getVar(...))) with a space after the
cast so it becomes (string) $GLOBALS['xoopsUser']->getVar(...); ensure the same
spacing rule is applied anywhere else using (string) casts in this file.

Comment thread htdocs/class/userutility.php Outdated
Comment on lines +223 to +224
} elseif ($pass !== '' && $pass !== null && $uname !== null && strtolower($pass) === strtolower($uname)) {
$stop .= _US_PWDEQUALSUNAME . '<br>';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add unit tests for the new password≠username rule.

This is new core validation behavior and should be covered by tests to prevent regressions in registration/profile flows.

Based on learnings: "New functionality has corresponding unit tests".

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

In `@htdocs/class/userutility.php` around lines 223 - 224, Add unit tests
exercising the new password≠username rule in userutility.php: write tests that
call the user validation/registration routine that uses $pass and $uname (the
code path where strtolower($pass) === strtolower($uname) triggers) and assert
that when password equals username (case-insensitive) the validation fails and
returns/contains the _US_PWDEQUALSUNAME error, and also assert that when
password and username differ validation succeeds; place tests alongside existing
registration/profile unit tests to cover both registration and profile-update
flows so this core behavior is protected from regressions.

Comment thread htdocs/edituser.php Outdated
Comment thread htdocs/modules/profile/changepass.php Outdated
…st spacing

The admin panel (system/admin/users/main.php) was missing the password!=username
validation that was already present in registration, profile edit, and password
change forms. Also fixes PSR-12 spacing after (string) casts.

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment on lines +183 to +186
if ('' !== Request::getString('password') &&
strtolower(Request::getString('password')) === strtolower(Request::getString('uname'))) {
xoops_error(_US_PWDEQUALSUNAME);
break;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

_US_PWDEQUALSUNAME is a front-end user language constant, but this admin script doesn’t appear to load user.php language strings. In the admin UI this can result in the raw constant name being displayed instead of a translated error. Consider either loading the user language (xoops_loadLanguage('user')) before using this constant, or (preferably) adding an _AM_SYSTEM_USERS_* admin language constant and using that here.

Copilot uses AI. Check for mistakes.
Comment on lines +276 to +279
if ('' !== Request::getString('password') &&
strtolower(Request::getString('password')) === strtolower(Request::getString('uname'))) {
xoops_error(_US_PWDEQUALSUNAME);
break;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Same issue as above: this uses _US_PWDEQUALSUNAME in an admin context without ensuring the user language file is loaded. Please switch to an admin language constant or explicitly load the user language file before this validation block.

Copilot uses AI. Check for mistakes.
Comment thread htdocs/modules/profile/changepass.php Outdated
}
if ($password != $vpass) {
$errors[] = _US_PASSNOTSAME;
} elseif (!empty($password) && strtolower($password) === strtolower((string) $GLOBALS['xoopsUser']->getVar('uname', 'n'))) {
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

!empty($password) treats the string '0' as empty, which would skip the new username-equality check even though '0' is a valid non-empty password. Since you’re already in the non-mismatch branch, this guard isn’t necessary—prefer a strict empty-string check (or remove the guard) so the comparison always runs for any non-empty password.

Suggested change
} elseif (!empty($password) && strtolower($password) === strtolower((string) $GLOBALS['xoopsUser']->getVar('uname', 'n'))) {
} elseif ($password !== '' && strtolower($password) === strtolower((string) $GLOBALS['xoopsUser']->getVar('uname', 'n'))) {

Copilot uses AI. Check for mistakes.
Comment thread htdocs/edituser.php
Comment on lines 61 to 66
$vpass = XoopsRequest::getString('vpass', '');
if ($password != $vpass) {
$errors[] = _US_PASSNOTSAME;
} elseif (strtolower($password) === strtolower((string) $xoopsUser->getVar('uname', 'n'))) {
$errors[] = _US_PWDEQUALSUNAME;
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The PR description says registration and profile edit are covered because they call XoopsUserUtility::validate(), but htdocs/edituser.php performs its own password validation instead of calling XoopsUserUtility::validate(). Either update the PR description to reflect the actual coverage, or refactor this flow to reuse XoopsUserUtility::validate() so all password rules stay consistent in one place.

Copilot uses AI. Check for mistakes.
Comment on lines 220 to +224
$stop .= _US_PASSNOTSAME . '<br>';
} elseif (($pass != '') && (strlen($pass) < $xoopsConfigUser['minpass'])) {
$stop .= sprintf(_US_PWDTOOSHORT, $xoopsConfigUser['minpass']) . '<br>';
} elseif ($pass !== '' && $pass !== null && $uname !== null && strtolower($pass) === strtolower($uname)) {
$stop .= _US_PWDEQUALSUNAME . '<br>';
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

New validation behavior was added to XoopsUserUtility::validate() but there are currently no unit tests exercising validate() (tests only cover getIP()). Please add a unit test that verifies a password equal to the username (case-insensitive) is rejected, using the existing stub DB setup in the test bootstrap so the DB uniqueness checks don’t make the test brittle.

Copilot generated this review using guidance from repository custom instructions.
- Use _AM_SYSTEM_USERS_PWDEQUALSUNAME admin constant instead of _US_PWDEQUALSUNAME
  in admin users panel (user language file is not loaded in admin context)
- Replace !empty($password) with $password !== '' in changepass.php to avoid
  treating the string '0' as empty

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

codecov Bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (master@7222796). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff            @@
##             master   #1608   +/-   ##
========================================
  Coverage          ?       0           
========================================
  Files             ?       0           
  Lines             ?       0           
  Branches          ?       0           
========================================
  Hits              ?       0           
  Misses            ?       0           
  Partials          ?       0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@htdocs/modules/system/admin/users/main.php`:
- Around line 183-187: The pwdequalsuname check fires even when the confirmation
field is empty; change the condition that currently tests
Request::getString('password') to also require Request::getString('pass2') !==
'' (i.e., only run the xoops_error(_AM_SYSTEM_USERS_PWDEQUALSUNAME) check when
pass2 is non-empty) in both the update path and the add path so the check
matches the same gating used when the password is actually hashed/persisted.
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a4a6b1 and 1abe236.

📒 Files selected for processing (4)
  • htdocs/edituser.php
  • htdocs/modules/profile/changepass.php
  • htdocs/modules/system/admin/users/main.php
  • htdocs/modules/system/language/english/admin/users.php

Comment thread htdocs/modules/system/admin/users/main.php Outdated
The check should only fire when a password change is actually being made
(pass2 is non-empty), matching the existing password-mismatch check and
the condition that triggers password hashing/persistence.

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment on lines 220 to 225
$stop .= _US_PASSNOTSAME . '<br>';
} elseif (($pass != '') && (strlen($pass) < $xoopsConfigUser['minpass'])) {
$stop .= sprintf(_US_PWDTOOSHORT, $xoopsConfigUser['minpass']) . '<br>';
} elseif ($pass !== '' && $pass !== null && $uname !== null && strtolower($pass) === strtolower($uname)) {
$stop .= _US_PWDEQUALSUNAME . '<br>';
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

XoopsUserUtility::validate() has new password rule logic, but there are existing PHPUnit tests for this class (e.g., tests/unit/htdocs/class/XoopsUserUtilityTest.php) and they currently explicitly avoid testing validate(). Consider adding coverage for the new “password equals username” rejection—if direct testing is hard due to DB/global dependencies, extracting the check into a small pure helper (or injecting dependencies) would make it testable and prevent regressions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +183 to +187
if ('' !== Request::getString('pass2') &&
'' !== Request::getString('password') &&
strtolower(Request::getString('password')) === strtolower(Request::getString('uname'))) {
xoops_error(_AM_SYSTEM_USERS_PWDEQUALSUNAME);
break;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

This password==username validation runs whenever password is non-empty, but later the code only applies a password change when pass2 is non-empty. This can block saving unrelated user edits (or show an error) in cases where the password won’t actually be updated. Consider scoping this validation to the same condition used to set the password (e.g., only when pass2 is provided / when a password change is intended), or require confirmation when password is set.

Copilot uses AI. Check for mistakes.
Comment on lines 276 to +280

if ('' !== Request::getString('pass2') &&
'' !== Request::getString('password') &&
strtolower(Request::getString('password')) === strtolower(Request::getString('uname'))) {
xoops_error(_AM_SYSTEM_USERS_PWDEQUALSUNAME);
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Same issue as above in the add-user flow: this check runs based on password alone, but the password is only persisted when pass2 is non-empty. Align the validation condition with the actual password-setting logic (or enforce that pass2 is required when password is provided) to avoid unexpected validation failures or confusing behavior.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@htdocs/modules/system/admin/users/main.php`:
- Around line 183-188: Replace ASCII-only strtolower() calls used when comparing
Request::getString('password') and Request::getString('uname') with
multibyte-aware mb_strtolower(..., 'UTF-8'); specifically change occurrences
like strtolower(Request::getString('password')) and
strtolower(Request::getString('uname')) to
mb_strtolower(Request::getString('password'), 'UTF-8') and
mb_strtolower(Request::getString('uname'), 'UTF-8') before calling
xoops_error(_AM_SYSTEM_USERS_PWDEQUALSUNAME); apply the same change to the
identical comparison in the update/add path referenced (the other occurrence
around lines 277–279).
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1abe236 and f0d790d.

📒 Files selected for processing (1)
  • htdocs/modules/system/admin/users/main.php

Comment thread htdocs/modules/system/admin/users/main.php
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Replace strtolower() with mb_strtolower(..., 'UTF-8') in all password!=username
checks for proper Unicode case folding when loose username validation is enabled.

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

@mambax7 mambax7 merged commit 69a82b5 into XOOPS:master Feb 23, 2026
9 of 11 checks passed
@mambax7 mambax7 deleted the fix/387-prevent-password-equals-username branch March 15, 2026 13:59
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.

Prevent username = password

2 participants