assets
23 hours ago
format
23 hours ago
helpers
23 hours ago
class-ajax-passkeys.php
23 hours ago
class-api-register.php
23 hours ago
class-api-signin.php
23 hours ago
class-attestation-object.php
23 hours ago
class-authenticate-server.php
23 hours ago
class-authenticator-data.php
23 hours ago
class-byte-buffer.php
23 hours ago
class-chor-decoder.php
23 hours ago
class-passkeys-endpoints.php
23 hours ago
class-passkeys-user-profile.php
23 hours ago
class-passkeys-wizard-steps.php
23 hours ago
class-passkeys.php
23 hours ago
class-pending-2fa-helper.php
23 hours ago
class-source-repository.php
23 hours ago
class-web-authn-exception.php
23 hours ago
class-web-authn.php
23 hours ago
index.php
23 hours ago
class-passkeys.php
737 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Responsible for the Passkeys extension plugin settings |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @subpackage passkeys |
| 7 | * @since 3.0.0 |
| 8 | * @copyright 2026 Melapress |
| 9 | * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 |
| 10 | * @link https://wordpress.org/plugins/wp-2fa/ |
| 11 | */ |
| 12 | |
| 13 | declare(strict_types=1); |
| 14 | |
| 15 | namespace WP2FA\Methods; |
| 16 | |
| 17 | use WP2FA\WP2FA; |
| 18 | use WP2FA\Admin\User_Profile; |
| 19 | use WP2FA\Utils\Settings_Utils; |
| 20 | use WP2FA\Passkeys\Ajax_Passkeys; |
| 21 | use WP2FA\Admin\Controllers\Methods; |
| 22 | use WP2FA\Admin\Helpers\User_Helper; |
| 23 | use WP2FA\Admin\Controllers\Settings; |
| 24 | use WP2FA\Passkeys\PassKeys_Endpoints; |
| 25 | use WP2FA\Admin\Methods\Traits\Providers; |
| 26 | use WP2FA\Passkeys\Passkeys_User_Profile; |
| 27 | use WP2FA\Extensions\EmailBackup\Email_Backup; |
| 28 | use WP2FA\Methods\Wizards\PassKeys_Wizard_Steps; |
| 29 | use WP2FA\Extensions\RoleSettings\Role_Settings_Controller; |
| 30 | |
| 31 | defined( 'ABSPATH' ) || exit; // Exit if accessed directly. |
| 32 | |
| 33 | /** |
| 34 | * Passkeys settings class |
| 35 | */ |
| 36 | if ( ! class_exists( '\WP2FA\Methods\Passkeys' ) ) { |
| 37 | |
| 38 | /** |
| 39 | * Responsible for setting different 2FA Passkeys settings |
| 40 | * |
| 41 | * @since 3.0.0 |
| 42 | */ |
| 43 | class Passkeys { |
| 44 | |
| 45 | use Providers; |
| 46 | |
| 47 | public const PASSKEY_DIR = 'includes' . \DIRECTORY_SEPARATOR . 'classes' . \DIRECTORY_SEPARATOR . 'Admin' . \DIRECTORY_SEPARATOR . 'Methods' . \DIRECTORY_SEPARATOR . 'passkeys'; |
| 48 | |
| 49 | public const METHOD_NAME = 'passkeys'; |
| 50 | public const POLICY_SETTINGS_NAME = 'enable_passkeys'; |
| 51 | |
| 52 | public const USER_PROFILE_JS_MODULE = 'wp_2fa_passkeys_user_profile'; |
| 53 | public const USER_LOGIN_JS_MODULE = 'wp_2fa_passkeys_user_login'; |
| 54 | |
| 55 | /** |
| 56 | * Is the passkeys method enabled |
| 57 | * |
| 58 | * @since 3.0.0 |
| 59 | * |
| 60 | * @var bool |
| 61 | */ |
| 62 | private static $enabled = null; |
| 63 | |
| 64 | /** |
| 65 | * Inits the main Passkeys class and sets the methods. |
| 66 | * |
| 67 | * @return void |
| 68 | * |
| 69 | * @since 3.0.0 |
| 70 | */ |
| 71 | public static function init() { |
| 72 | |
| 73 | self::always_init(); |
| 74 | |
| 75 | \add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); |
| 76 | \add_action( WP_2FA_PREFIX . 'shortcode_scripts', array( __CLASS__, 'enqueue_scripts' ), 10, 1 ); |
| 77 | |
| 78 | \add_filter( 'script_loader_tag', array( __CLASS__, 'set_script_type_attribute' ), 10, 3 ); |
| 79 | |
| 80 | \add_filter( WP_2FA_PREFIX . 'providers_translated_names', array( __CLASS__, 'provider_name_translated' ) ); |
| 81 | |
| 82 | \add_filter( WP_2FA_PREFIX . 'default_settings', array( __CLASS__, 'add_default_settings' ) ); |
| 83 | |
| 84 | \add_filter( WP_2FA_PREFIX . 'loop_settings', array( __CLASS__, 'settings_loop' ), 10, 1 ); |
| 85 | |
| 86 | \add_filter( WP_2FA_PREFIX . 'no_method_enabled', array( __CLASS__, 'return_default_selection' ), 10, 1 ); |
| 87 | |
| 88 | \add_filter( WP_2FA_PREFIX . 'save_additional_enabled_methods', array( __CLASS__, 'method_enabled' ), 10, 2 ); |
| 89 | |
| 90 | \add_filter( WP_2FA_PREFIX . 'enable_2fa_user_setting', array( __CLASS__, 'enable_2fa_user_setting' ), 10, 3 ); |
| 91 | |
| 92 | // Login forms. |
| 93 | |
| 94 | \add_action( 'login_enqueue_scripts', array( __CLASS__, 'enqueue_login_scripts' ) ); |
| 95 | |
| 96 | \add_action( 'woocommerce_login_form_end', array( __CLASS__, 'woocommerce_login_form' ) ); |
| 97 | |
| 98 | \add_action( 'login_form', array( __CLASS__, 'add_to_admin_login_page' ) ); |
| 99 | |
| 100 | // \add_action( WP_2FA_PREFIX . 'white_label_wizard_options', array( __CLASS__, 'white_label_option_labels' ) ); |
| 101 | |
| 102 | \add_filter( WP_2FA_PREFIX . 'white_label_default_settings', array( __CLASS__, 'add_whitelabel_settings' ) ); |
| 103 | |
| 104 | \add_filter( WP_2FA_PREFIX . 'method_settings_disabled', array( __CLASS__, 'disable_email_backup' ), 10, 3 ); |
| 105 | |
| 106 | \add_filter( WP_2FA_PREFIX . 'before_settings_save_roles', array( __CLASS__, 'set_this_method' ), 10, 1 ); |
| 107 | |
| 108 | \add_filter( WP_2FA_PREFIX . 'before_settings_save', array( __CLASS__, 'before_settings_save' ), 10, 1 ); |
| 109 | |
| 110 | \add_filter( WP_2FA_PREFIX . 'before_remove_role', array( __CLASS__, 'set_this_method' ), 10, 1 ); |
| 111 | |
| 112 | // Login forms end. |
| 113 | |
| 114 | // add the Passkeys methods to the list of available methods if enabled. |
| 115 | \add_filter( |
| 116 | WP_2FA_PREFIX . 'available_2fa_methods', |
| 117 | function ( $available_methods ) { |
| 118 | if ( ! empty( Settings_Utils::get_setting_role( null, self::POLICY_SETTINGS_NAME ) ) ) { |
| 119 | array_push( $available_methods, self::METHOD_NAME ); |
| 120 | } |
| 121 | |
| 122 | return $available_methods; |
| 123 | } |
| 124 | ); |
| 125 | |
| 126 | PassKeys_Wizard_Steps::init(); |
| 127 | |
| 128 | PassKeys_Endpoints::init(); |
| 129 | |
| 130 | Passkeys_User_Profile::init(); |
| 131 | |
| 132 | Ajax_Passkeys::init(); |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Responsible for providing proper assets on the user profile page. |
| 137 | * |
| 138 | * @param bool $shortcodes - Whether the scripts are being loaded for shortcodes. |
| 139 | * |
| 140 | * @return void |
| 141 | * |
| 142 | * @since 3.0.0 |
| 143 | */ |
| 144 | public static function enqueue_scripts( $shortcodes = false ) { |
| 145 | if ( ! \is_admin() && \function_exists( 'is_user_logged_in' ) && ! \is_user_logged_in() ) { |
| 146 | return; |
| 147 | } |
| 148 | global $current_screen; |
| 149 | |
| 150 | $woo = ''; |
| 151 | if ( function_exists( 'wc_get_page_id' ) ) { |
| 152 | $woo = (string) \get_permalink( \wc_get_page_id( 'myaccount' ) ); |
| 153 | } |
| 154 | |
| 155 | if ( ! isset( $current_screen->id ) || ( isset( $current_screen->id ) && ! in_array( |
| 156 | $current_screen->id, |
| 157 | array( |
| 158 | 'wp-2fa_page_wp-2fa-settings', |
| 159 | 'toplevel_page_wp-2fa-policies', |
| 160 | ), |
| 161 | true |
| 162 | ) ) ) { |
| 163 | // if ( ! self::is_set() ) { |
| 164 | // return; |
| 165 | // } |
| 166 | } |
| 167 | |
| 168 | if ( ( ( isset( $current_screen->id ) && in_array( |
| 169 | $current_screen->id, |
| 170 | array( |
| 171 | 'wp-2fa_page_wp-2fa-settings', |
| 172 | 'toplevel_page_wp-2fa-policies', |
| 173 | 'profile', |
| 174 | 'profile-network', |
| 175 | 'user-edit', |
| 176 | 'user-edit-network', |
| 177 | ), |
| 178 | true |
| 179 | ) ) || true === $shortcodes ) |
| 180 | || ( isset( $_GET['is_initial_setup'] ) && 'true' === $_GET['is_initial_setup'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized |
| 181 | || ( isset( $_SERVER['SCRIPT_NAME'] ) && false !== stripos( \wp_login_url(), \sanitize_text_field( \wp_unslash( $_SERVER['SCRIPT_NAME'] ) ) ) ) |
| 182 | || ( isset( $_SERVER['REQUEST_URI'] ) && false !== stripos( \wp_login_url(), \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ) |
| 183 | || ( isset( $_SERVER['REQUEST_URI'] ) && false !== stripos( $woo, \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ) |
| 184 | ) { |
| 185 | if ( false === Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ) { |
| 186 | \wp_enqueue_script( |
| 187 | self::USER_PROFILE_JS_MODULE, |
| 188 | \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-profile.js', |
| 189 | array( 'wp-api-fetch', 'wp-dom-ready', 'wp-i18n' ), |
| 190 | WP_2FA_VERSION, |
| 191 | array( 'in_footer' => true ) |
| 192 | ); |
| 193 | } else { |
| 194 | \wp_enqueue_script( |
| 195 | self::USER_PROFILE_JS_MODULE, |
| 196 | \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-profile-ajax.js', |
| 197 | array( 'wp-dom-ready', 'wp-i18n' ), |
| 198 | WP_2FA_VERSION, |
| 199 | array( 'in_footer' => true ) |
| 200 | ); |
| 201 | |
| 202 | \wp_localize_script( |
| 203 | self::USER_PROFILE_JS_MODULE, |
| 204 | 'wp2faData', |
| 205 | array( |
| 206 | 'ajaxURL' => \admin_url( 'admin-ajax.php' ), |
| 207 | ) |
| 208 | ); |
| 209 | } |
| 210 | |
| 211 | // Passkeys profile CSS. |
| 212 | \wp_enqueue_style( |
| 213 | 'wp-2fa-passkeys-profile', |
| 214 | \trailingslashit( WP_2FA_URL ) . 'css/passkeys/user-profile.css', |
| 215 | array(), |
| 216 | WP_2FA_VERSION |
| 217 | ); |
| 218 | |
| 219 | // Passkeys profile modal JS (vanilla, non-module). |
| 220 | \wp_enqueue_script( |
| 221 | 'wp-2fa-passkeys-profile-modal', |
| 222 | \trailingslashit( WP_2FA_URL ) . 'js/passkeys/user-profile.js', |
| 223 | array( 'wp-dom-ready', 'wp-util' ), |
| 224 | WP_2FA_VERSION, |
| 225 | array( 'in_footer' => true ) |
| 226 | ); |
| 227 | |
| 228 | \wp_localize_script( |
| 229 | 'wp-2fa-passkeys-profile-modal', |
| 230 | 'wp2faPasskeys', |
| 231 | array( |
| 232 | 'nonce' => \wp_create_nonce( 'wp2fa_profile_register' ), |
| 233 | 'setupTitle' => \esc_html__( 'Set up Passkey Login', 'wp-2fa' ), |
| 234 | 'setupDescription' => \esc_html__( 'Passkeys replace traditional passwords with a quick and secure login method built right into your device. Depending on your setup, this could be a fingerprint, facial recognition, or another trusted device. Even if your device doesn\'t support biometrics, you can still create and use a passkey. A Passkey is stored safely on your device. So it can\'t be phished, leaked, or reused by hackers.', 'wp-2fa' ), |
| 235 | 'stepsHeading' => \esc_html__( 'Steps to add a passkey:', 'wp-2fa' ), |
| 236 | 'step1' => \esc_html__( 'Click the "Add Passkey" button and a pop-up will appear.', 'wp-2fa' ), |
| 237 | 'step2' => \esc_html__( 'Follow the instructions on the popup', 'wp-2fa' ), |
| 238 | 'step3' => \esc_html__( 'Specify a name for your passkey and that is it!', 'wp-2fa' ), |
| 239 | 'cancelLabel' => \esc_html__( 'Cancel and back', 'wp-2fa' ), |
| 240 | 'usbLabel' => \esc_html__( 'Add a USB Security Key', 'wp-2fa' ), |
| 241 | 'passkeyLabel' => \esc_html__( 'Add a Passkey', 'wp-2fa' ), |
| 242 | 'successTitle' => \esc_html__( 'Passkey Added!', 'wp-2fa' ), |
| 243 | 'successDescription' => \esc_html__( 'The passkey has been added successfully. Give it a name or leave for auto-generated name.', 'wp-2fa' ), |
| 244 | 'namePlaceholder' => \esc_html__( 'Passkey name', 'wp-2fa' ), |
| 245 | 'submitLabel' => \esc_html__( 'Continue', 'wp-2fa' ), |
| 246 | 'nameValidationError' => \esc_html__( 'Only letters, numbers, dashes, underscores, and spaces allowed.', 'wp-2fa' ), |
| 247 | ) |
| 248 | ); |
| 249 | |
| 250 | // Print passkey templates in admin footer. |
| 251 | \add_action( 'admin_footer', array( '\WP2FA\Passkeys\Passkeys_User_Profile', 'print_passkey_templates' ) ); |
| 252 | \add_action( 'wp_footer', array( '\WP2FA\Passkeys\Passkeys_User_Profile', 'print_passkey_templates' ) ); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Adds login scripts to the login page |
| 258 | * |
| 259 | * @return void |
| 260 | * |
| 261 | * @since 3.0.0 |
| 262 | */ |
| 263 | public static function enqueue_login_scripts() { |
| 264 | if ( \is_user_logged_in() || ! self::is_globally_enabled() ) { |
| 265 | return; |
| 266 | } |
| 267 | if ( false === Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'disable_rest' ) ) ) { |
| 268 | \wp_enqueue_script( |
| 269 | self::USER_LOGIN_JS_MODULE, |
| 270 | \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-login.js', |
| 271 | array( 'wp-api-fetch', 'wp-dom-ready' ), |
| 272 | WP_2FA_VERSION, |
| 273 | array( 'in_footer' => true ) |
| 274 | ); |
| 275 | } else { |
| 276 | \wp_enqueue_script( |
| 277 | self::USER_LOGIN_JS_MODULE, |
| 278 | \trailingslashit( WP_2FA_URL ) . \trailingslashit( self::PASSKEY_DIR ) . 'assets/js/user-login-ajax.js', |
| 279 | array( 'jquery', 'wp-dom-ready' ), |
| 280 | WP_2FA_VERSION, |
| 281 | array( 'in_footer' => true ) |
| 282 | ); |
| 283 | |
| 284 | $variables = array( |
| 285 | 'ajaxurl' => \admin_url( 'admin-ajax.php' ), |
| 286 | ); |
| 287 | \wp_localize_script( self::USER_LOGIN_JS_MODULE, 'login', $variables ); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | /** |
| 292 | * Add `type="module"` to a script tag |
| 293 | * |
| 294 | * @param string $tag - Original script tag. |
| 295 | * @param string $handle - Handle of the script that's currently being filtered. |
| 296 | * @param string $src - Script src attribut. |
| 297 | * |
| 298 | * @return string Script tag with attribute `type="module"` added. |
| 299 | * |
| 300 | * @since 3.0.0 |
| 301 | */ |
| 302 | public static function set_script_type_attribute( string $tag, string $handle, string $src ): string { |
| 303 | if ( self::USER_PROFILE_JS_MODULE !== $handle && self::USER_LOGIN_JS_MODULE !== $handle ) { |
| 304 | return $tag; |
| 305 | } |
| 306 | |
| 307 | $tag = '<script type="module" src="' . esc_url( $src ) . '" id="' . self::USER_PROFILE_JS_MODULE . '"></script>' . "\n"; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript |
| 308 | |
| 309 | return $tag; |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * Adds passkeys provider translatable name |
| 314 | * |
| 315 | * @param array $providers - Array with all currently supported providers and their translated names. |
| 316 | * |
| 317 | * @return array |
| 318 | * |
| 319 | * @since 3.0.0 |
| 320 | */ |
| 321 | public static function provider_name_translated( array $providers ) { |
| 322 | $providers[ self::METHOD_NAME ] = WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true ); |
| 323 | |
| 324 | return $providers; |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Adds the extension default settings to the main plugin settings |
| 329 | * |
| 330 | * @param array $default_settings - array with plugin default settings. |
| 331 | * |
| 332 | * @return array |
| 333 | * |
| 334 | * @since 3.0.0 |
| 335 | */ |
| 336 | public static function add_default_settings( array $default_settings ) { |
| 337 | $default_settings[ self::POLICY_SETTINGS_NAME ] = false; // self::POLICY_SETTINGS_NAME; |
| 338 | |
| 339 | return $default_settings; |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * Add extension settings to the loop array |
| 344 | * |
| 345 | * @param array $loop_settings - Currently available settings array. |
| 346 | * |
| 347 | * @return array |
| 348 | * |
| 349 | * @since 3.0.0 |
| 350 | */ |
| 351 | public static function settings_loop( array $loop_settings ) { |
| 352 | array_push( $loop_settings, self::POLICY_SETTINGS_NAME ); |
| 353 | array_push( $loop_settings, self::POLICY_SETTINGS_NAME . '_certain_roles' ); |
| 354 | |
| 355 | return $loop_settings; |
| 356 | } |
| 357 | |
| 358 | /** |
| 359 | * Shows the passkeys use button to the main WP login form |
| 360 | * |
| 361 | * @return void |
| 362 | * |
| 363 | * @since 3.0.0 |
| 364 | */ |
| 365 | public static function add_to_admin_login_page() { |
| 366 | if ( \is_user_logged_in() || ! self::is_globally_enabled() ) { |
| 367 | return; |
| 368 | } |
| 369 | |
| 370 | echo self::load_style(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 371 | ?> |
| 372 | |
| 373 | <div class="wp-2fa-login-wrapper" id="wp-2fa-login-wrapper"> |
| 374 | <div id="errorMessage" class="notice notice-error" style="display: none;"></div> |
| 375 | <div id="successMessage" class="notice notice-success" style="display: none;"></div> |
| 376 | <button type="button" id="wp-2fa-login-via-passkey" class="button button-large wp-2fa-login-via-passkey"> |
| 377 | <span id="buttonText"><?php esc_html_e( 'Log in with a passkey', 'wp-2fa' ); ?></span> |
| 378 | </button> |
| 379 | <div style="display: none;margin-top: 10px;" id="wp-2fa-standard-login-wrapper"> |
| 380 | <button type="button" id="wp-2fa-login-standard" class="button button-large wp-2fa-login-standard"> |
| 381 | <span id="buttonText"><?php esc_html_e( 'Log in with your username and password', 'wp-2fa' ); ?></span> |
| 382 | </button> |
| 383 | </div> |
| 384 | </div> |
| 385 | <?php |
| 386 | } |
| 387 | |
| 388 | /** |
| 389 | * Loads the login form stylesheet |
| 390 | * |
| 391 | * @return string |
| 392 | * |
| 393 | * @since 3.0.0 |
| 394 | */ |
| 395 | public static function load_style(): string { |
| 396 | \ob_start(); |
| 397 | ?> |
| 398 | <style> |
| 399 | /** Default */ |
| 400 | .wp-2fa-login-wrapper { |
| 401 | text-align: center; |
| 402 | display: block; |
| 403 | border-top: 1px solid #ccc; |
| 404 | padding: 20px 0; |
| 405 | } |
| 406 | |
| 407 | .wp-2fa-login-wrapper .notice-error { |
| 408 | border-left-color: #d63638; |
| 409 | border-right-color: #d63638; |
| 410 | } |
| 411 | |
| 412 | .wp-2fa-login-wrapper .notice-success { |
| 413 | border-left-color: #358f49; |
| 414 | border-right-color: #358f49; |
| 415 | } |
| 416 | |
| 417 | /** Short Code */ |
| 418 | .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey { |
| 419 | display: inline-block; |
| 420 | font-weight: 400; |
| 421 | color: #1e1e1e; |
| 422 | text-align: center; |
| 423 | vertical-align: middle; |
| 424 | user-select: none; |
| 425 | background-color: #fff; |
| 426 | border: 1px solid #e9e9e9; |
| 427 | padding: 0.375rem 0.75rem; |
| 428 | font-size: 1rem; |
| 429 | line-height: 1.5; |
| 430 | transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; |
| 431 | } |
| 432 | |
| 433 | .wp-2fa-login-wrapper .notice { |
| 434 | text-align: initial; |
| 435 | } |
| 436 | |
| 437 | .wp-2fa-login-short-code-wrapper .notice-success { |
| 438 | color: #358f49; |
| 439 | background-color: #f3fff6; |
| 440 | border: 1px solid #d6f3dd; |
| 441 | padding: 10px; |
| 442 | border-radius: 5px; |
| 443 | margin: 15px 0; |
| 444 | font-size: 13px; |
| 445 | } |
| 446 | |
| 447 | .wp-2fa-login-short-code-wrapper .notice-error { |
| 448 | color: #721c24; |
| 449 | background-color: #fdf1f2; |
| 450 | border: 1px solid #ffdbdf; |
| 451 | padding: 10px; |
| 452 | border-radius: 5px; |
| 453 | margin: 15px 0; |
| 454 | font-size: 13px; |
| 455 | } |
| 456 | |
| 457 | .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey:hover { |
| 458 | background-color: #e2e2e2; |
| 459 | border-color: #e2e2e2; |
| 460 | } |
| 461 | |
| 462 | .wp-2fa-login-short-code-wrapper .wp-2fa-login-via-passkey:disabled { |
| 463 | opacity: 0.65; |
| 464 | cursor: not-allowed; |
| 465 | } |
| 466 | |
| 467 | /** WooCommerce */ |
| 468 | .wp-2fa-login-woocommerce-wrapper { |
| 469 | text-align: center; |
| 470 | margin-top: 50px; |
| 471 | display: block; |
| 472 | border-top: 1px solid #ccc; |
| 473 | padding: 20px 0; |
| 474 | margin-block-start: 1em; |
| 475 | margin-block-end: 1em; |
| 476 | } |
| 477 | |
| 478 | .wp-2fa-login-woocommerce-wrapper .wp-2fa-login-via-passkey { |
| 479 | float: unset !important; |
| 480 | } |
| 481 | |
| 482 | .wp-2fa-login-woocommerce-wrapper .wp-2fa-login-via-passkey:disabled { |
| 483 | opacity: 0.65; |
| 484 | cursor: not-allowed; |
| 485 | } |
| 486 | |
| 487 | .wp-2fa-login-woocommerce-wrapper .wc-block-components-notice-banner { |
| 488 | padding: 10px !important; |
| 489 | } |
| 490 | |
| 491 | /* Ultimate Member */ |
| 492 | .wp-2fa-login-ultimate-member-wrapper .is-error { |
| 493 | margin-top: 10px; |
| 494 | margin-bottom: 10px; |
| 495 | } |
| 496 | |
| 497 | .wp-2fa-login-ultimate-member-wrapper .is-success { |
| 498 | margin-top: 10px !important; |
| 499 | margin-bottom: 10px !important; |
| 500 | } |
| 501 | </style> |
| 502 | <?php |
| 503 | |
| 504 | return \ob_get_clean(); |
| 505 | } |
| 506 | |
| 507 | /** |
| 508 | * Shows passkey on the woocommerce login form |
| 509 | * |
| 510 | * @return void |
| 511 | * |
| 512 | * @since 3.0.0 |
| 513 | */ |
| 514 | public static function woocommerce_login_form() { |
| 515 | if ( \is_user_logged_in() || ! self::is_globally_enabled() ) { |
| 516 | return; |
| 517 | } |
| 518 | |
| 519 | self::enqueue_login_scripts(); |
| 520 | |
| 521 | echo self::load_style(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 522 | |
| 523 | ?> |
| 524 | |
| 525 | <div class="wp-2fa-login-woocommerce-wrapper" id="wp-2fa-login-woocommerce-wrapper"> |
| 526 | <div id="errorMessage" class="notice notice-error" style="display: none;"></div> |
| 527 | <div id="successMessage" class="notice notice-success" style="display: none;"></div> |
| 528 | <button type="button" id="wp-2fa-login-via-passkey" class="woocommerce-button button woocommerce-form-login__submit wp-element-button wp-2fa-login-via-passkey"> |
| 529 | <span id="buttonText"><?php esc_html_e( 'Log in with a passkey', 'wp-2fa' ); ?></span> |
| 530 | </button> |
| 531 | <div style="display: none;margin-top: 10px;" id="wp-2fa-standard-login-wrapper"> |
| 532 | <button type="button" id="wp-2fa-login-standard" class="woocommerce-form-login__submit wp-element-button wp-2fa-login-standard" style="float: unset !important;"> |
| 533 | <span id="buttonText"><?php esc_html_e( 'Log in with your username and password', 'wp-2fa' ); ?></span> |
| 534 | </button> |
| 535 | </div> |
| 536 | </div> |
| 537 | <?php |
| 538 | } |
| 539 | |
| 540 | /** |
| 541 | * Shows dates in proper format |
| 542 | * |
| 543 | * @param string|null $datetime - The unix timestamp. |
| 544 | * |
| 545 | * @return string|null |
| 546 | * |
| 547 | * @since 3.1.0 |
| 548 | */ |
| 549 | public static function get_datetime_from_now( ?string $datetime ) { |
| 550 | if ( ! $datetime ) { |
| 551 | return null; |
| 552 | } |
| 553 | |
| 554 | $human_time_diff = human_time_diff( $datetime ); |
| 555 | |
| 556 | // translators: %s represents a human-readable time difference (e.g., "5 minutes"). |
| 557 | return sprintf( __( '%s ago', 'wp-2fa' ), $human_time_diff ); |
| 558 | } |
| 559 | |
| 560 | /** |
| 561 | * Shows / hides the "Change 2FA settings" button in user profile page. |
| 562 | * |
| 563 | * @param bool $status - The currently set value. |
| 564 | * @param int $user_id - The ID of the user to check for. |
| 565 | * |
| 566 | * @since 3.1.0 |
| 567 | */ |
| 568 | public static function enable_2fa_user_setting( bool $status, $user_id ): bool { |
| 569 | if ( true === $status ) { |
| 570 | |
| 571 | $user_role = User_Helper::get_user_role( $user_id ); |
| 572 | $available_methods = Methods::get_enabled_methods( $user_role ); |
| 573 | |
| 574 | if ( $available_methods && isset( $available_methods[ $user_role ] ) && count( $available_methods[ $user_role ] ) === 1 && isset( $available_methods[ $user_role ][ self::METHOD_NAME ] ) ) { |
| 575 | return false; |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | return $status; |
| 580 | } |
| 581 | |
| 582 | /** |
| 583 | * Shows the Method option label and hint in the White Label settings. |
| 584 | * |
| 585 | * @return void |
| 586 | * |
| 587 | * @since 3.1.0 |
| 588 | */ |
| 589 | public static function white_label_option_labels() { |
| 590 | ?> |
| 591 | <strong class="description"> |
| 592 | <?php |
| 593 | echo \esc_html( |
| 594 | \wp_sprintf( |
| 595 | // translators: Method option label. |
| 596 | __( '%s option label', 'wp-2fa' ), |
| 597 | WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true ) |
| 598 | ) |
| 599 | ); |
| 600 | ?> |
| 601 | </strong> |
| 602 | <br> |
| 603 | <fieldset> |
| 604 | <input type="text" id="passkeys-option-label" name="wp_2fa_white_label[passkeys-option-label]" class="large-text" value="<?php echo \esc_attr( WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true ) ); ?>"> |
| 605 | </fieldset> |
| 606 | <br> |
| 607 | <?php |
| 608 | } |
| 609 | |
| 610 | /** |
| 611 | * Fills up the White Label settings array with the method defaults. |
| 612 | * |
| 613 | * @param array $default_settings - The array with the collected white label settings. |
| 614 | * |
| 615 | * @return array |
| 616 | * |
| 617 | * @since 3.1.0 |
| 618 | */ |
| 619 | public static function add_whitelabel_settings( array $default_settings ): array { |
| 620 | |
| 621 | $default_settings['passkeys-option-label'] = __( 'Passkeys', 'wp-2fa' ); |
| 622 | |
| 623 | return $default_settings; |
| 624 | } |
| 625 | |
| 626 | /** |
| 627 | * Disables email backup method if only Passkeys is enabled |
| 628 | * |
| 629 | * @param boolean $disabled - The current disabled status. |
| 630 | * @param string $method - The method to check if the logic needs to fire. |
| 631 | * @param string|null $role - The role to check for. |
| 632 | * |
| 633 | * @return boolean |
| 634 | * |
| 635 | * @since 3.1.0 |
| 636 | */ |
| 637 | public static function disable_email_backup( bool $disabled, string $method, $role = null ): bool { |
| 638 | if ( 'WP2FA\Extensions\EmailBackup\Email_Backup' === $method ) { |
| 639 | $available_methods = Settings::get_enabled_providers_for_role( (string) $role ); |
| 640 | if ( $available_methods && count( $available_methods ) === 2 && isset( $available_methods[ self::METHOD_NAME ] ) && isset( $available_methods[ Email_Backup::METHOD_NAME ] ) ) { |
| 641 | return true; |
| 642 | } elseif ( $available_methods && count( $available_methods ) === 1 && isset( $available_methods[ self::METHOD_NAME ] ) ) { |
| 643 | return true; |
| 644 | } |
| 645 | } |
| 646 | |
| 647 | return $disabled; |
| 648 | } |
| 649 | |
| 650 | /** |
| 651 | * Sets the Passkeys as a method for the given user. |
| 652 | * |
| 653 | * @param \WP_User $user - The user for which the method has to be set, if null, it uses the current user. |
| 654 | * |
| 655 | * @return void |
| 656 | * |
| 657 | * @since 3.1.0 |
| 658 | */ |
| 659 | public static function set_user_method( $user = null ) { |
| 660 | if ( null === $user ) { |
| 661 | $user = \wp_get_current_user(); |
| 662 | } |
| 663 | |
| 664 | // User_Helper::set_enabled_method_for_user( self::METHOD_NAME, $user ); . |
| 665 | User_Profile::delete_expire_and_enforced_keys( $user->ID ); |
| 666 | User_Helper::set_user_status( $user ); |
| 667 | } |
| 668 | |
| 669 | /** |
| 670 | * Sets this method in the role settings options before saving. |
| 671 | * |
| 672 | * @param array $roles_options - The currently set roles options. |
| 673 | * |
| 674 | * @return array |
| 675 | * |
| 676 | * @since 3.1.0 |
| 677 | */ |
| 678 | public static function set_this_method( array $roles_options ): array { |
| 679 | $roles_options_stored = Settings_Utils::get_option( Role_Settings_Controller::SETTINGS_NAME ); |
| 680 | if ( ! $roles_options_stored || ! is_array( $roles_options_stored ) ) { |
| 681 | return $roles_options; |
| 682 | } |
| 683 | |
| 684 | $stored_policy_settings = WP2FA::get_policy_settings(); |
| 685 | |
| 686 | if ( isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME ] ) && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) && ! empty( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) ) { |
| 687 | |
| 688 | foreach ( $roles_options_stored as $role => $settings ) { |
| 689 | if ( isset( $settings[ self::POLICY_SETTINGS_NAME ] ) && ! empty( $settings[ self::POLICY_SETTINGS_NAME ] ) ) { |
| 690 | $roles_options[ $role ][ self::POLICY_SETTINGS_NAME ] = self::POLICY_SETTINGS_NAME; |
| 691 | } |
| 692 | } |
| 693 | } else { |
| 694 | foreach ( $roles_options_stored as $role => $settings ) { |
| 695 | unset( $roles_options_stored[ $role ][ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ); |
| 696 | unset( $roles_options_stored[ $role ][ self::POLICY_SETTINGS_NAME ] ); |
| 697 | } |
| 698 | |
| 699 | foreach ( $roles_options as $role => $settings ) { |
| 700 | unset( $roles_options[ $role ][ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ); |
| 701 | unset( $roles_options[ $role ][ self::POLICY_SETTINGS_NAME ] ); |
| 702 | } |
| 703 | } |
| 704 | |
| 705 | return $roles_options; |
| 706 | } |
| 707 | |
| 708 | /** |
| 709 | * Before saving the settings, it checks if the Passkeys policy settings are set and saves them. |
| 710 | * |
| 711 | * @param array $settings - The current settings array. |
| 712 | * |
| 713 | * @return array |
| 714 | * |
| 715 | * @since 3.1.0 |
| 716 | */ |
| 717 | public static function before_settings_save( array $settings ): array { |
| 718 | $stored_policy_settings = WP2FA::get_policy_settings(); |
| 719 | |
| 720 | if ( ! $settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] ) ) { |
| 721 | $settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ] = $stored_policy_settings[ self::POLICY_SETTINGS_NAME . '_certain_roles' ]; |
| 722 | } |
| 723 | if ( ! $settings[ self::POLICY_SETTINGS_NAME ] && isset( $stored_policy_settings[ self::POLICY_SETTINGS_NAME ] ) ) { |
| 724 | $settings[ self::POLICY_SETTINGS_NAME ] = $stored_policy_settings[ self::POLICY_SETTINGS_NAME ]; |
| 725 | } |
| 726 | |
| 727 | if ( isset( $settings[ self::POLICY_SETTINGS_NAME ] ) && ! empty( $settings[ self::POLICY_SETTINGS_NAME ] ) ) { |
| 728 | $settings[ self::POLICY_SETTINGS_NAME ] = true; |
| 729 | } else { |
| 730 | $settings[ self::POLICY_SETTINGS_NAME ] = false; |
| 731 | } |
| 732 | |
| 733 | return $settings; |
| 734 | } |
| 735 | } |
| 736 | } |
| 737 |