assets
1 week ago
format
1 week ago
helpers
1 week ago
class-ajax-passkeys.php
1 week ago
class-api-register.php
1 week ago
class-api-signin.php
1 week ago
class-attestation-object.php
1 week ago
class-authenticate-server.php
1 week ago
class-authenticator-data.php
1 week ago
class-byte-buffer.php
1 week ago
class-chor-decoder.php
1 week ago
class-passkeys-endpoints.php
1 week ago
class-passkeys-user-profile.php
1 week ago
class-passkeys-wizard-steps.php
1 week ago
class-passkeys.php
1 week ago
class-pending-2fa-helper.php
1 week ago
class-source-repository.php
1 week ago
class-web-authn-exception.php
1 week ago
class-web-authn.php
1 week ago
index.php
1 week ago
class-passkeys-wizard-steps.php
262 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Responsible for WP2FA user's Passkeys manipulation. |
| 4 | * |
| 5 | * @package wp2fa |
| 6 | * @subpackage methods-wizard |
| 7 | * @since 3.0.0 |
| 8 | * @copyright 2026 Melapress |
| 9 | * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 |
| 10 | * |
| 11 | * @see https://wordpress.org/plugins/wp-2fa/ |
| 12 | */ |
| 13 | |
| 14 | declare(strict_types=1); |
| 15 | |
| 16 | namespace WP2FA\Methods\Wizards; |
| 17 | |
| 18 | use WP2FA\WP2FA; |
| 19 | use WP2FA\Methods\Passkeys; |
| 20 | use WP2FA\Utils\Settings_Utils; |
| 21 | use WP2FA\Admin\Helpers\WP_Helper; |
| 22 | use WP2FA\Admin\Views\Wizard_Steps; |
| 23 | use WP2FA\Admin\Helpers\User_Helper; |
| 24 | use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait; |
| 25 | |
| 26 | /** |
| 27 | * Class for handling passkeys codes. |
| 28 | * |
| 29 | * @since 3.0.0 |
| 30 | * |
| 31 | * @package WP2FA |
| 32 | */ |
| 33 | if ( ! class_exists( '\WP2FA\Methods\Wizards\PassKeys_Wizard_Steps' ) ) { |
| 34 | /** |
| 35 | * Passkeys code class, for handling passkeys (app) code generation and such. |
| 36 | * |
| 37 | * @since 3.0.0 |
| 38 | */ |
| 39 | class PassKeys_Wizard_Steps extends Wizard_Steps { |
| 40 | |
| 41 | use Methods_Wizards_Trait; |
| 42 | |
| 43 | /** |
| 44 | * Keeps the main class method name, so we can call it when needed. |
| 45 | * |
| 46 | * @var string |
| 47 | * |
| 48 | * @since 3.0.0 |
| 49 | */ |
| 50 | private static $main_class = Passkeys::class; |
| 51 | |
| 52 | /** |
| 53 | * The default value of the method order in the wizards. |
| 54 | * |
| 55 | * @var integer |
| 56 | * |
| 57 | * @since 3.0.0 |
| 58 | */ |
| 59 | private static $order = 9; |
| 60 | |
| 61 | /** |
| 62 | * Inits the class hooks |
| 63 | * |
| 64 | * @return void |
| 65 | * |
| 66 | * @since 2.4.0 |
| 67 | */ |
| 68 | public static function init() { |
| 69 | // \add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'passkeys_option' ), 10, 2 ); |
| 70 | // \add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'passkeys_re_configure' ), 10, 2 ); |
| 71 | // \add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'passkeys_wizard_settings' ), 10, 4 ); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Shows the option to reconfigure email (if applicable) |
| 76 | * |
| 77 | * @param array $methods - Array of methods collected. |
| 78 | * @param string $role - The name of the role to show option to. |
| 79 | * |
| 80 | * @since 3.0.0 |
| 81 | * |
| 82 | * @return array |
| 83 | */ |
| 84 | public static function passkeys_re_configure( array $methods, string $role ): array { |
| 85 | |
| 86 | if ( ! Passkeys::is_enabled() ) { |
| 87 | return $methods; |
| 88 | } |
| 89 | \ob_start(); |
| 90 | ?> |
| 91 | <div class="option-pill"> |
| 92 | <?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'passkeys_reconfigure_intro', true ), User_Helper::get_user_object()->ID, Passkeys::METHOD_NAME ) ); ?> |
| 93 | <div class="wp2fa-setup-actions"> |
| 94 | <a href="#" class="button button-primary wp-2fa-button-primary" data-name="next_step_setting_modal_wizard" data-trigger-reset-key <?php echo WP_Helper::create_data_nonce( self::json_nonce() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> data-user-id="<?php echo \esc_attr( User_Helper::get_user_object()->ID ); ?>" data-next-step="wp-2fa-wizard-passkeys"><?php \esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a> |
| 95 | </div> |
| 96 | </div> |
| 97 | <?php |
| 98 | $output = ob_get_contents(); |
| 99 | ob_end_clean(); |
| 100 | |
| 101 | $methods[ self::get_order( $role, $methods ) ] = array( |
| 102 | 'name' => self::$main_class::METHOD_NAME, |
| 103 | 'output' => $output, |
| 104 | ); |
| 105 | |
| 106 | return $methods; |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Shows the initial passkeys setup options based on enabled methods |
| 111 | * |
| 112 | * @param array $methods - Array of methods collected. |
| 113 | * @param string $role - The name of the role to show option to. |
| 114 | * |
| 115 | * @since 3.0.0 |
| 116 | * |
| 117 | * @return array |
| 118 | */ |
| 119 | public static function passkeys_option( array $methods, string $role ): array { |
| 120 | if ( Passkeys::is_enabled() ) { |
| 121 | \ob_start(); |
| 122 | ?> |
| 123 | <div class="option-pill"> |
| 124 | <label for="basic"> |
| 125 | <input id="basic" name="wp_2fa_enabled_methods" type="radio" value="passkeys"> |
| 126 | <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'passkeys-option-label', true ) ); ?> |
| 127 | <?php |
| 128 | $show = ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) && WP2FA::get_wp2fa_white_label_setting( \esc_attr( Passkeys::METHOD_NAME ) . '-option-label-hint', true ) ) ?? false; |
| 129 | if ( $show ) { |
| 130 | echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( Passkeys::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>'; |
| 131 | } |
| 132 | ?> |
| 133 | </label> |
| 134 | <?php |
| 135 | if ( $show ) { |
| 136 | echo '<p class="description tooltip-content-wrapper" data-' . \esc_attr( Passkeys::METHOD_NAME ) . '-tooltip-content-wrapper>' . \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( \esc_attr( Passkeys::METHOD_NAME ) . '-option-label-hint', true ) ) . '</p>'; |
| 137 | } |
| 138 | ?> |
| 139 | </div> |
| 140 | <?php |
| 141 | $output = ob_get_contents(); |
| 142 | ob_end_clean(); |
| 143 | |
| 144 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 145 | } |
| 146 | |
| 147 | return $methods; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Settings page and first time wizard settings render |
| 152 | * |
| 153 | * @param array $methods - Array with all the methods in which we have to add this one. |
| 154 | * @param boolean $setup_wizard - Is that the first time setup wizard. |
| 155 | * @param string $data_role - Additional HTML data attribute. |
| 156 | * @param mixed $role - Name of the role. |
| 157 | * |
| 158 | * @return array - The array with the methods with all the methods wizard steps. |
| 159 | * |
| 160 | * @since 3.0.0 |
| 161 | */ |
| 162 | public static function passkeys_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) { |
| 163 | $name_prefix = WP_2FA_POLICY_SETTINGS_NAME; |
| 164 | $role_id = ''; |
| 165 | if ( null !== $role && '' !== trim( (string) $role ) ) { |
| 166 | $name_prefix .= "[{$role}]"; |
| 167 | $data_role = 'data-role="' . $role . '"'; |
| 168 | $role_id = '-' . $role; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Filter to check if the interface has to disable this method. |
| 173 | * |
| 174 | * @param bool - Should we disable the method - default false. |
| 175 | * @param Providers - The current method class. |
| 176 | * @param null|string - The role name for which the status must be checked. |
| 177 | * |
| 178 | * @since 2.9.2 |
| 179 | */ |
| 180 | $method_disabled_class = \apply_filters( WP_2FA_PREFIX . 'method_settings_disabled', false, self::$main_class, $role ); |
| 181 | |
| 182 | if ( $method_disabled_class ) { |
| 183 | $method_disabled_class = 'disabled'; |
| 184 | } |
| 185 | |
| 186 | \ob_start(); |
| 187 | ?> |
| 188 | <div id="<?php echo \esc_attr( Passkeys::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper"> |
| 189 | <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 190 | <label class="<?php echo \esc_html( $method_disabled_class ); ?>" for="passkeys<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;"> |
| 191 | <input type="checkbox" id="passkeys<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_passkeys]" value="enable_passkeys" |
| 192 | <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> |
| 193 | |
| 194 | |
| 195 | <?php \checked( Passkeys::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, Passkeys::POLICY_SETTINGS_NAME ), true ); ?> |
| 196 | |
| 197 | > |
| 198 | <?php \esc_html_e( 'Passkeys - ', 'wp-2fa' ); ?><a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank" rel=noopener><?php \esc_html_e( 'complete list of supported 2FA apps.', 'wp-2fa' ); ?></a> |
| 199 | </label> |
| 200 | <?php |
| 201 | if ( $setup_wizard ) { |
| 202 | echo '<p class="description">'; |
| 203 | printf( |
| 204 | /* translators: link to the knowledge base website */ |
| 205 | \esc_html__( 'When using this method, users will need to configure a 2FA app to get the one-time login code. The plugin supports all standard 2FA apps. Refer to the %s for more information. Allowing users to configure a secondary 2FA method is highly recommended. You can configure this in the next step of the wizard. This allows users to log in using an alternative method if they lose access to their primary 2FA device, such as their phone.', 'wp-2fa' ), |
| 206 | '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>' |
| 207 | ); |
| 208 | echo '</p>'; |
| 209 | } |
| 210 | if ( ! $setup_wizard ) { |
| 211 | echo '<p class="description">'; |
| 212 | printf( |
| 213 | /* translators: link to the knowledge base website */ |
| 214 | \esc_html__( 'Refer to the %s for more information on how to setup these apps and which apps are supported.', 'wp-2fa' ), |
| 215 | '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=link&utm_campaign=wp2fa" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>' |
| 216 | ); |
| 217 | echo '</p>'; |
| 218 | } |
| 219 | ?> |
| 220 | </div> |
| 221 | <?php |
| 222 | $output = ob_get_contents(); |
| 223 | ob_end_clean(); |
| 224 | |
| 225 | $methods[ self::get_order( $role, $methods ) ] = $output; |
| 226 | |
| 227 | return $methods; |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Prints the form that prompts the user to authenticate. |
| 232 | * |
| 233 | * @param \WP_User $user - \WP_User object of the logged-in user. |
| 234 | * |
| 235 | * @since 3.0.0 |
| 236 | */ |
| 237 | public static function passkeys_authentication_page( $user ) { |
| 238 | require_once ABSPATH . '/wp-admin/includes/template.php'; |
| 239 | ?> |
| 240 | <?php |
| 241 | if ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) { |
| 242 | echo WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 243 | } else { |
| 244 | echo WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 245 | } |
| 246 | ?> |
| 247 | <p> |
| 248 | </br> |
| 249 | <label for="authcode"><?php \esc_html_e( 'Authentication Code:', 'wp-2fa' ); ?></label> |
| 250 | <input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" /> |
| 251 | <script> |
| 252 | const authcode = document.getElementById('authcode'); |
| 253 | authcode.addEventListener('input', function() { |
| 254 | this.value = this.value.trim(); |
| 255 | }); |
| 256 | </script> |
| 257 | </p> |
| 258 | <?php |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 |