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.
131 lines
4.9 KiB
PHP
131 lines
4.9 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Sirius Press Bootstrap
|
|
* Description: Loads before everything else, so the parts of WordPress that run before plugins do — the installer, and core's pluggable notification functions — behave on a site with no email identity.
|
|
* Version: 0.1.0
|
|
* License: GPL-2.0-or-later
|
|
*
|
|
* @package SiriusPress
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
*
|
|
* Must-use plugins are loaded by `wp-settings.php` at a point where two useful
|
|
* things are still true: ordinary plugins have not loaded (so this runs during
|
|
* the installer, when they are skipped entirely), and `pluggable.php` has not
|
|
* been included yet (so functions declared here win over core's).
|
|
*
|
|
* Both properties are needed, and neither is available anywhere else:
|
|
*
|
|
* - The patched `wp-admin/install.php` calls two helpers that must exist
|
|
* before any plugin could have defined them.
|
|
* - Core's mail notifications are pluggable functions. Overriding them is the
|
|
* only way to stop the installer and the user system from handing messages
|
|
* to a mail transport that has nowhere to send them.
|
|
*
|
|
* This file is deliberately tiny and dependency-free. Anything that can wait
|
|
* for `plugins_loaded` belongs in Sirius Press Core instead.
|
|
*/
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
define( 'SIRIUS_PRESS_BOOTSTRAP', '0.1.0' );
|
|
|
|
/**
|
|
* Validate and normalise an address typed into the installer.
|
|
*
|
|
* @param string $address
|
|
* @return string|false Normalised address, '' when blank, false when invalid.
|
|
*/
|
|
function sirius_press_install_address( $address ) {
|
|
$address = trim( (string) $address );
|
|
if ( '' === $address ) {
|
|
return '';
|
|
}
|
|
$class = WP_PLUGIN_DIR . '/sirius-press-core/includes/class-sp-cashaddr.php';
|
|
if ( ! class_exists( 'SP_CashAddr' ) && is_readable( $class ) ) {
|
|
require_once $class;
|
|
}
|
|
if ( ! class_exists( 'SP_CashAddr' ) ) {
|
|
// Core plugin not present. Refusing here would make the site
|
|
// uninstallable; accepting an unchecked string would attach an
|
|
// account to an address nobody can prove. Reject the value and let
|
|
// the operator install without one.
|
|
return false;
|
|
}
|
|
$normalised = SP_CashAddr::normalize( $address );
|
|
return '' === $normalised ? false : $normalised;
|
|
}
|
|
|
|
/**
|
|
* A permanently unroutable address for a user, derived from their wallet.
|
|
*
|
|
* `wp_install()` and `wp_insert_user()` both want an email string. They get
|
|
* one that RFC 2606 guarantees can never resolve.
|
|
*/
|
|
function sirius_press_install_stub_email( $address ) {
|
|
$tag = is_string( $address ) && '' !== $address
|
|
? substr( hash( 'sha256', $address ), 0, 16 )
|
|
: substr( hash( 'sha256', (string) wp_rand( 0, PHP_INT_MAX ) . microtime() ), 0, 16 );
|
|
return 'noreply+' . $tag . '@sirius-press.invalid';
|
|
}
|
|
|
|
/*
|
|
* ---------------------------------------------------------------------------
|
|
* Core notifications that have nowhere to go.
|
|
*
|
|
* These are declared before `pluggable.php` so core's versions never load.
|
|
* Each one exists in WordPress to email somebody about their own account;
|
|
* on a site where accounts have no mailbox, the message belongs in the
|
|
* in-app inbox, and where the inbox is not available yet (during install)
|
|
* it belongs nowhere at all.
|
|
*
|
|
* Deliberately NOT overridden: wp_mail() itself. Contact forms, order
|
|
* receipts and newsletters go to addresses real people supplied, and those
|
|
* must keep working for a site owner who has configured SMTP. Only the
|
|
* account-notification functions are replaced.
|
|
* ---------------------------------------------------------------------------
|
|
*/
|
|
|
|
if ( ! function_exists( 'wp_new_blog_notification' ) ) {
|
|
/**
|
|
* Core mails the new administrator their own login URL and password.
|
|
*
|
|
* Suppressed: the person triggering it is looking at the screen that
|
|
* already shows both, and the address on file cannot receive mail.
|
|
*
|
|
* @param string $blog_title
|
|
* @param string $blog_url
|
|
* @param int $user_id
|
|
* @param string $password
|
|
*/
|
|
function wp_new_blog_notification( $blog_title, $blog_url, $user_id, $password ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames
|
|
// Nothing to send, and nothing to log — the installer's success page
|
|
// is the notification.
|
|
}
|
|
}
|
|
|
|
if ( ! function_exists( 'wp_password_change_notification' ) ) {
|
|
/**
|
|
* Core mails every administrator when a user changes their password.
|
|
*
|
|
* Routed to the in-app inbox when it exists. During install it does not,
|
|
* and no password has changed anyway.
|
|
*
|
|
* @param WP_User $user
|
|
*/
|
|
function wp_password_change_notification( $user ) {
|
|
if ( ! class_exists( 'SP_Inbox' ) ) {
|
|
return;
|
|
}
|
|
SP_Inbox::add(
|
|
0,
|
|
__( 'A password was changed', 'sirius-press' ),
|
|
sprintf(
|
|
/* translators: %s: the user login whose password changed. */
|
|
esc_html__( 'The password for %s was changed. On this site passwords are a fallback, not the identity — the account is still controlled by its wallet.', 'sirius-press' ),
|
|
'<code>' . esc_html( $user->user_login ) . '</code>'
|
|
),
|
|
'core'
|
|
);
|
|
}
|
|
}
|