Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions includes/admin/class-wpum-options-panel.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ public function register_settings( $settings ) {
'value' => true,
),
),
array(
'id' => 'generic_login_errors',
'name' => __( 'Generic Login Error', 'wp-user-manager' ),
'desc' => __( 'Replace specific login error messages with a generic message to prevent username enumeration.', 'wp-user-manager' ),
'type' => 'checkbox',
Comment on lines +247 to +251
),
),
'misc' => array(
array(
Expand Down
39 changes: 39 additions & 0 deletions includes/filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,45 @@
}
add_filter( 'authenticate', 'wpum_authentication', 20, 3 );

/**
* Replace specific login error messages with a generic message
* to prevent username enumeration when the setting is enabled.
*
* @param WP_User|WP_Error $user
* @param string $username
* @param string $password
*
* @return WP_User|WP_Error
Comment on lines +189 to +193
*/
function wpum_generic_login_errors( $user, $username, $password ) {

Check warning on line 195 in includes/filters.php

View workflow job for this annotation

GitHub Actions / PHPCS

The method parameter $password is never used
if ( ! is_wp_error( $user ) || empty( $username ) ) {
return $user;
}

if ( ! wpum_get_option( 'generic_login_errors' ) ) {
return $user;
}

$code = $user->get_error_code();

$enumeration_codes = array(
'invalid_username',
'invalid_email',
'incorrect_password',
'email_only',
);

if ( in_array( $code, $enumeration_codes, true ) ) {
return new WP_Error(
'authentication_failed',
apply_filters( 'wpum_generic_login_error_message', __( '<strong>Error:</strong> The username or password you entered is incorrect.', 'wp-user-manager' ) )
);
}

return $user;
}
add_filter( 'authenticate', 'wpum_generic_login_errors', 50, 3 );

/**
* Highlight all pages used by WPUM.
*
Expand Down
33 changes: 32 additions & 1 deletion tests/e2e/login.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect } from './fixtures';
import { test, expect, wpCli } from './fixtures';

test.describe('Login Form', () => {
test.beforeEach(async ({ page }) => {
Expand Down Expand Up @@ -108,6 +108,37 @@ test.describe('Login Form', () => {
await expect(page).toHaveURL(/wp-login\.php/);
});

test('generic login errors hides username enumeration', async ({ page, loginPage }) => {
wpCli(`eval 'wpum_update_option("generic_login_errors", true);'`);

await page.goto(loginPage);
Comment on lines +112 to +114
await page.locator('#username').fill('nonexistent_user_12345');
await page.locator('#password').fill('WrongPassword123!');
await page.locator('input[name="submit_login"]').click();
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});

const errorMessage = page.locator('.wpum-message.error');
await expect(errorMessage).toBeVisible({ timeout: 5000 });
const errorText = await errorMessage.textContent();
expect(errorText).toContain('username or password you entered is incorrect');
expect(errorText).not.toContain('not registered');
expect(errorText).not.toContain('Unknown');

Comment on lines +120 to +126
await page.goto(loginPage);
await page.locator('#username').fill('testuser_login');
await page.locator('#password').fill('WrongPassword123!');
await page.locator('input[name="submit_login"]').click();
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});

const errorMessage2 = page.locator('.wpum-message.error');
await expect(errorMessage2).toBeVisible({ timeout: 5000 });
const errorText2 = await errorMessage2.textContent();
expect(errorText2).toContain('username or password you entered is incorrect');
expect(errorText2).not.toContain('incorrect password');
Comment on lines +133 to +137

wpCli(`eval 'wpum_update_option("generic_login_errors", false);'`);
});
Comment on lines +139 to +140

test('redirect after login', async ({ page, loginPage }) => {
await page.goto(loginPage);

Expand Down
Loading