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 / js / wp2fa-wizard-totp.js
wp-2fa / js Last commit date
passkeys 5 days ago profile 5 days ago wp2fa-dialog.js 5 days ago wp2fa-premium-badge-dialog.js 5 days ago wp2fa-wizard-backup-codes.js 5 days ago wp2fa-wizard-email.js 5 days ago wp2fa-wizard-totp.js 5 days ago wp2fa-wizard.js 5 days ago
wp2fa-wizard-totp.js
231 lines
1 /**
2 * WP 2FA - TOTP Method Module
3 *
4 * Registers the TOTP (authenticator app) method with the wizard via wp.hooks.
5 * Renders the QR code setup and verification steps.
6 *
7 * @package wp2fa
8 * @since 4.0.0
9 *
10 * Hooks this module uses:
11 * - wp2fa_wizard_methods (filter) — registers TOTP as an available method
12 * - wp2fa_wizard_render_method (action) — renders TOTP-specific configuration UI
13 */
14 ( function () {
15 'use strict';
16
17 if ( ! window.wp || ! window.wp.hooks ) {
18 return;
19 }
20
21 var hooks = wp.hooks;
22 var METHOD_ID = 'totp';
23
24 /* -------------------------------------------------------
25 * Register method in the selection list
26 * ------------------------------------------------------- */
27 hooks.addFilter( 'wp2fa_wizard_methods', 'wp2fa/totp', function ( methods, context ) {
28 /* Only add if server-side says TOTP is enabled for this user */
29 if ( typeof wp2faWizardData === 'undefined' ) {
30 return methods;
31 }
32 var enabledMethods = wp2faWizardData.enabledMethods || {};
33 if ( ! enabledMethods[ METHOD_ID ] ) {
34 return methods;
35 }
36
37 methods.push( {
38 id: METHOD_ID,
39 name: wp2faWizardData.methodLabels && wp2faWizardData.methodLabels[ METHOD_ID ]
40 ? wp2faWizardData.methodLabels[ METHOD_ID ]
41 : 'One-time code via 2FA App (TOTP)',
42 // description: wp2faWizardData.methodDescriptions && wp2faWizardData.methodDescriptions[ METHOD_ID ]
43 // ? wp2faWizardData.methodDescriptions[ METHOD_ID ]
44 // : 'Refer to the guide on how to set up 2FA apps for more information on how to setup these apps and which apps are supported.',
45 order: ( wp2faWizardData.methodOrders && typeof wp2faWizardData.methodOrders[ METHOD_ID ] !== 'undefined' )
46 ? wp2faWizardData.methodOrders[ METHOD_ID ]
47 : 1
48 } );
49
50 return methods;
51 } );
52
53 /* -------------------------------------------------------
54 * Render TOTP configuration
55 * ------------------------------------------------------- */
56 hooks.addAction( 'wp2fa_wizard_render_method', 'wp2fa/totp', function ( methodId, container, context ) {
57 if ( methodId !== METHOD_ID ) {
58 return;
59 }
60
61 var totpData = wp2faWizardData.totpData || {};
62
63 renderSetupStep();
64
65 function renderSetupStep() {
66 container.innerHTML = '';
67
68 /* Title update */
69 var titleEl = document.getElementById( 'wp2fa-wizard-title' );
70 if ( titleEl ) {
71 titleEl.innerHTML = hooks.applyFilters(
72 'wp2fa_wizard_step_title',
73 wp2faWizardData.i18n.settingUpTotp || 'Setting up TOTP (one-time code via app)',
74 'configure-totp-setup'
75 );
76 }
77
78 /* Render body via template */
79 var setupTmpl = wp.template( 'wp2fa-totp-setup' );
80 container.innerHTML = setupTmpl( {
81 qrCodeUrl: totpData.qrCodeUrl || '',
82 totpKey: totpData.totpKey || '',
83 step1: wp2faWizardData.i18n.totpStep1 || 'Download and start the application of your choice',
84 step2: wp2faWizardData.i18n.totpStep2 || 'From within the application scan the QR code provided on the left. Otherwise, enter the following code manually in the application:',
85 step3: wp2faWizardData.i18n.totpStep3 || 'Click the "I\'m ready" button below when you complete the application setup process to proceed with the wizard.',
86 copyKeyLabel: wp2faWizardData.i18n.copyKey || 'Copy key'
87 } );
88
89 /* Copy button event */
90 var copyBtn = container.querySelector( '#wp2fa-totp-key-copy' );
91 var keyInput = container.querySelector( '#wp2fa-totp-key-input' );
92 if ( copyBtn && keyInput ) {
93 copyBtn.addEventListener( 'click', function () {
94 if ( navigator.clipboard && window.isSecureContext ) {
95 navigator.clipboard.writeText( keyInput.value );
96 } else {
97 keyInput.select();
98 document.execCommand( 'copy' );
99 }
100
101 // Show copied tooltip.
102 var tooltip = copyBtn.querySelector( '.wp2fa-copy-tooltip' );
103 if ( ! tooltip ) {
104 tooltip = document.createElement( 'span' );
105 tooltip.className = 'wp2fa-copy-tooltip';
106 copyBtn.style.position = 'relative';
107 copyBtn.appendChild( tooltip );
108 }
109 tooltip.textContent = wp2faWizardData.i18n.copied || 'Copied!';
110 tooltip.classList.add( 'visible' );
111 setTimeout( function () {
112 tooltip.classList.remove( 'visible' );
113 }, 1500 );
114 } );
115 }
116
117 /* Footer via template */
118 var footer = document.getElementById( 'wp2fa-wizard-footer' );
119 var setupFooterTmpl = wp.template( 'wp2fa-totp-setup-footer' );
120 footer.innerHTML = setupFooterTmpl( {
121 goBackLabel: wp2faWizardData.i18n.goBack || 'Go back',
122 continueLabel: wp2faWizardData.i18n.continueBtn || 'Continue'
123 } );
124
125 footer.querySelector( '#wp2fa-totp-btn-goback' ).addEventListener( 'click', function ( e ) {
126 e.preventDefault();
127 hooks.doAction( 'wp2fa_wizard_before_back', 'configure-totp-setup', 'select-method' );
128 window.wp2faWizard.goToStep( 'select-method' );
129 hooks.doAction( 'wp2fa_wizard_after_back', 'select-method' );
130 } );
131
132 footer.querySelector( '#wp2fa-totp-btn-ready' ).addEventListener( 'click', function () {
133 renderVerifyStep();
134 } );
135 }
136
137 function renderVerifyStep() {
138 container.innerHTML = '';
139
140 /* Title */
141 var titleEl = document.getElementById( 'wp2fa-wizard-title' );
142 if ( titleEl ) {
143 titleEl.innerHTML = hooks.applyFilters(
144 'wp2fa_wizard_step_title',
145 wp2faWizardData.i18n.verifyConfig || 'Verify configuration',
146 'configure-totp-verify'
147 );
148 }
149
150 /* Render body via template */
151 var verifyTmpl = wp.template( 'wp2fa-totp-verify' );
152 container.innerHTML = verifyTmpl( {
153 description: wp2faWizardData.i18n.totpVerifyIntro || 'Please type in the one-time code from your authenticator app to finalize setup.',
154 codeLabel: wp2faWizardData.i18n.verificationCode || 'Verification Code:',
155 placeholder: '000000'
156 } );
157
158 var input = container.querySelector( '#wp2fa-totp-authcode' );
159 var response = container.querySelector( '#wp2fa-totp-verification-response' );
160
161 input.addEventListener( 'input', function () {
162 this.value = this.value.replace( /\s/g, '' );
163 } );
164
165 /* Footer via template */
166 var footer = document.getElementById( 'wp2fa-wizard-footer' );
167 var verifyFooterTmpl = wp.template( 'wp2fa-totp-verify-footer' );
168 footer.innerHTML = verifyFooterTmpl( {
169 goBackLabel: wp2faWizardData.i18n.goBack || 'Go back',
170 validateLabel: wp2faWizardData.i18n.validateSave || 'Validate & Save'
171 } );
172
173 footer.querySelector( '#wp2fa-totp-verify-btn-back' ).addEventListener( 'click', function () {
174 renderSetupStep();
175 } );
176
177 var validateBtn = footer.querySelector( '#wp2fa-totp-btn-validate' );
178 validateBtn.addEventListener( 'click', function () {
179 var code = input.value.trim();
180 if ( ! code ) {
181 window.wp2faWizard.utils.showResponse( response, 'error', wp2faWizardData.i18n.enterCode || 'Please enter the verification code.' );
182 return;
183 }
184
185 if ( ! /^\d{6}$/.test( code ) ) {
186 window.wp2faWizard.utils.showResponse( response, 'error', wp2faWizardData.i18n.invalidCodeFormat || 'Invalid code. Please enter the correct OTP code generated by your Authenticator app.' );
187 return;
188 }
189
190 validateBtn.disabled = true;
191 var spinner = window.wp2faWizard.utils.addSpinner( validateBtn );
192
193 hooks.doAction( 'wp2fa_wizard_validate_method', METHOD_ID, container );
194
195 var formData = new FormData();
196 formData.append( 'action', 'validate_authcode_via_ajax' );
197 formData.append( 'form[wp-2fa-totp-authcode]', code );
198 formData.append( 'form[wp-2fa-totp-key]', totpData.totpKey || '' );
199 formData.append( '_wpnonce', context.nonce );
200
201 fetch( wp2faWizardData.ajaxUrl, {
202 method: 'POST',
203 credentials: 'same-origin',
204 body: formData
205 } )
206 .then( function ( res ) { return res.json(); } )
207 .then( function ( data ) {
208 validateBtn.disabled = false;
209 window.wp2faWizard.utils.removeSpinner( spinner );
210
211 if ( data.success ) {
212 window.wp2faWizard.utils.showResponse( response, 'success', wp2faWizardData.i18n.totpSuccess || 'TOTP configured successfully!' );
213 hooks.doAction( 'wp2fa_wizard_method_validated', METHOD_ID );
214 } else {
215 var errMsg = window.wp2faWizard.utils.extractAjaxError( data, wp2faWizardData.i18n.invalidCode || 'Invalid or expired OTP code. Please try again.' );
216 window.wp2faWizard.utils.showResponse( response, 'error', errMsg );
217 }
218 } )
219 .catch( function () {
220 validateBtn.disabled = false;
221 window.wp2faWizard.utils.removeSpinner( spinner );
222 window.wp2faWizard.utils.showResponse( response, 'error', wp2faWizardData.i18n.networkError || 'Network error. Please try again.' );
223 } );
224 } );
225
226 input.focus();
227 }
228
229 } );
230 }() );
231