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-backup-codes.js
wp-2fa / js Last commit date
passkeys 1 day ago profile 1 day ago wp2fa-dialog.js 1 day ago wp2fa-premium-badge-dialog.js 1 day ago wp2fa-wizard-backup-codes.js 1 day ago wp2fa-wizard-email.js 1 day ago wp2fa-wizard-totp.js 1 day ago wp2fa-wizard.js 1 day ago
wp2fa-wizard-backup-codes.js
284 lines
1 /**
2 * WP 2FA - Backup Codes Method Module
3 *
4 * Registers backup codes as a secondary/backup method with the wizard via wp.hooks.
5 * After a primary method is validated, the wizard core calls the backup methods flow.
6 * This module renders a "generate backup codes" step with download/copy/print options.
7 *
8 * @package wp2fa
9 * @since 4.0.0
10 *
11 * Hooks this module uses:
12 * - wp2fa_wizard_backup_methods (filter) — registers backup codes as available
13 * - wp2fa_wizard_render_backup_method (action) — renders backup codes generation UI
14 */
15 ( function () {
16 'use strict';
17
18 if ( ! window.wp || ! window.wp.hooks ) {
19 return;
20 }
21
22 var hooks = wp.hooks;
23 var METHOD_ID = 'backup_codes';
24
25 /* -------------------------------------------------------
26 * Register as a backup method
27 * ------------------------------------------------------- */
28 hooks.addFilter( 'wp2fa_wizard_backup_methods', 'wp2fa/backup-codes', function ( methods, context ) {
29 if ( typeof wp2faWizardData === 'undefined' ) {
30 return methods;
31 }
32 var backupMethods = wp2faWizardData.backupMethods || {};
33 if ( ! backupMethods[ METHOD_ID ] ) {
34 return methods;
35 }
36
37 methods.push( {
38 id: METHOD_ID,
39 name: wp2faWizardData.backupMethodLabels && wp2faWizardData.backupMethodLabels[ METHOD_ID ]
40 ? wp2faWizardData.backupMethodLabels[ METHOD_ID ]
41 : 'Backup Codes',
42 description: '',
43 order: 1
44 } );
45
46 return methods;
47 } );
48
49 /* -------------------------------------------------------
50 * Render backup codes generation step
51 * ------------------------------------------------------- */
52 hooks.addAction( 'wp2fa_wizard_render_backup_method', 'wp2fa/backup-codes', function ( methodId, container, context ) {
53 if ( methodId !== METHOD_ID ) {
54 return;
55 }
56
57 var backupData = wp2faWizardData.backupCodesData || {};
58
59 renderGenerateStep();
60
61 function renderGenerateStep() {
62 container.innerHTML = '';
63
64 /* Title — hide when user already selected this from a multi-method list */
65 var titleEl = document.getElementById( 'wp2fa-wizard-title' );
66 if ( titleEl ) {
67 if ( Object.keys( wp2faWizardData.backupMethods || {} ).length > 1 ) {
68 titleEl.innerHTML = '';
69 } else {
70 titleEl.innerHTML = hooks.applyFilters(
71 'wp2fa_wizard_step_title',
72 wp2faWizardData.i18n.backupCodesTitle || '',
73 'backup-method-backup_codes'
74 );
75 }
76 }
77
78 /* Render body via template */
79 var generateTmpl = wp.template( 'wp2fa-backup-codes-generate' );
80 container.innerHTML = generateTmpl( {
81 intro: wp2faWizardData.i18n.backupCodesIntro || 'Backup codes let you log in when you can\'t access your primary 2FA method. Each code can only be used once. We recommend generating and safely storing your backup codes now.',
82 generateLabel: wp2faWizardData.i18n.generateBackupCodes || 'Generate list of backup codes'
83 } );
84
85 /* Generate button event */
86 var generateBtn = container.querySelector( '#wp2fa-backup-codes-generate-btn' );
87 generateBtn.addEventListener( 'click', function () {
88 generateBtn.disabled = true;
89 var spinner = window.wp2faWizard.utils.addSpinner( generateBtn );
90
91 var formData = new FormData();
92 formData.append( 'action', 'wp2fa_run_ajax_generate_json' );
93 formData.append( 'nonce', backupData.nonce || '' );
94
95 fetch( wp2faWizardData.ajaxUrl, {
96 method: 'POST',
97 credentials: 'same-origin',
98 body: formData
99 } )
100 .then( function ( res ) { return res.json(); } )
101 .then( function ( data ) {
102 generateBtn.disabled = false;
103 window.wp2faWizard.utils.removeSpinner( spinner );
104 if ( data.success && data.data && data.data.codes ) {
105 renderCodesDisplay( data.data.codes );
106 } else {
107 var errEl = document.createElement( 'div' );
108 errEl.className = 'wp2fa-wizard-verification-response wp2fa-response-error';
109 errEl.textContent = wp2faWizardData.i18n.backupCodesError || 'Failed to generate backup codes.';
110 container.appendChild( errEl );
111 setTimeout( function () {
112 errEl.textContent = '';
113 errEl.className = 'wp2fa-wizard-verification-response';
114 }, 3000 );
115 }
116 } )
117 .catch( function () {
118 generateBtn.disabled = false;
119 window.wp2faWizard.utils.removeSpinner( spinner );
120 } );
121 } );
122 }
123
124 function renderCodesDisplay( codes ) {
125 container.innerHTML = '';
126
127 /* Title */
128 var titleEl = document.getElementById( 'wp2fa-wizard-title' );
129 if ( titleEl ) {
130 titleEl.innerHTML = hooks.applyFilters(
131 'wp2fa_wizard_step_title',
132 wp2faWizardData.i18n.yourBackupCodes || 'Your backup codes',
133 'backup-method-backup_codes-display'
134 );
135 }
136
137 var codesLines = [];
138 for ( var ci = 0; ci < codes.length; ci++ ) {
139 codesLines.push( ( ci + 1 ) + ': ' + codes[ ci ] );
140 }
141 var codesText = codesLines.join( '\n' );
142
143 /* Render body via template */
144 var displayTmpl = wp.template( 'wp2fa-backup-codes-display' );
145 container.innerHTML = displayTmpl( {
146 intro: wp2faWizardData.i18n.backupCodesGenerated || 'Here are your backup codes. Store them somewhere safe. Each code can only be used once.',
147 codesText: codesText,
148 codesCount: Math.min( codes.length, 10 ),
149 showCopyBtn: !! ( navigator.clipboard || document.queryCommandSupported( 'copy' ) ),
150 copyLabel: wp2faWizardData.i18n.copyBackupCodes || 'Copy',
151 downloadLabel: wp2faWizardData.i18n.downloadBackupCodes || 'Download',
152 printLabel: wp2faWizardData.i18n.printBackupCodes || 'Print',
153 emailLabel: wp2faWizardData.i18n.sendBackupCodesEmail || 'Send via email',
154 emailNonce: backupData.emailNonce || ''
155 } );
156
157 var textarea = container.querySelector( '#wp2fa-backup-codes-textarea' );
158
159 /* Copy button event */
160 var copyBtn = container.querySelector( '#wp2fa-backup-codes-copy-btn' );
161 if ( copyBtn ) {
162 copyBtn.addEventListener( 'click', function () {
163 if ( navigator.clipboard && window.isSecureContext ) {
164 navigator.clipboard.writeText( codesText );
165 } else {
166 textarea.select();
167 document.execCommand( 'copy' );
168 }
169 copyBtn.textContent = wp2faWizardData.i18n.copied || 'Copied!';
170 setTimeout( function () {
171 copyBtn.textContent = wp2faWizardData.i18n.copyBackupCodes || 'Copy';
172 }, 2000 );
173 } );
174 }
175
176 /* Download button event */
177 container.querySelector( '#wp2fa-backup-codes-download-btn' ).addEventListener( 'click', function () {
178 var siteUrl = window.location.hostname || 'site';
179 var blob = new Blob( [ 'Backup codes for ' + siteUrl + '\n\n' + codesText + '\n' ], { type: 'text/plain' } );
180 var url = URL.createObjectURL( blob );
181 var a = document.createElement( 'a' );
182 a.href = url;
183 a.download = 'wp-2fa-backup-codes.txt';
184 document.body.appendChild( a );
185 a.click();
186 document.body.removeChild( a );
187 URL.revokeObjectURL( url );
188 } );
189
190 /* Print button event */
191 container.querySelector( '#wp2fa-backup-codes-print-btn' ).addEventListener( 'click', function () {
192 var printWindow = window.open( '', '_blank' );
193 if ( printWindow ) {
194 var safeText = codesText.replace( /&/g, '&amp;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' ).replace( /"/g, '&quot;' );
195 printWindow.document.write( '<html><head><title>Backup Codes</title></head><body>' );
196 printWindow.document.write( '<h1>Backup Codes</h1>' );
197 printWindow.document.write( '<pre>' + safeText + '</pre>' );
198 printWindow.document.write( '</body></html>' );
199 printWindow.document.close();
200 printWindow.print();
201 }
202 } );
203
204 /* Email button event */
205 var emailBtn = container.querySelector( '#wp2fa-backup-codes-email-btn' );
206 if ( emailBtn ) {
207 emailBtn.addEventListener( 'click', function () {
208 sendCodesViaEmail( emailBtn, codesText );
209 } );
210 }
211
212 /* Footer via template */
213 var footer = document.getElementById( 'wp2fa-wizard-footer' );
214 var displayFooterTmpl = wp.template( 'wp2fa-backup-codes-display-footer' );
215 footer.innerHTML = displayFooterTmpl( {
216 closeLabel: wp2faWizardData.i18n.imReadyClose || "I'm ready, close the wizard"
217 } );
218
219 footer.querySelector( '#wp2fa-backup-codes-close-btn' ).addEventListener( 'click', function () {
220 window.wp2faWizard.nextBackupMethod();
221 } );
222 }
223
224 /**
225 * Send codes via email.
226 *
227 * @param {HTMLElement} emailBtn The email button element.
228 * @param {string} codesText The formatted codes text to send.
229 */
230 function sendCodesViaEmail( emailBtn, codesText ) {
231 var nonce = emailBtn.getAttribute( 'data-nonce' );
232 var ajaxUrl = wp2faWizardData.ajaxUrl;
233
234 if ( ! nonce ) {
235 showStatus( 'Invalid request. Please try again.', true );
236 return;
237 }
238
239 emailBtn.disabled = true;
240 var originalText = emailBtn.textContent;
241
242 var formData = new FormData();
243 formData.append( 'action', 'send_backup_codes_email' );
244 formData.append( '_wpnonce', nonce );
245 formData.append( 'codes', JSON.stringify( codesText ) );
246
247 fetch( ajaxUrl, {
248 method: 'POST',
249 credentials: 'same-origin',
250 body: formData
251 } )
252 .then( function ( res ) { return res.json(); } )
253 .then( function ( data ) {
254 emailBtn.disabled = false;
255 if ( data.success ) {
256 showStatus( wp2faWizardData.i18n.backupCodesEmailSent || 'Codes sent to your email.', false );
257 } else {
258 showStatus( wp2faWizardData.i18n.backupCodesEmailError || 'Failed to send email.', true );
259 }
260 } )
261 .catch( function () {
262 emailBtn.disabled = false;
263 showStatus( wp2faWizardData.i18n.networkError || 'Network error. Please try again.', true );
264 } );
265 }
266
267 /**
268 * Show status message below the buttons.
269 *
270 * @param {string} message The status message.
271 * @param {boolean} isError Whether this is an error message.
272 */
273 function showStatus( message, isError ) {
274 var el = container.querySelector( '#wp2fa-backup-codes-status' );
275 if ( el ) {
276 el.textContent = message;
277 el.style.display = '';
278 el.style.color = isError ? '#d63638' : '#00a32a';
279 el.style.marginTop = '10px';
280 }
281 }
282 } );
283 }() );
284