PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.9.0
WP 2FA – Two-factor authentication for WordPress v2.9.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 11 months ago class-backup-codes.php 11 months ago class-email-wizard-steps.php 11 months ago class-email.php 11 months ago class-totp-wizard-steps.php 11 months ago class-totp.php 11 months ago index.php 11 months ago
class-totp-wizard-steps.php
392 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 2025 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__, 'totp_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
77 /**
78 * Shows the option to reconfigure email (if applicable)
79 *
80 * @param array $methods - Array of methods collected.
81 * @param string $role - The name of the role to show option to.
82 *
83 * @since 2.6.0
84 *
85 * @return array
86 */
87 public static function totp_re_configure( array $methods, string $role ): array {
88
89 if ( ! TOTP::is_enabled( $role ) ) {
90 return $methods;
91 }
92 \ob_start();
93 ?>
94 <div class="option-pill">
95 <?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 ) ); ?>
96 <div class="wp2fa-setup-actions">
97 <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="2fa-wizard-totp"><?php \esc_html_e( 'Reset Key', 'wp-2fa' ); ?></a>
98 </div>
99 </div>
100 <?php
101 $output = ob_get_contents();
102 ob_end_clean();
103
104 $methods[ self::get_order( $role, $methods ) ] = array(
105 'name' => self::$main_class::METHOD_NAME,
106 'output' => $output,
107 );
108
109 return $methods;
110 }
111
112 /**
113 * Shows the initial totp setup options based on enabled methods
114 *
115 * @param array $methods - Array of methods collected.
116 * @param string $role - The name of the role to show option to.
117 *
118 * @since 2.6.0
119 *
120 * @return array
121 */
122 public static function totp_option( array $methods, string $role ): array {
123 if ( TOTP::is_enabled( $role ) && Methods::is_method_enabled_for_role( TOTP::METHOD_NAME, $role ) ) {
124 \ob_start();
125 ?>
126 <div class="option-pill">
127 <label for="basic">
128 <input id="basic" name="wp_2fa_enabled_methods" type="radio" value="totp">
129 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'totp-option-label', true ) ); ?>
130 <?php
131 $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;
132 if ( $show ) {
133 echo '<br><span class="wizard-tooltip" data-tooltip-content="data-' . \esc_attr( TOTP::METHOD_NAME ) . '-tooltip-content-wrapper">i</span>';
134 }
135 ?>
136 </label>
137 <?php
138 if ( $show ) {
139 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>';
140 }
141 ?>
142 </div>
143 <?php
144 $output = ob_get_contents();
145 ob_end_clean();
146
147 $methods[ self::get_order( $role, $methods ) ] = $output;
148 }
149
150 return $methods;
151 }
152
153 /**
154 * Shows the TOTP modal configuration.
155 *
156 * @return void
157 *
158 * @since 2.6.0
159 */
160 public static function totp_modal_configure() {
161 if ( TOTP::is_enabled() ) {
162 ?>
163 <div class="wizard-step" id="2fa-wizard-totp">
164 <fieldset>
165 <?php self::totp_configure(); ?>
166 </fieldset>
167 </div>
168 <?php
169 }
170 }
171
172 /**
173 * Reconfigures the totp form
174 *
175 * @since 2.6.0
176 *
177 * @return void
178 */
179 public static function totp_configure() {
180
181 if ( ! TOTP::is_enabled() ) {
182 return;
183 }
184
185 // Regenerate the code if the method is not in use.
186 if ( TOTP::METHOD_NAME !== User_Helper::get_enabled_method_for_user() ) {
187 TOTP::remove_user_totp_key();
188 }
189
190 /**
191 * Active on modal, additional attribute is required on standard HTML (check below)
192 */
193 $add_step_attributes = 'active';
194
195 /**
196 * Closing div for extra modal wrappers see lines above
197 */
198 $close_div = '';
199
200 $qr_code = '<img class="qr-code" src="' . ( TOTP::get_qr_code() ) . '" id="wp-2fa-totp-qrcode" />';
201 $open30_wrapper = '
202 <div class="mb-30 clear-both">
203 ';
204 $open60_wrapper = '
205 <div class="modal-60">
206 ';
207 $open40_wrapper = '
208 <div class="modal-40">
209 ';
210 $close_div = '
211 </div>
212 ';
213
214 ?>
215 <div class="step-setting-wrapper <?php echo \esc_attr( $add_step_attributes ); ?>">
216 <div class="mb-20">
217 <?php echo wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_intro', true ) ); ?>
218 </div>
219 <?php echo $open30_wrapper . $open40_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
220
221 <div class="qr-code-wrapper">
222 <?php echo $qr_code; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
223 </div>
224 <?php
225 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
226 echo $open60_wrapper; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
227 ?>
228
229 <div class="radio-cells option-pill mb-0">
230 <ol class="wizard-custom-counter">
231 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_1', true ) ); ?>
232 <?php
233 if ( 'show_help_text' === WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) {
234 ?>
235 <span class="wizard-tooltip" data-tooltip-content="data-totp-setup-tooltip-content-wrapper">i</span>
236 <?php } ?>
237 </li>
238 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_2', true ) ); ?>
239 <div class="app-key-wrapper">
240 <input type="text" id="app-key-input" readonly value="<?php echo \esc_html( TOTP::get_totp_decrypted() ); ?>" class="app-key">
241 <?php
242 if ( is_ssl() ) {
243 ?>
244 <span class="click-to-copy"><?php \esc_html_e( 'COPY', 'wp-2fa' ); ?></span>
245 <?php } ?>
246 </div>
247 </li>
248 <li><?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_help_totp_step_3', true ) ); ?></li>
249 </ol>
250 </div>
251 <?php
252 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
253 echo $close_div; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
254 ?>
255 <?php if ( ! empty( WP2FA::get_wp2fa_white_label_setting( 'show_help_text' ) ) ) : ?>
256 <div class="tooltip-content-wrapper" data-totp-setup-tooltip-content-wrapper>
257 <p class="description"><?php \esc_html_e( 'Click on the icon of the app that you are using for a detailed guide on how to set it up.', 'wp-2fa' ); ?></p>
258 <div class="apps-wrapper">
259 <?php foreach ( Authentication::get_apps() as $app ) { ?>
260 <a href="https://melapress.com/support/kb/wp-2fa-configuring-2fa-apps/?&utm_source=plugin&utm_medium=wp2fa&utm_campaign=authentication_help#<?php echo $app['hash']; ?>" target="_blank" class="app-logo"><img src="<?php echo \esc_url( WP_2FA_URL . 'dist/images/' . $app['logo'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></a>
261 <?php } ?>
262 </div>
263 </div>
264 <?php endif; ?>
265 <div class="wp2fa-setup-actions">
266 <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>
267 <a class="button button-primary wp-2fa-button-secondary modal_cancel"><?php \esc_attr_e( 'Cancel', 'wp-2fa' ); ?></a>
268 </div>
269 </div>
270 <div class="step-setting-wrapper" data-step-title="<?php \esc_html_e( 'Verify configuration', 'wp-2fa' ); ?>">
271 <div class="mb-20">
272 <?php echo \wp_kses_post( WP2FA::get_wp2fa_white_label_setting( 'method_verification_totp_pre', true ) ); ?>
273 </div>
274 <fieldset>
275 <label for="2fa-totp-authcode">
276 <?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?>
277 <input type="tel" name="wp-2fa-totp-authcode" id="wp-2fa-totp-authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off"/>
278 <script>
279 const totp_authcode = document.getElementById('wp-2fa-totp-authcode');
280 totp_authcode.addEventListener('input', function() {
281 this.value = this.value.trim();
282 });
283 </script>
284 </label>
285 <div class="verification-response"></div>
286 </fieldset>
287 <input type="hidden" name="wp-2fa-totp-key" value="<?php echo \esc_attr( TOTP::get_totp_decrypted() ); ?>" />
288
289 <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>
290 <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>
291 </div>
292
293 <?php
294 }
295
296 /**
297 * Settings page and first time wizard settings render
298 *
299 * @param array $methods - Array with all the methods in which we have to add this one.
300 * @param boolean $setup_wizard - Is that the first time setup wizard.
301 * @param string $data_role - Additional HTML data attribute.
302 * @param mixed $role - Name of the role.
303 *
304 * @return array - The array with the methods with all the methods wizard steps.
305 *
306 * @since 2.6.0
307 */
308 public static function totp_wizard_settings( array $methods, bool $setup_wizard, string $data_role, $role = null ) {
309 $name_prefix = WP_2FA_POLICY_SETTINGS_NAME;
310 $role_id = '';
311 if ( null !== $role && '' !== trim( (string) $role ) ) {
312 $name_prefix .= "[{$role}]";
313 $data_role = 'data-role="' . $role . '"';
314 $role_id = '-' . $role;
315 }
316 \ob_start();
317 ?>
318 <div id="<?php echo \esc_attr( TOTP::METHOD_NAME ); ?>-method-wrapper" class="method-wrapper">
319 <?php echo self::hidden_order_setting( $role ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
320 <label for="totp<?php echo \esc_attr( $role_id ); ?>" style="margin-bottom: 0 !important;">
321 <input type="checkbox" id="totp<?php echo \esc_attr( $role_id ); ?>" name="<?php echo \esc_attr( $name_prefix ); ?>[enable_totp]" value="enable_totp"
322 <?php echo $data_role; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
323
324
325 <?php \checked( TOTP::POLICY_SETTINGS_NAME, Settings_Utils::get_setting_role( $role, TOTP::POLICY_SETTINGS_NAME ), true ); ?>
326
327 >
328 <?php \esc_html_e( 'One-time code via 2FA App (TOTP) - ', 'wp-2fa' ); ?><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>
329 </label>
330 <?php
331 if ( $setup_wizard ) {
332 echo '<p class="description">';
333 printf(
334 /* translators: link to the knowledge base website */
335 \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 set up a secondary 2FA method is highly recommended. You can do this in the next step of the wizard. This will allow users to log in using an alternative method should they, for example lose access to their phone.', 'wp-2fa' ),
336 '<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>'
337 );
338 echo '</p>';
339 }
340 if ( ! $setup_wizard ) {
341 echo '<p class="description">';
342 printf(
343 /* translators: link to the knowledge base website */
344 \esc_html__( 'Refer to the %s for more information on how to setup these apps and which apps are supported.', 'wp-2fa' ),
345 '<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_2" target="_blank">' . \esc_html__( 'guide on how to set up 2FA apps', 'wp-2fa' ) . '</a>'
346 );
347 echo '</p>';
348 }
349 ?>
350 </div>
351 <?php
352 $output = ob_get_contents();
353 ob_end_clean();
354
355 $methods[ self::get_order( $role, $methods ) ] = $output;
356
357 return $methods;
358 }
359
360 /**
361 * Prints the form that prompts the user to authenticate.
362 *
363 * @param \WP_User $user - \WP_User object of the logged-in user.
364 *
365 * @since 2.6.0
366 */
367 public static function totp_authentication_page( $user ) {
368 require_once ABSPATH . '/wp-admin/includes/template.php';
369 ?>
370 <?php
371 if ( 'use-custom' == WP2FA::get_wp2fa_white_label_setting( 'use_custom_2fa_message' ) ) {
372 echo WP2FA::get_wp2fa_white_label_setting( 'custom-text-app-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
373 } else {
374 echo WP2FA::get_wp2fa_white_label_setting( 'default-text-code-page', true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
375 }
376 ?>
377 <p>
378 </br>
379 <label for="authcode"><?php \esc_html_e( 'Verification Code:', 'wp-2fa' ); ?></label>
380 <input type="tel" name="authcode" id="authcode" class="input" value="" size="20" pattern="[0-9]*" autocomplete="off" />
381 <script>
382 const authcode = document.getElementById('authcode');
383 authcode.addEventListener('input', function() {
384 this.value = this.value.trim();
385 });
386 </script>
387 </p>
388 <?php
389 }
390 }
391 }
392