sirius-press/plugins/sirius-press-auth/includes/class-spa-settings.php
Local Dev c403a42904 sirius-press: let the sign-in screen move off wp-login.php
wp-login.php is the most-requested path on the internet. There is no
password here to guess, so this is not about brute force — it is that a
wallet login screen answering that path tells every scanner that goes
past exactly what is running, and serves a few hundred bots a day a page
they cannot use.

Setting a login address moves the screen and makes wp-login.php answer
404 through the theme's own template. A 404 and not a redirect: a
redirect would hand the new address to the first scanner that asked for
the old one. Everything that builds a login URL follows it, because they
all go through site_url() or network_site_url() with a wp-login.php
path, and there is a wp_redirect backstop for code that builds the URL
by hand.

Refusing wp-login.php has to happen at wp_loaded and has to end the
request there. The web server sends that path to the file directly
rather than through index.php, so by the time anything of ours runs we
are already inside its require of wp-load.php — rewriting $pagenow
changes what WordPress thinks the request is, but it does not stop the
script that is already running.

What this does not do is rename wp-admin. WordPress has no supported way
to, and faking it with a proxy and a filtered admin_url() gets most of
the way before breaking plugins quietly. Breaking plugins is the one
thing this fork promises not to do, so the setting says as much on the
screen rather than implying more than it delivers.
2026-10-03 18:42:45 +02:00

188 lines
7.4 KiB
PHP

<?php
/**
* The one auth decision a site owner has to make.
*
* Passwords stay on by default, and that is not timidity — a fresh install
* has exactly one account, created by the installer, with no wallet attached
* yet. Shipping with passwords off would mean the first thing a new site does
* is lock out its own administrator.
*
* The screen therefore refuses to turn passwords off until the person doing
* it has a wallet on their own account. That check is the difference between
* a setting and a trapdoor.
*
* @package SiriusPress
*/
defined( 'ABSPATH' ) || exit;
final class SPA_Settings {
const PAGE = 'sirius-press-auth';
public static function hooks() {
add_action( 'admin_menu', array( __CLASS__, 'menu' ), 20 );
}
public static function menu() {
add_submenu_page(
'sirius-press',
__( 'Sign-in', 'sirius-press' ),
__( 'Sign-in', 'sirius-press' ),
'manage_options',
self::PAGE,
array( __CLASS__, 'render' )
);
}
public static function render() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to configure this site.', 'sirius-press' ) );
}
$errors = array();
$notes = array();
if ( isset( $_POST['sirius_auth_settings'] ) && check_admin_referer( 'sirius_auth_settings' ) ) {
$want_passwords = ! empty( $_POST['sirius_allow_passwords'] );
if ( ! $want_passwords && '' === sirius_press_address() ) {
$errors[] = __( 'Attach a wallet to your own account first. Turning passwords off right now would lock you out of this site with no way back in.', 'sirius-press' );
} else {
update_option( SPA_Login::OPT_ALLOW_PASSWORDS, $want_passwords );
$notes[] = $want_passwords
? __( 'Password sign-in is on.', 'sirius-press' )
: __( 'Password sign-in is off. Only wallet signatures are accepted from now on.', 'sirius-press' );
}
update_option( SP_Settings::OPT_OPEN_REG, ! empty( $_POST['sirius_open_registration'] ) );
$raw = isset( $_POST['sirius_login_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['sirius_login_slug'] ) ) : '';
$slug = SPA_Login_URL::sanitize( $raw );
$slug_issues = SPA_Login_URL::problems( $raw );
if ( $slug_issues ) {
$errors = array_merge( $errors, $slug_issues );
} elseif ( $slug !== SPA_Login_URL::slug() ) {
if ( '' === $slug ) {
delete_option( SPA_Login_URL::OPT_SLUG );
$notes[] = __( 'The login screen is back at wp-login.php.', 'sirius-press' );
} else {
update_option( SPA_Login_URL::OPT_SLUG, $slug );
$notes[] = sprintf(
/* translators: %s: the new login URL. */
__( 'The login screen has moved to %s. Save it somewhere — wp-login.php now answers 404.', 'sirius-press' ),
home_url( '/' . $slug )
);
}
}
}
$stranded = self::accounts_without_wallets();
echo '<div class="wrap"><h1>' . esc_html__( 'Sign-in', 'sirius-press' ) . '</h1>';
foreach ( $errors as $e ) {
printf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $e ) );
}
foreach ( $notes as $n ) {
printf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( $n ) );
}
echo '<form method="post">';
wp_nonce_field( 'sirius_auth_settings' );
echo '<input type="hidden" name="sirius_auth_settings" value="1">';
echo '<table class="form-table" role="presentation">';
echo '<tr><th>' . esc_html__( 'Passwords', 'sirius-press' ) . '</th><td><label>';
printf(
'<input type="checkbox" name="sirius_allow_passwords" value="1"%s> %s',
checked( SPA_Login::passwords_allowed(), true, false ),
esc_html__( 'Also accept username and password sign-in', 'sirius-press' )
);
echo '</label><p class="description">';
esc_html_e( 'Leave this on until every account that needs access has a wallet attached. With it off, a wallet signature is the only way in — which is the point of this fork, but it is also final.', 'sirius-press' );
echo '</p></td></tr>';
echo '<tr><th>' . esc_html__( 'Registration', 'sirius-press' ) . '</th><td><label>';
printf(
'<input type="checkbox" name="sirius_open_registration" value="1"%s> %s',
checked( SP_Settings::open_registration(), true, false ),
esc_html__( 'Anyone who can sign with a wallet may create an account', 'sirius-press' )
);
echo '</label></td></tr>';
echo '<tr><th>' . esc_html__( 'Login address', 'sirius-press' ) . '</th><td>';
printf(
'<code>%s</code> <input type="text" name="sirius_login_slug" value="%s" class="regular-text" placeholder="%s" autocomplete="off" spellcheck="false">',
esc_html( trailingslashit( home_url( '/' ) ) ),
esc_attr( SPA_Login_URL::slug() ),
esc_attr__( 'wp-login.php', 'sirius-press' )
);
echo '<p class="description">';
esc_html_e( 'Move the sign-in screen somewhere other than wp-login.php. Leave it empty to keep the usual address.', 'sirius-press' );
echo ' ';
esc_html_e( 'This is not about brute force — there is no password to guess — but wp-login.php is the most-requested path on the internet, and answering it tells every scanner what is running here. Note that it does not move wp-admin: WordPress has no supported way to rename that, and pretending otherwise breaks plugins quietly.', 'sirius-press' );
echo '</p>';
if ( SPA_Login_URL::slug() ) {
echo '<p class="description"><strong>';
printf(
/* translators: %s: the current login URL. */
esc_html__( 'Signing in now happens at %s.', 'sirius-press' ),
'<code>' . esc_html( home_url( '/' . SPA_Login_URL::slug() ) ) . '</code>'
);
echo '</strong> ';
esc_html_e( 'If you lose it, `wp option get sirius_press_login_slug` on the server will tell you, and `wp option delete sirius_press_login_slug` will put the login back.', 'sirius-press' );
echo '</p>';
}
echo '</td></tr>';
echo '</table>';
submit_button();
echo '</form>';
echo '<h2>' . esc_html__( 'Accounts without a wallet', 'sirius-press' ) . '</h2>';
if ( ! $stranded ) {
echo '<p>' . esc_html__( 'Every account here can sign in with a wallet.', 'sirius-press' ) . '</p>';
} else {
echo '<p>' . esc_html__( 'These accounts can only sign in with a password. Turning passwords off locks them out.', 'sirius-press' ) . '</p>';
echo '<table class="widefat striped" style="max-width:640px"><thead><tr><th>'
. esc_html__( 'Account', 'sirius-press' ) . '</th><th>'
. esc_html__( 'Role', 'sirius-press' ) . '</th></tr></thead><tbody>';
foreach ( $stranded as $user ) {
printf(
'<tr><td><a href="%s">%s</a></td><td>%s</td></tr>',
esc_url( get_edit_user_link( $user->ID ) ),
esc_html( $user->user_login ),
esc_html( implode( ', ', $user->roles ) )
);
}
echo '</tbody></table>';
}
echo '<h2>' . esc_html__( 'For other plugins', 'sirius-press' ) . '</h2>';
echo '<p>' . esc_html__( 'A plugin that wants a fresh signature before something irreversible can call sirius_press_confirmation_challenge() and post the answer to:', 'sirius-press' ) . '</p>';
echo '<p><code>' . esc_html( rest_url( 'sirius-press/v1/confirm' ) ) . '</code></p>';
echo '</div>';
}
/** Accounts that would be locked out if passwords were turned off. */
private static function accounts_without_wallets() {
return get_users(
array(
'meta_query' => array(
'relation' => 'OR',
array(
'key' => SP_Identity::META_ADDRESS,
'compare' => 'NOT EXISTS',
),
array(
'key' => SP_Identity::META_ADDRESS,
'value' => '',
'compare' => '=',
),
),
'number' => 50,
)
);
}
}