Create one — it takes one more signature.', 'sirius-press' ), esc_url( SPA_Register::url() ) ) ); } return new WP_Error( 'sirius_no_account', __( 'No account on this site uses that wallet, and registration is closed.', 'sirius-press' ) ); } /** * Fires after a wallet signature has been accepted for a user. * * A plugin that wants a second factor can return a WP_Error from the * `authenticate` chain at a later priority; this is the hook that * tells it a wallet proof already succeeded. * * @param WP_User $found * @param string $address */ do_action( 'sirius_press_wallet_authenticated', $found, $address ); return $found; } // ------------------------------------------------------------------- UI public static function enqueue() { self::enqueue_wallet(); wp_enqueue_style( 'sirius-press-login', SIRIUS_PRESS_AUTH_URL . 'assets/login.css', array(), SIRIUS_PRESS_AUTH_VERSION ); } /** Shared by the login screen, the registration screen and the profile. */ public static function enqueue_wallet() { wp_enqueue_script( 'sirius-press-bip39', SIRIUS_PRESS_AUTH_URL . 'assets/bip39-en.js', array(), SIRIUS_PRESS_AUTH_VERSION, true ); wp_enqueue_script( 'sirius-press-wallet', SIRIUS_PRESS_AUTH_URL . 'assets/wallet.js', array( 'sirius-press-bip39' ), SIRIUS_PRESS_AUTH_VERSION, true ); wp_enqueue_script( 'sirius-press-login-js', SIRIUS_PRESS_AUTH_URL . 'assets/login.js', array( 'sirius-press-wallet' ), SIRIUS_PRESS_AUTH_VERSION, true ); } /** Configuration the scripts need, printed once. */ public static function footer_config() { self::print_config(); } public static function print_config() { static $printed = false; if ( $printed ) { return; } $printed = true; printf( '', wp_json_encode( array( 'prefix' => SP_Settings::prefix(), 'path' => SP_Settings::derivation_path(), 'network' => SP_Settings::network(), 'site' => home_url( '/' ), // So the page can stop core's username and password inputs // from blocking a submit they are no longer part of. 'passwords' => self::passwords_allowed(), 'strings' => array( 'signing' => __( 'Signing…', 'sirius-press' ), 'noWallet' => __( 'No wallet found in this browser.', 'sirius-press' ), 'generated' => __( 'Write these words down. They are the only way back into this account — nobody, including this site, can reset them for you.', 'sirius-press' ), ), ) ) ); } /** * The wallet block on wp-login.php. * * Rendered by `login_form`, which puts it directly under the password * field, so the wallet path reads as the primary one and passwords as the * leftover they are. */ public static function render_form() { $nonce = SPA_Challenge::issue(); $message = SPA_Challenge::message( $nonce, SPA_Challenge::PURPOSE_LOGIN ); self::render_signing_block( $nonce, $message, SPA_Challenge::PURPOSE_LOGIN, __( 'Sign in with your wallet', 'sirius-press' ) ); } /** * The signing widget, shared by login and registration. * * @param string $nonce * @param string $message Exact text to be signed. * @param string $purpose * @param string $button Label for the primary action. */ public static function render_signing_block( $nonce, $message, $purpose, $button ) { ?>

' . esc_html__( 'This site has no passwords to forget and no email to confirm. Your wallet is the account.', 'sirius-press' ) . '

'; return $intro . $message; } /** * Stop the login screen greeting every visitor with a failed sign-in. * * `wp-login.php` calls `wp_signon()` on its way to drawing the form, even * for a plain GET with nothing submitted. Core tolerates that by throwing * the result away — but only when the error codes are exactly the pair its * own two password filters produce for an empty form. Removing those * filters, which is the entire point of turning passwords off, leaves the * generic `authentication_failed` instead. Core does not recognise it, so * it prints it: a red "Invalid username, email address or incorrect * password" above a form that has neither field, on a site that has no * passwords. * * @param WP_Error $errors * @return WP_Error */ public static function quiet_empty_form_error( $errors ) { // Nothing was submitted, so there is nothing to forge: this only reads // whether the request carried a body at all. // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $_POST ) ) { return $errors; } if ( ! is_wp_error( $errors ) || array( 'authentication_failed' ) !== $errors->get_error_codes() ) { return $errors; } return new WP_Error(); } /** * Visually retire the username and password fields when passwords are off. * * Both of them, not just the password. With passwords off, `authenticate()` * never reads the username either — the account is found from the address * recovered out of the signature — so leaving "Username or Email Address" * at the top of the form puts the exact thing this fork exists to remove * in the first position a visitor looks. * * Core's own submit button goes too. The paste-a-signature path — the one * for people whose keys are on another machine — carries its own, so it no * longer depends on a control that belongs to the password form it is * replacing. The `required` attributes those two inputs carry are cleared * in login.js, * because a hidden required field does not fail validation quietly: it * refuses the submit and points an error bubble at a control nobody can * see. */ public static function hide_credential_fields() { echo ''; } }