Skip to content

[7 / 7] - Admin settings + Cross-site request forgery / Nonces#204

Open
TheSCREWEDSoftware wants to merge 7 commits into
azerothcore:masterfrom
TheSCREWEDSoftware:7_user_admincsrf
Open

[7 / 7] - Admin settings + Cross-site request forgery / Nonces#204
TheSCREWEDSoftware wants to merge 7 commits into
azerothcore:masterfrom
TheSCREWEDSoftware:7_user_admincsrf

Conversation

@TheSCREWEDSoftware

@TheSCREWEDSoftware TheSCREWEDSoftware commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Split from the main PR: #197

Split by Claude

Realm Settings revamp: SOAP status badge (online/offline on save), live module list from the server, and module-requirement mapping that hides a page when its module is missing. Tools page gets the Web Integration group (security logging, allow-old-passwords, 2FA admin controls, IP-history lookup), Name-Unlock delete buttons moved inside the box + a reset-to-default, and CSRF nonces added to all settings forms (Realm, Tools, Eluna, PvP).

image

Summary by CodeRabbit

  • New Features

    • Added two-factor authentication management with website and in-game 2FA support
    • Added Security page with login history tracking, password management, and recent connections display
    • Added Dark Mode toggle for WordPress admin interface
    • Enhanced item restoration with new card-based interface
    • Added module management and requirements configuration for server modules
    • Improved mail return interface with character sidebar and item grid layout
  • Bug Fixes

    • Enhanced security with CSRF protection across all settings forms

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a user Security page with website and in-game 2FA management (TOTP/email verification, WP2FA integration), a password-change handler with SOAP sync and reuse history, CSRF nonce protection across all admin forms, a connection logger recording login IPs with GeoIP enrichment, a dark-mode admin toggle, server-module management panels in Realm Settings, admin 2FA removal tools in the Tools page, and UI overhauls for character ordering, mail return, item restoration, and unstuck menus backed by a new AcoreCharColors utility.

Changes

AzerothCore WP Plugin: Security, 2FA, Connection Logging, and UI Overhaul

Layer / File(s) Summary
Shared utilities, config options, and bootstrap
src/.../Utils/AcoreCharColors.php, src/.../Manager/Opts.php, src/.../Manager/Soap/SmartstoneService.php, src/.../Hooks/Various/tgmplugin_activator.php, src/acore-wp-plugin/src/boot.php
Introduces AcoreCharColors with WoW class/race/faction lookup and row-style helpers; adds three new Opts config fields; registers WP 2FA as a required TGMPA plugin; wires both new files into the bootstrap.
CSRF nonce hardening across admin and user forms
src/.../AdminPanel/Pages/ElunaSettings.php, src/.../Pages/PVPRewards.php, src/.../AdminPanel/SettingsController.php, src/.../Pages/Tools.php (nonce only), src/.../UserPanel/Pages/RafProgressPage.php (nonce only), src/.../CharactersMenu/CharactersController.php, src/.../Tools/ToolsApi.php, src/.../Manager/ACoreServices.php
Adds wp_nonce_field to ElunaSettings, PVPRewards, Tools, and RAF forms; adds check_admin_referer in SettingsController for all four endpoints; adds nonce validation and resetCharacterOrder() branching in CharactersController; adds permission_callback guards and input/ownership validation to ToolsApi::ItemRestore; adds ownership validation in ACoreServices::getRestorableItemsByCharacter.
Login connection logging with GeoIP enrichment
src/.../Hooks/User/ConnectionLogger.php, src/.../Hooks/User/Include.php
Creates acore_login_history table, hooks wp_login to record website and in-game IPs with country lookup via ip-api, implements trusted-proxy CIDR matching, rate-limit-aware GeoIP requests, a cron backfill for Unknown countries, and history query/format helpers.
Password change handler with SOAP sync and history
src/.../Hooks/User/PasswordHandler.php, src/.../Hooks/User/User.php (timestamp lines)
PasswordHandler.php processes security form POST under admin_init: verifies nonce, enforces 2FA unlock, validates old/new/confirm passwords and reuse history, syncs to game server via SOAP (aborting WP update on SOAP failure), records change timestamp and password history, and redirects with a transient status message. User.php records acore_password_changed_at on profile update and password reset.
2FA/TOTP backend helpers and REST API endpoints
src/.../ServerInfo/ServerInfoApi.php
Adds RFC 6238 TOTP helpers, WP2FA integration with fallback manual decryption, unlock/attempt transient key helpers, and a self-removal sync logger. Adds 14 new REST endpoints covering server-modules, server-module-requirements, admin 2FA check/remove/backup-codes-remove/login-history, and user-facing verify-website-2fa, request-email-2fa, verify-email-2fa, remove-ingame-2fa, 2fa-status, and connections.
User Security page: password, 2FA, and connections
src/.../UserPanel/Pages/SecurityPage.php, src/.../UserPanel/UserController.php, src/.../UserPanel/UserView.php, src/.../UserPanel/UserMenu.php, src/.../Hooks/User/User.php (profile 2FA hooks)
Adds SecurityPage.php with password postbox, website 2FA management (WP2FA embed behind TOTP/email gate), in-game 2FA removal UI, and paginated recent connections table with all REST-backed client JS. Wires through UserController::showSecurityPage, UserView::getSecurityRender, and a new "Security" submenu registration. Adds acore_profile_2fa_removal_warning and acore_profile_recent_connections hooks on the standard profile page.
Admin Tools: 2FA management, security settings, login history
src/.../AdminPanel/Pages/Tools.php, src/.../UserPanel/UserController.php (RAF validation)
Tools.php adds module-gated resurrection scroll controls, a Web Integration column (security logging, GeoIP dependency, old-password policy, website/in-game 2FA removal via wire2fa JS), a paginated User Login History lookup card, Name Unlock Thresholds add/delete/reindex JS, and a reusable confirm modal. UserController::showRafProgress tightens POST validation with absint and non-positive rejection.
Realm Settings: multi-panel module management
src/.../AdminPanel/Pages/RealmSettings.php, src/.../UserPanel/Pages/RafProgressPage.php (HTML only)
Reworks Realm Settings into a flex layout with nonce-bearing settings form, a SOAP-dependent Modules panel (refresh/timestamp/diff messaging), and a Module Requirements editor. JS is rewritten as a jQuery IIFE with on-load SOAP check, REST-driven module refresh, requirements save, and AJAX form submission. RAF page restructures HTML to use localized strings and Bootstrap notice containers.
Character ordering, mail return, item restoration, and unstuck UI overhauls
src/.../CharactersMenu/CharactersView.php, src/.../MailReturnMenu/MailReturn*.php, src/.../mail-return/mail-return.js, src/.../UserPanel/Pages/ItemRestorationPage.php, src/.../UnstuckMenu/UnstuckView.php, src/.../ResurrectionScrollMenu/ResurrectionScroll*.php
CharactersView adopts AcoreCharColors and adds Reset Order button. Mail Return gains a sidebar character picker, CoD label, item quality coloring, and Wowhead icon upgrades (controller adds cod/Quality SQL fields). Item Restoration becomes a sidebar+card-grid with fetch-based loading. UnstuckView adopts AcoreCharColors and wp_localize_script. ResurrectionScrollMenu adds module-CSV gate; view adjusts badge colors.
Dark mode toggle and theming CSS
src/.../Hooks/Various/DarkMode.php, web/assets/css/dark-mode.css, web/assets/css/theme.css, web/assets/css/main.css
DarkMode.php registers admin bar toggle, body class filter, AJAX handler, late style overrides, and WP2FA wizard CSS hook. dark-mode.css adds broad admin UI dark theming. theme.css introduces CSS custom properties for light/dark modes, item-quality colors, 2FA UI, myCRED, connection rows, and buttons. main.css adds the shared character-row system, mail return grid layout, and profile sub-page component styles.

Sequence Diagram(s)

sequenceDiagram
  participant User as User Browser
  participant SecurityPage as SecurityPage.php / JS
  participant PasswordHandler as PasswordHandler (admin_init)
  participant ServerInfoApi as ServerInfoApi REST
  participant WP2FA as WP2FA Plugin
  participant GameDB as Game Account DB
  participant ConnectionLogger as ConnectionLogger (wp_login)

  User->>SecurityPage: Load /security page
  SecurityPage->>ServerInfoApi: GET 2fa-status
  ServerInfoApi-->>SecurityPage: {website_enabled, ingame_enabled, removal_count}
  User->>PasswordHandler: POST change-password form (nonce)
  PasswordHandler->>GameDB: SOAP sync new password
  PasswordHandler-->>User: redirect with transient message
  User->>SecurityPage: POST verify-website-2fa (6-digit TOTP)
  SecurityPage->>ServerInfoApi: POST verify-website-2fa
  ServerInfoApi->>WP2FA: validate TOTP token
  ServerInfoApi-->>SecurityPage: {success} + set unlock transient
  SecurityPage-->>User: reveal WP2FA management panel
  User->>SecurityPage: POST remove in-game 2FA
  SecurityPage->>ServerInfoApi: POST remove-ingame-2fa
  ServerInfoApi->>GameDB: UPDATE account SET totp_secret=NULL
  ServerInfoApi-->>SecurityPage: {success}
  SecurityPage-->>User: reload page
  User->>ConnectionLogger: Login (wp_login hook)
  ConnectionLogger->>GameDB: query last_ip
  ConnectionLogger-->>ConnectionLogger: INSERT acore_login_history
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 Hop, hop through the code so wide,
Two-factor gates on every side,
Dark mode gleams, the nonces bloom,
GeoIP lights each login room,
TOTP ticks and passwords hash —
Security delivered, rather fast! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding CSRF protection via nonces across admin settings forms and introducing administrative settings overhaul.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/acore-wp-plugin/src/Components/Tools/ToolsApi.php (1)

9-10: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Return REST errors for invalid item-list requests.

getRestorableItemsByCharacter() now throws for invalid or not-owned characters; without catching that here, a tampered REST request can surface as a 500 instead of a controlled 400/403 response.

Proposed fix
     public static function ItemRestoreList($request) {
-        return ACoreServices::I()->getRestorableItemsByCharacter($request['cguid']);
+        try {
+            return ACoreServices::I()->getRestorableItemsByCharacter($request['cguid']);
+        } catch (\InvalidArgumentException $e) {
+            return new \WP_Error('invalid_character', 'Character not found.', ['status' => 403]);
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/acore-wp-plugin/src/Components/Tools/ToolsApi.php` around lines 9 - 10,
The ItemRestoreList method currently does not handle exceptions thrown by
getRestorableItemsByCharacter(), which means invalid or not-owned character
requests will result in unhandled exceptions and 500 errors. Wrap the call to
getRestorableItemsByCharacter() in a try-catch block to catch any exceptions
thrown by that method, and return appropriate REST error responses (such as 400
for invalid requests or 403 for unauthorized/not-owned characters) instead of
allowing the exception to propagate and cause a 500 error.
src/acore-wp-plugin/src/Hooks/User/User.php (1)

105-111: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Apply the password-history policy to native password-change paths too.

These hooks now update acore_password_changed_at, but they still sync passwords without enforcing acore_password_is_reused() or recording the old hash. That lets profile edits/admin resets/password resets bypass the allow_old_passwords policy enforced by the Security page handler.

Also applies to: 126-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/acore-wp-plugin/src/Hooks/User/User.php` around lines 105 - 111, The
password update logic in the User class is bypassing the password history policy
enforcement. Before calling setAccountPassword() in the password change handler,
you need to check whether the new password is being reused by calling
acore_password_is_reused() with the user login and new password. If the function
returns true and the allow_old_passwords policy is disabled, prevent the
password change and display an appropriate error message to the user.
Additionally, before updating the password with setAccountPassword(), capture
and record the current password hash by storing it in user metadata (similar to
how the old hash should be preserved for the password history policy). This
ensures that profile edits, admin password resets, and password reset paths all
enforce the same password reuse restrictions as the Security page handler.
🧹 Nitpick comments (2)
src/acore-wp-plugin/src/Utils/AcoreCharColors.php (1)

84-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unused $fDark assignment.

Line 88 computes a dark faction color, but the returned style never uses it, so this just creates static-analysis noise and makes the styling contract look broader than it is.

Proposed cleanup
     public static function rowStyle(int $classId, int $raceId): string {
         $cls     = self::CLASS_COLORS[$classId]  ?? ['light' => self::FALLBACK_LIGHT, 'dark' => self::FALLBACK_DARK];
         $faction = self::RACE_FACTION[$raceId]   ?? 'unknown';
         $fLight  = $faction === 'alliance' ? '`#3FACF4`' : '`#FF653D`';
-        $fDark   = $faction === 'alliance' ? '`#3FACF4`' : '`#FF653D`';
         return sprintf(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/acore-wp-plugin/src/Utils/AcoreCharColors.php` around lines 84 - 93, In
the rowStyle method, the variable $fDark is assigned on line 88 but is never
used in the sprintf return statement that follows it. Remove the unused $fDark
assignment to clean up unnecessary code and reduce static analysis noise, as
only $fLight is utilized in the returned style string.

Source: Linters/SAST tools

src/acore-wp-plugin/src/Components/UserPanel/UserController.php (1)

217-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the stale getModel() return contract.

Line 220 now returns the controller itself, but the PHPDoc still advertises UserModel, which is the contract PHPStan is checking. Update the docblock or return a real model.

Proposed fix
     /**
-     * `@return` UserModel
+     * `@return` self
      */
     public function getModel() {
         return $this;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/acore-wp-plugin/src/Components/UserPanel/UserController.php` around lines
217 - 221, The getModel() method in the UserController class has a PHPDoc that
declares `@return` UserModel, but the method actually returns $this (the
controller instance), causing a type mismatch that PHPStan will flag. Either
update the `@return` docblock to accurately reflect that it returns UserController
(self), or modify the implementation to return an actual UserModel instance
instead of the controller itself.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/acore-wp-plugin/src/Components/AdminPanel/Pages/PVPRewards.php`:
- Line 17: The nonce field acore_pvp_rewards_nonce added via wp_nonce_field() on
line 17 is being included in the GET request URL when the form is submitted for
Preview (lines 247-251), which leaks the CSRF token via referrers and browser
history. Fix this by splitting the form into two separate forms: one for Preview
that submits as GET without the nonce field, and one for Send that submits as
POST with the wp_nonce_field() call. Alternatively, conditionally exclude the
nonce field when the preview action is triggered, ensuring the nonce is only
sent with POST requests and not exposed in preview URLs.

In `@src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php`:
- Around line 180-186: The hidden input field for acore_geoip_lookup is only
created when Security Logging is disabled (inside the conditional if
acore_security_logging != '1'). When Security Logging is enabled and then later
disabled via JavaScript, the select element gets disabled and won't submit any
value, allowing the old GeoIP setting to persist. To fix this, ensure that a
fallback hidden input with acore_geoip_lookup value of 0 is ALWAYS submitted
regardless of the Security Logging state. Remove the conditional wrapper around
the hidden input so it is always present in the form, guaranteeing that a
default value of 0 will be submitted even when the select element is disabled.
- Around line 255-262: When all rows are deleted from the
acore-name-unlock-thresholds table, the form submission does not include the
acore_name_unlock_thresholds key, causing SettingsController::loadTools() to
skip updating it and leaving stale data in the database. Add a hidden marker
field (acore_name_unlock_thresholds_present) to the table in Tools.php that
indicates the thresholds section was submitted, then in
SettingsController::loadTools() check for this marker before the option update
loop and explicitly clear the acore_name_unlock_thresholds option by calling
storeConf with an empty array if the marker exists but the thresholds key is
absent in $_POST. Apply the same pattern to the other affected tables noted in
the comment (lines 588-590).

In `@src/acore-wp-plugin/src/Components/MailReturnMenu/MailReturnView.php`:
- Around line 58-59: The src attributes in the race-icon and class-icon img tags
are not escaped with esc_url(), creating a security inconsistency with other
character views. Wrap both src attribute values (the concatenated URL strings
for ACORE_URL_PLG . 'web/assets/race/...' and ACORE_URL_PLG .
'web/assets/class/...') with the esc_url() function to properly escape the URLs
before they are output in the HTML.

In `@src/acore-wp-plugin/src/Components/ServerInfo/ServerInfoApi.php`:
- Around line 350-353: The issue is that checking if affected rows equals zero
after the UPDATE statement in the executeStatement call is unreliable for
determining account existence, because updating totp_secret to NULL returns 0
affected rows when the account exists but the field is already NULL. Instead of
relying on the affected rows count, first verify the account exists using a
separate SELECT query (checking if the account with the given username exists),
and only if the account is confirmed to exist, then proceed with the UPDATE
statement. This way the account existence check is independent from whether the
field was actually changed, preventing false "No game account" errors from
double-clicks or race conditions.
- Around line 200-208: The code calls ServerInfoApi::serverInfo() and
immediately uses stripos() to scan the result against error patterns, but if
serverInfo() throws an exception or returns a non-scalar value (like an
Exception object), stripos() will fail fatally instead of gracefully returning a
503 response. Wrap the ServerInfoApi::serverInfo() call in a try-catch block to
handle any exceptions, and normalize the result to ensure it is a scalar string
value before passing it to the stripos() calls in the foreach loop that scans
for error patterns.
- Around line 447-456: The fresh TOTP code validation path in the in-game 2FA
removal endpoint lacks attempt rate limiting, allowing unlimited brute-force
attacks on the token validation. Apply the same 2FA attempt limiter mechanism
that is used in the verify-website-2fa endpoint to this code path. Specifically,
before and after validating the token with the acore_wp2fa_code_is_valid()
function, implement attempt tracking that increments on failed validation
(including invalid token format checks) and blocks further attempts after a
threshold is reached, returning an appropriate error when the limit is exceeded.
- Around line 124-136: The functions acore_website_totp_enabled() and
acore_website_2fa_enabled() do not verify that the WP2FA plugin is actually
active before reading and processing its metadata. This causes stale
wp_2fa_enabled_methods and wp_2fa_totp_key metadata from a disabled plugin to
incorrectly report 2FA as enabled. Both functions should check if the WP2FA
plugin is active at the start and return false immediately if it is not
available, ensuring that password changes are not blocked when the plugin is
inactive.

In `@src/acore-wp-plugin/src/Components/Tools/ToolsApi.php`:
- Around line 14-22: The function validates that the item ID is a valid integer
and that the character name belongs to the current user, but it does not verify
that the recovery item ID actually belongs to that character or account before
executing the restore command. Add an authorization check after the
currentAccountOwnsCharacterName validation and before the executeCommand call to
verify that the recovery item specified by the item variable is owned by or
associated with the character identified by cname, returning a WP_Error with
appropriate status code if the authorization fails.

In `@src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php`:
- Line 62: The hearthstone image URL on line 62 in the img src attribute is
echoing the asset path directly without escaping, while adjacent race and class
icon URLs use esc_url() for security. Wrap the ACORE_URL_PLG constant and the
hearthstone.jpg path with the esc_url() function to match the security pattern
used for the other icon URLs in the same file.

In `@src/acore-wp-plugin/src/Components/UserPanel/Pages/ItemRestorationPage.php`:
- Around line 8-16: The translation domain parameter in the _e() function calls
throughout the ItemRestorationPage.php file is using Opts::I()->page_alias,
which is incorrect. The Opts class exposes acore_page_alias as the proper
translation domain constant. Replace all instances of Opts::I()->page_alias with
acore_page_alias in all _e() function calls across the file (at lines 8, 20, 42,
54, and 66 as mentioned in the review comment) to ensure the translations
resolve with the correct domain.
- Around line 114-123: The selectCharacter function initiates fetch requests
that can race when users click characters quickly, allowing stale responses from
older requests to overwrite newer selections. Implement request sequencing by
storing a request identifier (such as a timestamp or counter) at the start of
selectCharacter, and before processing the response in the then handlers (where
the grid is updated around lines 120-202), verify that the current response
matches the latest request identifier. Only proceed with updating the grid and
content if the response is from the most recent request; otherwise, ignore it as
stale data. This ensures that out-of-order responses from slower network
requests do not corrupt the current character's item list.

In `@src/acore-wp-plugin/src/Components/UserPanel/Pages/SecurityPage.php`:
- Around line 220-267: The security issue is that the $wp2faHtml containing
sensitive 2FA settings and backup codes is being rendered in the page source
with display:none CSS in both the websiteTotpEnabled and websiteEmailEnabled
branches, making it accessible by viewing source code before verification.
Remove the hidden acore-2fa-panel div containing $wp2faHtml from both the elseif
($websiteTotpEnabled) and elseif ($websiteEmailEnabled) conditional blocks
entirely. Instead, after successful TOTP or email verification (when the unlock
transient is set server-side), either reload the page so $twofaUnlocked becomes
true and the panel renders, or fetch the panel content via AJAX and inject it
into the DOM only after successful verification. This ensures sensitive content
is never included in the initial page source until the user has been properly
authenticated.

In `@src/acore-wp-plugin/src/Components/UserPanel/UserController.php`:
- Around line 54-65: The IP detection logic in the RAF abuse check section of
UserController.php uses spoofable headers (HTTP_CLIENT_IP and
HTTP_X_FORWARDED_FOR) as the primary source, allowing users to bypass the
same-IP RAF block. Replace this insecure IP detection with the trusted-proxy
resolver that is already used elsewhere in the codebase for connection logging,
and fall back to REMOTE_ADDR as the trusted source. Specifically, in the block
where check_ip is enabled and $activeUserIp is being determined, use the
existing trusted-proxy resolver instead of checking the client-supplied headers
first.

In `@src/acore-wp-plugin/src/Hooks/User/ConnectionLogger.php`:
- Around line 15-24: The ConnectionLogger class creates a table that stores user
IP history indefinitely without any data retention mechanism. Add a
configuration option to define a retention period (in days) for login records,
then implement a scheduled cleanup method (likely using WordPress cron hooks)
that runs periodically to delete login_at records older than the retention
period. This retention logic should be applied to the table creation in the
ConnectionLogger class constructor and referenced at the other locations
mentioned (lines 44-54, 94-104) to ensure consistent data minimization practices
across all table operations.

In `@src/acore-wp-plugin/src/Hooks/User/User.php`:
- Around line 760-764: The code that displays the "Website 2FA removed" message
in the printf statement does not handle self-removal entries properly. When a
2FA removal is marked as self-removal (with by => self), the lastWebRemoval
array contains no staff key, which causes an undefined-index warning and
displays incorrect attribution. Add a conditional check before the printf
statement to determine if the removal was self-initiated or by staff, and
display appropriate text accordingly. For self-removals, display a message
indicating the user removed it themselves, and for staff removals, use the
existing message that includes the staff name. This ensures the warning displays
correct information without undefined-index errors regardless of removal type.

In `@src/acore-wp-plugin/web/assets/mail-return/mail-return.js`:
- Around line 28-33: The issue is that the return-success cleanup code only
shows the child message but doesn't hide the content wrapper or show the empty
wrapper when the final mail is returned. Find the return-success cleanup handler
(the code that executes after a mail is successfully returned) and mirror the
empty-state logic from the load path: after handling the successful return,
check if there are no more mails remaining, and if so, hide the content wrapper
and show the emptyWrap element, similar to how it's done in the conditional
check at the start of the load path.
- Around line 60-63: The factionClr function receives raceId values that may be
strings from the REST API, but the allianceRaces array contains numbers, causing
indexOf to always fail and return incorrect faction colors. Convert the raceId
parameter to a number using parseInt, Number conversion, or the unary plus
operator before performing the indexOf comparison against the allianceRaces
array to ensure proper type matching.

---

Outside diff comments:
In `@src/acore-wp-plugin/src/Components/Tools/ToolsApi.php`:
- Around line 9-10: The ItemRestoreList method currently does not handle
exceptions thrown by getRestorableItemsByCharacter(), which means invalid or
not-owned character requests will result in unhandled exceptions and 500 errors.
Wrap the call to getRestorableItemsByCharacter() in a try-catch block to catch
any exceptions thrown by that method, and return appropriate REST error
responses (such as 400 for invalid requests or 403 for unauthorized/not-owned
characters) instead of allowing the exception to propagate and cause a 500
error.

In `@src/acore-wp-plugin/src/Hooks/User/User.php`:
- Around line 105-111: The password update logic in the User class is bypassing
the password history policy enforcement. Before calling setAccountPassword() in
the password change handler, you need to check whether the new password is being
reused by calling acore_password_is_reused() with the user login and new
password. If the function returns true and the allow_old_passwords policy is
disabled, prevent the password change and display an appropriate error message
to the user. Additionally, before updating the password with
setAccountPassword(), capture and record the current password hash by storing it
in user metadata (similar to how the old hash should be preserved for the
password history policy). This ensures that profile edits, admin password
resets, and password reset paths all enforce the same password reuse
restrictions as the Security page handler.

---

Nitpick comments:
In `@src/acore-wp-plugin/src/Components/UserPanel/UserController.php`:
- Around line 217-221: The getModel() method in the UserController class has a
PHPDoc that declares `@return` UserModel, but the method actually returns $this
(the controller instance), causing a type mismatch that PHPStan will flag.
Either update the `@return` docblock to accurately reflect that it returns
UserController (self), or modify the implementation to return an actual
UserModel instance instead of the controller itself.

In `@src/acore-wp-plugin/src/Utils/AcoreCharColors.php`:
- Around line 84-93: In the rowStyle method, the variable $fDark is assigned on
line 88 but is never used in the sprintf return statement that follows it.
Remove the unused $fDark assignment to clean up unnecessary code and reduce
static analysis noise, as only $fLight is utilized in the returned style string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 044ea6ab-4d9c-4513-9abe-e55fc6d5e4b2

📥 Commits

Reviewing files that changed from the base of the PR and between 94af292 and 80e07dc.

📒 Files selected for processing (35)
  • src/acore-wp-plugin/src/Components/AdminPanel/Pages/ElunaSettings.php
  • src/acore-wp-plugin/src/Components/AdminPanel/Pages/PVPRewards.php
  • src/acore-wp-plugin/src/Components/AdminPanel/Pages/RealmSettings.php
  • src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php
  • src/acore-wp-plugin/src/Components/AdminPanel/SettingsController.php
  • src/acore-wp-plugin/src/Components/CharactersMenu/CharactersController.php
  • src/acore-wp-plugin/src/Components/CharactersMenu/CharactersView.php
  • src/acore-wp-plugin/src/Components/MailReturnMenu/MailReturnController.php
  • src/acore-wp-plugin/src/Components/MailReturnMenu/MailReturnView.php
  • src/acore-wp-plugin/src/Components/ResurrectionScrollMenu/ResurrectionScrollMenu.php
  • src/acore-wp-plugin/src/Components/ResurrectionScrollMenu/ResurrectionScrollView.php
  • src/acore-wp-plugin/src/Components/ServerInfo/ServerInfoApi.php
  • src/acore-wp-plugin/src/Components/Tools/ToolsApi.php
  • src/acore-wp-plugin/src/Components/UnstuckMenu/UnstuckView.php
  • src/acore-wp-plugin/src/Components/UserPanel/Pages/ItemRestorationPage.php
  • src/acore-wp-plugin/src/Components/UserPanel/Pages/RafProgressPage.php
  • src/acore-wp-plugin/src/Components/UserPanel/Pages/SecurityPage.php
  • src/acore-wp-plugin/src/Components/UserPanel/UserController.php
  • src/acore-wp-plugin/src/Components/UserPanel/UserMenu.php
  • src/acore-wp-plugin/src/Components/UserPanel/UserView.php
  • src/acore-wp-plugin/src/Hooks/User/ConnectionLogger.php
  • src/acore-wp-plugin/src/Hooks/User/Include.php
  • src/acore-wp-plugin/src/Hooks/User/PasswordHandler.php
  • src/acore-wp-plugin/src/Hooks/User/User.php
  • src/acore-wp-plugin/src/Hooks/Various/DarkMode.php
  • src/acore-wp-plugin/src/Hooks/Various/tgmplugin_activator.php
  • src/acore-wp-plugin/src/Manager/ACoreServices.php
  • src/acore-wp-plugin/src/Manager/Opts.php
  • src/acore-wp-plugin/src/Manager/Soap/SmartstoneService.php
  • src/acore-wp-plugin/src/Utils/AcoreCharColors.php
  • src/acore-wp-plugin/src/boot.php
  • src/acore-wp-plugin/web/assets/css/dark-mode.css
  • src/acore-wp-plugin/web/assets/css/main.css
  • src/acore-wp-plugin/web/assets/css/theme.css
  • src/acore-wp-plugin/web/assets/mail-return/mail-return.js

Comment thread src/acore-wp-plugin/src/Components/AdminPanel/Pages/PVPRewards.php
Comment thread src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php
Comment thread src/acore-wp-plugin/src/Components/AdminPanel/Pages/Tools.php
Comment thread src/acore-wp-plugin/src/Components/ServerInfo/ServerInfoApi.php
Comment thread src/acore-wp-plugin/src/Components/UserPanel/UserController.php
Comment thread src/acore-wp-plugin/src/Hooks/User/ConnectionLogger.php
Comment thread src/acore-wp-plugin/src/Hooks/User/User.php
Comment thread src/acore-wp-plugin/web/assets/mail-return/mail-return.js
Comment thread src/acore-wp-plugin/web/assets/mail-return/mail-return.js
TheSCREWEDSoftware added a commit to TheSCREWEDSoftware/acore-cms that referenced this pull request Jun 24, 2026
(Resume generated by Claude)

**PR azerothcore#199 — Characters Menu**
- Fixed typo: "succesfully" → "successfully" in save confirmation
- Fixed broken sentence in the Order Character Screen description
- Extended `user-select: none` to cover all `.acore-char-row` elements, not just `button`

**PR azerothcore#200 — Mail Return**
- *(issues covered by later PRs — no unique fixes)*

**PR azerothcore#201 — Item Restoration**
- Added `alt=` attributes to race/class icons in `CharactersView.php`
- `ToolsApi`: `ItemRestoreList` now catches exceptions and returns a 403 instead of a 500
- `ToolsApi` + `ACoreServices`: **Security fix** — `ItemRestore` now verifies the recovery item belongs to the character, not just that the user owns the character name (IDOR vulnerability)

**PR azerothcore#202 — Unstuck / Scroll / RAF**
- `ItemRestorationPage`: removed brittle `data.includes('mail')` success check — any HTTP success now triggers the success path

**PR azerothcore#203 — Security Tab**
- `CharactersController`: `resetCharacterOrder()` now guards against invalid/null account ID before running UPDATE
- `Tools.php`: added `esc_attr()` on banned-names table input (XSS fix)
- `Tools.php`: added `is_array()` guard on thresholds `foreach`
- `Tools.php` + `SettingsController`: added sentinel input so deleting all thresholds actually clears them in the DB
- `Tools.php`: GeoIP hidden fallback `value="0"` is now always rendered, not conditionally

**PR azerothcore#204 — Admin Settings / CSRF**
- `AcoreCharColors`: removed unused `$fDark`, unknown race IDs now get a neutral color instead of Horde red
- `MailReturnView`: added `esc_url()` on race/class icon `src` attributes
- `DarkMode`: added `dmToggleInFlight` flag to prevent overlapping AJAX requests on rapid dark mode clicks
@TheSCREWEDSoftware

Copy link
Copy Markdown
Contributor Author

The reviews are valid for this, latest PR #205

PR #204 — Admin Settings / CSRF

  • AcoreCharColors: removed unused $fDark, unknown race IDs now get a neutral color instead of Horde red
  • MailReturnView: added esc_url() on race/class icon src attributes
  • DarkMode: added dmToggleInFlight flag to prevent overlapping AJAX requests on rapid dark mode clicks

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