PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / class-totp-wizard-steps.php
wp-2fa / includes / classes / Admin / Methods Last commit date
Traits 1 day ago passkeys 1 day ago class-backup-codes.php 1 day ago class-email-wizard-steps.php 1 day ago class-email.php 1 day ago class-totp-wizard-steps.php 1 day ago class-totp.php 1 day ago index.php 1 day ago
class-totp-wizard-steps.php
464 lines
1 <?php
2 /**
3 * Responsible for WP2FA user's TOTP manipulation.
4 *
5 * @package wp2fa
6 * @subpackage methods-wizard
7 * @since 2.6.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\TOTP;
20 use WP2FA\Utils\Settings_Utils;
21 use WP2FA\Admin\Helpers\WP_Helper;
22 use WP2FA\Admin\Views\Wizard_Steps;
23 use WP2FA\Admin\Controllers\Methods;
24 use WP2FA\Admin\Helpers\User_Helper;
25 use WP2FA\Authenticator\Authentication;
26 use WP2FA\Admin\Methods\Traits\Methods_Wizards_Trait;
27
28 /**
29 * Class for handling totp codes.
30 *
31 * @since 2.6.0
32 *
33 * @package WP2FA
34 */
35 if ( ! class_exists( '\WP2FA\Methods\Wizards\TOTP_Wizard_Steps' ) ) {
36 /**
37 * TOTP code class, for handling totp (app) code generation and such.
38 *
39 * @since 2.6.0
40 */
41 class TOTP_Wizard_Steps extends Wizard_Steps {
42
43 use Methods_Wizards_Trait;
44
45 /**
46 * Keeps the main class method name, so we can call it when needed.
47 *
48 * @var string
49 *
50 * @since 2.6.0
51 */
52 private static $main_class = TOTP::class;
53
54 /**
55 * The default value of the method order in the wizards.
56 *
57 * @var integer
58 *
59 * @since 2.6.0
60 */
61 private static $order = 1;
62
63 /**
64 * Inits the class hooks
65 *
66 * @return void
67 *
68 * @since 2.4.0
69 */
70 public static function init() {
71 \add_filter( WP_2FA_PREFIX . 'methods_modal_options', array( __CLASS__, 'totp_option' ), 10, 2 );
72 \add_action( WP_2FA_PREFIX . 'modal_methods', array( __CLASS__, 'modal_configure' ) );
73 \add_filter( WP_2FA_PREFIX . 'methods_re_configure', array( __CLASS__, 'totp_re_configure' ), 10, 2 );
74 \add_filter( WP_2FA_PREFIX . 'methods_settings', array( __CLASS__, 'totp_wizard_settings' ), 10, 4 );
75
76 self::common_init();
77 }
78
79 /**
80 * Shows the option to reconfigure email (if applicable)
81 *
82 * @param array $methods - Array of methods collected.
83 * @param string $role - The name of the role to show option to.
84 *
85 * @since 2.6.0
86 *
87 * @return array
88 */
89 public static function totp_re_configure( array $methods, string $role ): array {
90
91 if ( ! TOTP::is_enabled( $role ) ) {
92 return $methods;
93 }
94 \ob_start();
95 ?>
96 <div class="option-pill">
97 <?php echo \wp_kses_post( WP2FA::contextual_reconfigure_text( WP2FA::get_wp2fa_white_label_setting( 'totp_reconfigure_intro', true ), User_Helper::get_user_object()->ID, TOTP::METHOD_NAME ) ); ?>
98 <div class="wp2fa-setup-actions">
99 <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-totp"><?php \esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a>
100 </div>
101 </div>
102 <?php
103 $output = ob_get_contents();
104 ob_end_clean();
105
106 $methods[ self::get_order( $role, $methods ) ] = array(
107 'name' => self::$main_class::METHOD_NAME,
108 'output' => $output,
109 );
110
111 return $methods;
112 }
113
114 /**
115 * Shows the initial totp setup options based on enabled methods
116 *
117 * @param array $methods - Array of methods collected.
118 * @param string $role - The name of the role to show option to.
119 *
120 * @since 2.6.0
121 *
122 * @return array
123 */
124 public static function totp_option( array $methods, string $role ): array {
125 if ( TOTP::is_enabled( $role ) && Methods::is_method_enabled_for_role( TOTP::METHOD_NAME, $role ) ) {
126 \ob_start();
127 ?>
128 <div class="option-pill">
129 <label for="basic">
130 <input id="basic" name="wp_2fa_enabled_methods" type="radio" value="totp">
131 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label', true ) ); ?>
132 <?php
133 $show = ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) && WP2FA::get_wp2fa_white_label_setting( \esc_attr( TOTP::METHOD_NAME ) . '-option-label-hint', true ) ) ?? false;
134 if ( $show ) {
135 echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( TOTP::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>';
136 }
137 ?>
138 </label>
139 <?php
140 if ( $show ) {
141 echo '<p class="description tooltip-content-wrapper" data-' . \esc_attr( TOTP::METHOD_NAME ) . '-tooltip-content-wrapper>' . \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( \esc_attr( TOTP::METHOD_NAME ) . '-option-label-hint', true ) ) . '</p>';
142 }
143 ?>
144 </div>
145 <?php
146 $output = ob_get_contents();
147 ob_end_clean();
148
149 $methods[ self::get_order( $role, $methods ) ] = $output;
150 }
151
152 return $methods;
153 }
154
155 /**
156 * Shows the TOTP modal configuration.
157 *
158 * @param \WP_User $user - WP User object.
159 *
160 * @return void
161 *
162 * @since 2.6.0
163 * @since 3.0.0 - Added $user parameter.
164 */
165 public static function modal_configure( $user ) {
166 if ( TOTP::is_enabled( User_Helper::get_user_role( $user ) ) ) {
167 ?>
168 <div class="wizard-step" id="wp-2fa-wizard-totp">
169 <fieldset>
170 <?php self::totp_configure( $user ); ?>
171 </fieldset>
172 </div>
173 <?php
174 }
175 }
176
177 /**
178 * Reconfigures the totp form
179 *
180 * @param \WP_User $user - WP User object.
181 *
182 * @since 2.6.0
183 * @since 3.0.0 - $user parameter is added.
184 *
185 * @return void
186 */
187 public static function totp_configure( $user ) {
188
189 if ( ! TOTP::is_enabled( User_Helper::get_user_role( $user ) ) ) {
190 return;
191 }
192
193 // Regenerate the code if the method is not in use.
194 if ( TOTP::METHOD_NAME !== User_Helper::get_enabled_method_for_user() ) {
195 TOTP::remove_user_totp_key();
196 }
197
198 /**
199 * Active on modal, additional attribute is required on standard HTML (check below)
200 */
201 $add_step_attributes = 'active';
202
203 /**
204 * Closing div for extra modal wrappers see lines above
205 */
206 $close_div = '';
207
208 $qr_code = '<img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" id="wp-2fa-totp-qrcode" />';
209 $open30_wrapper = '
210 <div class="mb-30 clear-both">
211 ';
212 $open60_wrapper = '
213 <div class="modal-60">
214 ';
215 $open40_wrapper = '
216 <div class="modal-40">
217 ';
218 $close_div = '
219 </div>
220 ';
221
222 ?>
223 <div class="step-setting-wrapper <?php echo \esc_attr( $add_step_attributes ); ?>">
224 <div class="mb-20">
225 <?php echo wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_intro', true ) ); ?>
226 </div>
227 <?php echo $open30_wrapper . $open40_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
228
229 <div class="qr-code-wrapper">
230 <?php echo $qr_code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
231 </div>
232 <?php
233 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
234 echo $open60_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
235 ?>
236
237 <div class="radio-cells option-pill mb-0">
238 <ol class="wizard-custom-counter">
239 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_1', true ) ); ?>
240 <?php
241 if ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) {
242 ?>
243 <span class="wizard-tooltip" data-tooltip-content="data-totp-setup-tooltip-content-wrapper">i</span>
244 <?php } ?>
245 </li>
246 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_2', true ) ); ?>
247 <div class="app-key-wrapper">
248 <input type="text" id="app-key-input" readonly value="<?php echo \esc_html( TOTP::get_totp_decrypted() ); ?>" class="app-key">
249 <?php
250 if ( is_ssl() ) {
251 ?>
252 <span class="click-to-copy"><?php \esc_html_e( 'COPY', 'wp-2fa' ); ?></span>
253 <?php } ?>
254 </div>
255 </li>
256 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_3', true ) ); ?></li>
257 </ol>
258 </div>
259 <?php
260 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
261 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
262 ?>
263 <?php if ( ! empty( WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) ) { ?>
264 <div class="tooltip-content-wrapper" data-totp-setup-tooltip-content-wrapper>
265 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_more_intro', true ) ); ?>
266 </div>
267 <?php } ?>
268 <div class="wp2fa-setup-actions">
269 <button class="button wp-2fa-button-primary" name="next_step_setting" value="<?php \esc_attr_e( 'I\'m Ready', 'wp-2fa' ); ?>" type="button"><?php \esc_html_e( 'I\'m Ready', 'wp-2fa' ); ?></button>
270 <a class="button button-primary wp-2fa-button-secondary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a>
271 <?php
272 /*
273 if ( User_Helper::is_enforced( User_Helper::get_user_object()->ID ) ) {
274 ?>
275 <a class="button button-primary wp-2fa-button-primary modal_logout" <?php echo WP_Helper::create_data_nonce( 'wp-2fa-logout' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_attr_e( 'Log out', 'wp-2fa' ); ?></a>
276 <?php
277 }
278 */
279 ?>
280 </div>
281 </div>
282 <div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>">
283 <div class="mb-20">
284 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_totp_pre', true ) ); ?>
285 </div>
286 <fieldset>
287 <label for="wp-2fa-totp-authcode">
288 <?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?>
289 <input type="tel" name="wp-2fa-totp-authcode" id="wp-2fa-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/>
290 <script>
291 const totp_authcode = document.getElementById('wp-2fa-totp-authcode');
292 totp_authcode.addEventListener('input', function() {
293 this.value = this.value.trim();
294 });
295 </script>
296 </label>
297 <div class="verification-response"></div>
298 </fieldset>
299 <input type="hidden" name="wp-2fa-totp-key" value="<?php echo \esc_attr( TOTP::get_totp_decrypted() ); ?>" />
300
301 <a href="#" class="modal__btn button button-primary wp-2fa-button-primary" data-validate-authcode-ajax <?php echo WP_Helper::create_data_nonce( 'wp-2fa-validate-authcode' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php \esc_html_e( 'Validate & Save', 'wp-2fa' ); ?></a>
302 <button class="modal__btn wp-2fa-button-secondary button button-secondary wp-2fa-button-secondary" data-close-2fa-modal aria-label="Close this dialog window"><?php \esc_html_e( 'Cancel', 'wp-2fa' ); ?></button>
303 </div>
304
305 <?php
306 }
307
308 /**
309 * Settings page and first time wizard settings render
310 *
311 * @param array $methods - Array with all the methods in which we have to add this one.
312 * @param boolean $setup_wizard - Is that the first time setup wizard.
313 * @param string $data_role - Additional HTML data attribute.
314 * @param mixed $role - Name of the role.
315 *
316 * @return array - The array with the methods with all the methods wizard steps.
317 *
318 * @since 2.6.0
319 */
320 public static function totp_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) {
321 $name_prefix = WP_2FA_POLICY_SETTINGS_NAME;
322 $role_id = '';
323 if ( null !== $role && '' !== trim( (string) $role ) ) {
324 $name_prefix .= "[{$role}]";
325 $data_role = 'data-role="' . $role . '"';
326 $role_id = '-' . $role;
327 }
328
329 /**
330 * Filter to check if the interface has to disable this method.
331 *
332 * @param bool - Should we disable the method - default false.
333 * @param Providers - The current method class.
334 * @param null|string - The role name for which the status must be checked.
335 *
336 * @since 2.9.2
337 */
338 $method_disabled_class = \apply_filters( WP_2FA_PREFIX . 'method_settings_disabled', false, self::$main_class, $role );
339
340 if ( $method_disabled_class ) {
341 $method_disabled_class = 'disabled';
342 }
343
344 \ob_start();
345 if ( ! \class_exists( \XMLWriter::class ) ) {
346 TOTP::disable_globally();
347 ?>
348 <div id="<?php echo \esc_attr( TOTP::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
349 <?php
350 \esc_html_e( 'Setting up TOTP (one-time codes via an authenticator app) requires the libxml PHP extension to generate the QR code. Please contact your hosting provider and ask them to enable it on your server.', 'wp-2fa' );
351
352 ?>
353 </div>
354 <?php
355 $output = ob_get_contents();
356 ob_end_clean();
357
358 $methods[ self::get_order( $role, $methods ) ] = $output;
359
360 return $methods;
361
362 }
363 ?>
364 <div id="<?php echo \esc_attr( TOTP::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
365 <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
366 <label class="<?php echo \esc_html( $method_disabled_class ); ?>" for="totp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;">
367 <input type="checkbox" id="totp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_totp]" value="enable_totp"
368 <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
369
370
371 <?php \checked( TOTP::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, TOTP::POLICY_SETTINGS_NAME ), true ); ?>
372
373 >
374 <?php echo \esc_html( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label', true ) ) . ' - '; ?><a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=totp_aplications_help" target="_blank" rel=noopener><?php \esc_html_e( 'complete list of supported 2FA apps.', 'wp-2fa' ); ?></a>
375 </label>
376 <?php
377 if ( $setup_wizard ) {
378 echo '<p class="description">';
379 printf(
380 /* translators: link to the knowledge base website */
381 \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' ),
382 '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_how_to_setup_2fa_apps" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>'
383 );
384 echo '</p>';
385 }
386 if ( ! $setup_wizard ) {
387 echo '<p class="description">';
388 printf(
389 /* translators: link to the knowledge base website */
390 \esc_html__( 'Refer to the %s for step-by-step instructions on how to set up this method.', 'wp-2fa' ),
391 '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-wordpress-user/?utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_how_to_setup_totp_user_settings" target="_blank">' . \esc_html__( 'setup guide', 'wp-2fa' ) . '</a>'
392 );
393 echo '</p>';
394 }
395 ?>
396 </div>
397 <?php
398 $output = ob_get_contents();
399 ob_end_clean();
400
401 $methods[ self::get_order( $role, $methods ) ] = $output;
402
403 return $methods;
404 }
405
406 /**
407 * Prints the form that prompts the user to authenticate.
408 *
409 * @param \WP_User $user - \WP_User object of the logged-in user.
410 *
411 * @since 2.6.0
412 */
413 public static function totp_authentication_page( $user ) {
414 require_once ABSPATH . '/wp-admin/includes/template.php';
415 ?>
416 <?php
417 if ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) {
418 echo WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
419 } else {
420 echo WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
421 }
422 ?>
423 <p>
424 </br>
425 <label for="authcode"><?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?></label>
426 <input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" />
427 <script>
428 const authcode = document.getElementById('authcode');
429 authcode.addEventListener('input', function() {
430 this.value = this.value.trim();
431 });
432 </script>
433 </p>
434 <?php
435 }
436
437 /**
438 * Returns an inline hint shown next to the TOTP method title.
439 *
440 * @return string
441 *
442 * @since 4.0.0
443 */
444 public static function get_method_title_hint(): string {
445 return ''; //' - <a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=totp_aplications_help" target="_blank" rel="noopener">' . \esc_html__( 'complete list of supported 2FA apps.', 'wp-2fa' ) . '</a>';
446 }
447
448 /**
449 * Returns a description for the TOTP method shown below the title.
450 *
451 * @return string
452 *
453 * @since 4.0.0
454 */
455 public static function get_method_description(): string {
456 return \wp_sprintf(
457 /* translators: link to the knowledge base website */
458 \esc_html__( 'Refer to the %s for step-by-step instructions on how to set up this method.', 'wp-2fa' ),
459 '<a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-wordpress-user/?utm_source=plugin&utm_medium=wp2fa&utm_campaign=guide_how_to_setup_totp_user_settings" target="_blank">' . \esc_html__( 'setup guide', 'wp-2fa' ) . '</a>'
460 );
461 }
462 }
463 }
464