WordPress makes two assumptions this project cannot accept: that identity comes from an email address, and that a site lives at one server. Both are things somebody else can take away — a mailbox is rented from a provider who can close it or be compelled to open it, and a server is one seizure from being gone. Sirius Press replaces the first and hedges the second. Signing in means signing a challenge with the key that controls a CashAddress. The address is recovered from the signature, so nothing is typed but the signature itself, and the result is an ordinary WordPress session cookie — roles, capabilities, nonces and the REST API never learn the login was different. Three ways to produce one: a wallet the browser already exposes, a phrase used once in the page and wiped, or a signature pasted in from any BIP-137 wallet, which needs no JavaScript and lets the key stay on a machine that never touches the web. There is no password reset, and the recovery page says so plainly rather than offering a form that cannot work. A reset mechanism is by construction a way to take an account from its owner, and it is always easier to attack than the cryptography it bypasses. Publishing a post also exports it as static HTML to the name's storage on Sia, signed by the key that owns the name, so the site keeps answering when the server does not. Email as a feature is untouched. wp_mail() still works, SMTP still sends, and contact forms still deliver to addresses real people typed. Only mail to the site's own unroutable placeholder addresses is diverted to an in-app inbox. The objection was to email as identity, not to email. Core is pinned and patched rather than vendored. WordPress 7.1.1 is 149 MB and 5,008 files; the fork's entire core diff is 75 lines in wp-admin/install.php. Carrying the former to express the latter would bury the patch where nobody reviews it and make every clone of the monorepo pay for it. Upstream releases still merge through tools/update-wordpress.sh, which reapplies the series and says exactly which hunk needs a human. The cryptography is implemented twice — PHP on the server, JavaScript in the page — because the server must verify and the browser must sign. Both are pinned against libauth, the library the Sirius portal wallet and the BNS gateway already use, so a disagreement of one byte fails the test suite rather than presenting as a rejected login at three in the morning. 132 checks, no framework, about a second.
143 lines
5.1 KiB
PHP
143 lines
5.1 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'] ) );
|
|
}
|
|
|
|
$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 '</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,
|
|
)
|
|
);
|
|
}
|
|
}
|