PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.8
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.8
3.0.0 2.11.12 2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 All 88 releases
vigilante / assets / js / two-factor-admin.js

two-factor-admin.js in Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… 2.11.8, at assets/js/two-factor-admin.js

539 lines 23.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Two-Factor Authentication Admin JavaScript
3 *
4 * Handles method selector, AJAX user search, TOTP reset,
5 * notification controls, and TOTP profile setup
6 *
7 * @package Vigilante
8 */
9
10 (function($) {
11 'use strict';
12
13 // =========================================================================
14 // Admin settings module
15 // =========================================================================
16 var Vigilante2FA = {
17 searchTimeout: null,
18 resetSearchTimeout: null,
19 minSearchLength: 2,
20
21 init: function() {
22 this.bindEvents();
23 this.toggleSettingsVisibility();
24 this.toggleMethodFields();
25 },
26
27 bindEvents: function() {
28 var self = this;
29
30 $(document).on('change', '#vigilante_2fa_enabled', function() {
31 self.toggleSettingsVisibility();
32 });
33
34 $(document).on('change', 'input[name="login_security[two_factor][method]"]', function() {
35 self.toggleMethodFields();
36 });
37
38 // Exclusion search
39 $(document).on('input', '#vigilante_2fa_user_search', function() {
40 self.handleUserSearch($(this), '.vigilante-2fa-search-results', 'vigilante_search_users_2fa');
41 });
42 $(document).on('focus', '#vigilante_2fa_user_search', function() {
43 var $r = $(this).siblings('.vigilante-2fa-search-results');
44 if ($r.children().length > 0) $r.addClass('active');
45 });
46 $(document).on('click', '.vigilante-2fa-search-results .search-result-item', function() {
47 self.addExcludedUser($(this));
48 });
49 $(document).on('click', '.vigilante-2fa-excluded-user .remove-user', function() {
50 $(this).closest('.vigilante-2fa-excluded-user').remove();
51 self.toggleResetButton();
52 });
53
54 // Close dropdowns on outside click
55 $(document).on('click', function(e) {
56 if (!$(e.target).closest('.vigilante-2fa-user-search').length) {
57 $('.vigilante-2fa-search-results, .vigilante-totp-reset-results').removeClass('active');
58 }
59 });
60
61 // Notification
62 $(document).on('click', '#vigilante_2fa_send_notification', function(e) {
63 e.preventDefault();
64 self.sendNotification($(this));
65 });
66
67 // TOTP reset search
68 $(document).on('input', '#vigilante_totp_reset_search', function() {
69 self.handleResetSearch($(this));
70 });
71 $(document).on('focus', '#vigilante_totp_reset_search', function() {
72 var $r = $(this).siblings('.vigilante-totp-reset-results');
73 if ($r.children().length > 0) $r.addClass('active');
74 });
75 $(document).on('click', '.vigilante-totp-reset-results .search-result-item', function() {
76 self.addResetUser($(this));
77 });
78 $(document).on('click', '.vigilante-totp-reset-selected .remove-user', function() {
79 $(this).closest('.vigilante-totp-reset-user').remove();
80 self.toggleResetButton();
81 });
82 $(document).on('click', '#vigilante_totp_reset_btn', function(e) {
83 e.preventDefault();
84 self.resetTotpUsers($(this));
85 });
86 },
87
88 toggleSettingsVisibility: function() {
89 var enabled = $('#vigilante_2fa_enabled').is(':checked');
90 $('.vigilante-2fa-settings-wrapper').toggleClass('vigilante-2fa-settings-disabled', !enabled);
91 },
92
93 toggleMethodFields: function() {
94 var method = $('input[name="login_security[two_factor][method]"]:checked').val() || 'email';
95 $('.vigilante-2fa-method-option').removeClass('selected');
96 $('input[name="login_security[two_factor][method]"]:checked').closest('.vigilante-2fa-method-option').addClass('selected');
97 $('.vigilante-2fa-totp-only').toggle('totp' === method);
98 $('.vigilante-2fa-email-only').toggle('email' === method);
99 },
100
101 handleUserSearch: function($input, resultsSelector, action) {
102 var self = this;
103 var query = $input.val().trim();
104 var $results = $input.siblings(resultsSelector || '.vigilante-2fa-search-results');
105
106 if (this.searchTimeout) clearTimeout(this.searchTimeout);
107 if (query.length < this.minSearchLength) {
108 $results.removeClass('active').empty();
109 return;
110 }
111
112 $results.addClass('active').html('<div class="searching">' + self.str('searching') + '</div>');
113
114 this.searchTimeout = setTimeout(function() {
115 $.post(self.ajaxUrl(), {
116 action: action || 'vigilante_search_users_2fa',
117 nonce: self.nonce(),
118 query: query,
119 exclude: self.getExcludedUserIds()
120 }, function(response) {
121 if (response.success && response.data.length > 0) {
122 self.renderResults(response.data, $results);
123 } else {
124 $results.html('<div class="no-results">' + self.str('noUsersFound') + '</div>');
125 }
126 });
127 }, 300);
128 },
129
130 handleResetSearch: function($input) {
131 var self = this;
132 var query = $input.val().trim();
133 var $results = $input.siblings('.vigilante-totp-reset-results');
134
135 if (this.resetSearchTimeout) clearTimeout(this.resetSearchTimeout);
136 if (query.length < this.minSearchLength) {
137 $results.removeClass('active').empty();
138 return;
139 }
140
141 $results.addClass('active').html('<div class="searching">' + self.str('searching') + '</div>');
142
143 this.resetSearchTimeout = setTimeout(function() {
144 $.post(self.ajaxUrl(), {
145 action: 'vigilante_search_totp_users',
146 nonce: self.nonce(),
147 query: query
148 }, function(response) {
149 if (response.success && response.data.length > 0) {
150 self.renderResults(response.data, $results);
151 } else {
152 $results.html('<div class="no-results">No TOTP users found</div>');
153 }
154 });
155 }, 300);
156 },
157
158 renderResults: function(users, $results) {
159 var self = this;
160 var html = '';
161 $.each(users, function(i, u) {
162 html += '<div class="search-result-item" data-user-id="' + u.ID + '" data-user-email="' + self.esc(u.user_email) + '" data-user-display="' + self.esc(u.display_name) + '">';
163 html += '<img class="user-avatar" src="' + self.esc(u.avatar) + '" alt="">';
164 html += '<div class="user-info"><div class="user-name">' + self.esc(u.display_name) + '</div>';
165 html += '<div class="user-email">' + self.esc(u.user_email) + '</div></div></div>';
166 });
167 $results.html(html);
168 },
169
170 addExcludedUser: function($item) {
171 var uid = $item.data('user-id');
172 if ($('.vigilante-2fa-excluded-user[data-user-id="' + uid + '"]').length) return;
173
174 $('.vigilante-2fa-excluded-users').append(
175 '<div class="vigilante-2fa-excluded-user" data-user-id="' + uid + '">' +
176 '<span class="user-display">' + this.esc($item.data('user-display')) + ' (' + this.esc($item.data('user-email')) + ')</span>' +
177 '<button type="button" class="remove-user" aria-label="Remove">&times;</button>' +
178 '<input type="hidden" name="login_security[two_factor][excluded_users][]" value="' + uid + '">' +
179 '</div>'
180 );
181 $('#vigilante_2fa_user_search').val('');
182 $('.vigilante-2fa-search-results').removeClass('active').empty();
183 },
184
185 addResetUser: function($item) {
186 var uid = $item.data('user-id');
187 if ($('.vigilante-totp-reset-user[data-user-id="' + uid + '"]').length) return;
188
189 $('.vigilante-totp-reset-selected').append(
190 '<div class="vigilante-totp-reset-user vigilante-2fa-excluded-user" data-user-id="' + uid + '">' +
191 '<span class="user-display">' + this.esc($item.data('user-display')) + ' (' + this.esc($item.data('user-email')) + ')</span>' +
192 '<button type="button" class="remove-user" aria-label="Remove">&times;</button></div>'
193 );
194 $('#vigilante_totp_reset_search').val('');
195 $('.vigilante-totp-reset-results').removeClass('active').empty();
196 this.toggleResetButton();
197 },
198
199 getExcludedUserIds: function() {
200 var ids = [];
201 $('.vigilante-2fa-excluded-user').each(function() { ids.push($(this).data('user-id')); });
202 return ids;
203 },
204
205 toggleResetButton: function() {
206 $('#vigilante_totp_reset_btn').toggle($('.vigilante-totp-reset-user').length > 0);
207 },
208
209 resetTotpUsers: function($btn) {
210 var self = this;
211 var ids = [];
212 $('.vigilante-totp-reset-user').each(function() { ids.push($(this).data('user-id')); });
213 if (!ids.length) return;
214
215 $btn.prop('disabled', true).text('Resetting...');
216 var $status = $('.vigilante-totp-reset-status');
217
218 $.post(self.ajaxUrl(), {
219 action: 'vigilante_reset_totp_users',
220 nonce: self.nonce(),
221 user_ids: ids
222 }, function(response) {
223 $btn.prop('disabled', false).text('Reset selected');
224 if (response.success) {
225 $status.addClass('success').text(response.data.message);
226 $('.vigilante-totp-reset-selected').empty();
227 self.toggleResetButton();
228 } else {
229 $status.addClass('error').text(response.data || 'Error');
230 }
231 setTimeout(function() { $status.removeClass('success error').text(''); }, 4000);
232 });
233 },
234
235 sendNotification: function($btn) {
236 var self = this;
237 var $status = $btn.siblings('.vigilante-2fa-notification-status');
238 var mode = $('input[name="vigilante_2fa_notify_mode"]:checked').val() || 'all';
239
240 $btn.prop('disabled', true).text(self.str('sending'));
241 $status.removeClass('success error').text('');
242
243 $.post(self.ajaxUrl(), {
244 action: 'vigilante_send_2fa_notification',
245 nonce: self.nonce(),
246 mode: mode
247 }, function(response) {
248 $btn.prop('disabled', false).text(self.str('sendNotification'));
249 if (response.success) {
250 var msg = response.data.sent + ' ' + self.str('notificationsSent');
251 if (response.data.skipped > 0) msg += ', ' + response.data.skipped + ' ' + self.str('skipped');
252 if (response.data.failed > 0) msg += ', ' + response.data.failed + ' ' + self.str('failed');
253 $status.addClass('success').text(msg);
254 } else {
255 $status.addClass('error').text(response.data || 'Error');
256 }
257 });
258 },
259
260 // Helpers
261 ajaxUrl: function() { return typeof vigilanteAdmin !== 'undefined' ? vigilanteAdmin.ajaxUrl : ajaxurl; },
262 nonce: function() { return typeof vigilanteAdmin !== 'undefined' ? vigilanteAdmin.nonce : ''; },
263 str: function(key) {
264 if (typeof vigilanteAdmin !== 'undefined' && vigilanteAdmin.strings && vigilanteAdmin.strings[key]) return vigilanteAdmin.strings[key];
265 var fallback = { searching: 'Searching...', noUsersFound: 'No users found', sending: 'Sending...', sendNotification: 'Send notification now', notificationsSent: 'sent', skipped: 'skipped', failed: 'failed' };
266 return fallback[key] || key;
267 },
268 esc: function(text) {
269 var map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
270 return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
271 }
272 };
273
274 // =========================================================================
275 // TOTP profile setup module (user profile page)
276 // =========================================================================
277 var VigilanteTOTPProfile = {
278 init: function() {
279 if (!$('.vigilante-totp-profile').length) return;
280 this.bindEvents();
281 },
282
283 bindEvents: function() {
284 var self = this;
285 $(document).on('click', '.vigilante-totp-start-setup', function() { self.startSetup(); });
286 $(document).on('click', '.vigilante-totp-confirm-setup', function() { self.confirmSetup(); });
287 $(document).on('click', '.vigilante-totp-regenerate-backup', function() { self.regenerateBackup($(this)); });
288 $(document).on('click', '.vigilante-totp-reconfigure', function() { self.reconfigure($(this)); });
289 $(document).on('keypress', '#vigilante_totp_verify_code', function(e) {
290 if (e.which === 13) { e.preventDefault(); $('.vigilante-totp-confirm-setup').click(); }
291 });
292 },
293
294 cfg: function() {
295 return typeof vigilanteTOTP !== 'undefined' ? vigilanteTOTP : { ajaxUrl: ajaxurl, nonce: '', strings: {} };
296 },
297
298 getUserId: function() {
299 // Our own hidden field (most reliable)
300 var uid = $('.vigilante-totp-user-id').val();
301 if (uid && parseInt(uid, 10) > 0) return parseInt(uid, 10);
302 // WordPress profile hidden fields
303 uid = $('input[name="checkuser_id"]').val() || $('input[name="user_id"]').val();
304 if (uid && parseInt(uid, 10) > 0) return parseInt(uid, 10);
305 // URL param (edit other user)
306 var m = window.location.search.match(/user_id=(\d+)/);
307 if (m) return parseInt(m[1], 10);
308 // Fallback: server will use current user
309 return 0;
310 },
311
312 startSetup: function() {
313 var self = this;
314 var c = this.cfg();
315 var $btn = $('.vigilante-totp-start-setup');
316 $btn.prop('disabled', true).text(c.strings.generating || 'Generating...');
317
318 $.post(c.ajaxUrl, {
319 action: 'vigilante_totp_get_setup',
320 nonce: c.nonce,
321 user_id: self.getUserId()
322 }, function(response) {
323 if (response.success) {
324 var d = response.data;
325 $('.vigilante-totp-setup-loading').hide();
326
327 // Generate QR code client-side
328 var $qrContainer = $('.vigilante-totp-qr-container');
329 $qrContainer.empty();
330 var qrEl = document.createElement('div');
331 $qrContainer.append(qrEl);
332
333 if (typeof QRCode !== 'undefined') {
334 new QRCode(qrEl, {
335 text: d.uri,
336 width: 200,
337 height: 200,
338 colorDark: '#000000',
339 colorLight: '#ffffff',
340 correctLevel: QRCode.CorrectLevel.M
341 });
342 } else {
343 $qrContainer.html('<p style="color:#d63638;">QR library not loaded. Use the manual key below.</p>');
344 }
345
346 // Format secret in groups of 4 for readability
347 var formattedSecret = d.secret.replace(/(.{4})/g, '$1 ').trim();
348 $('.vigilante-totp-secret-display').text(formattedSecret);
349 $('.vigilante-totp-setup-qr').data('secret', d.secret).show();
350 $('#vigilante_totp_verify_code').focus();
351 } else {
352 $btn.prop('disabled', false).text('Start setup');
353 alert(response.data || 'Error');
354 }
355 });
356 },
357
358 confirmSetup: function() {
359 var self = this;
360 var c = this.cfg();
361 var code = $('#vigilante_totp_verify_code').val().trim();
362 var secret = $('.vigilante-totp-setup-qr').data('secret');
363 var $status = $('.vigilante-totp-setup-status');
364 var $btn = $('.vigilante-totp-confirm-setup');
365
366 if (!code || code.length !== 6) {
367 $status.text('Enter a valid 6-digit code.').addClass('error');
368 return;
369 }
370
371 $btn.prop('disabled', true);
372 $status.text(c.strings.verifying || 'Verifying...').removeClass('error success');
373
374 $.post(c.ajaxUrl, {
375 action: 'vigilante_totp_verify_setup',
376 nonce: c.nonce,
377 user_id: self.getUserId(),
378 code: code,
379 secret: secret
380 }, function(response) {
381 if (response.success) {
382 $status.text('').removeClass('error');
383 $('.vigilante-totp-setup-qr').hide();
384 var $success = $('.vigilante-totp-setup-success');
385 if (response.data.backup_codes) {
386 self.renderCodes(response.data.backup_codes, $success.find('.vigilante-totp-backup-codes-display'));
387 }
388 $success.show();
389 } else {
390 $btn.prop('disabled', false);
391 $status.text(response.data || 'Invalid code.').addClass('error');
392 }
393 });
394 },
395
396 regenerateBackup: function($btn) {
397 var self = this;
398 var c = this.cfg();
399 if (!confirm(c.strings.confirmRegen || 'This will invalidate all existing backup codes. Continue?')) return;
400
401 $btn.prop('disabled', true);
402 $.post(c.ajaxUrl, {
403 action: 'vigilante_totp_regenerate_backup',
404 nonce: c.nonce,
405 user_id: $btn.data('user')
406 }, function(response) {
407 $btn.prop('disabled', false);
408 if (response.success && response.data.backup_codes) {
409 var $container = $btn.closest('td').find('.vigilante-totp-backup-codes-display');
410 self.renderCodes(response.data.backup_codes, $container);
411 }
412 });
413 },
414
415 reconfigure: function($btn) {
416 var c = this.cfg();
417
418 if (!confirm(c.strings.confirmReconfig || 'This will reset your current TOTP setup. Continue?')) return;
419
420 $btn.prop('disabled', true).text('Resetting...');
421
422 $.post(c.ajaxUrl, {
423 action: 'vigilante_totp_reconfigure',
424 nonce: c.nonce,
425 user_id: $btn.data('user')
426 }, function(response) {
427 if (response.success) {
428 window.location.reload();
429 } else {
430 $btn.prop('disabled', false).text($btn.data('original-text') || 'Set up new authenticator');
431 alert(response.data || 'Error');
432 }
433 }).fail(function() {
434 $btn.prop('disabled', false);
435 });
436 },
437
438 renderCodes: function(codes, $container) {
439 var c = this.cfg();
440 var html = '<div class="vigilante-totp-backup-codes-box">';
441 html += '<p class="vigilante-totp-backup-warning"><span class="dashicons dashicons-warning"></span> ';
442 html += (c.strings.saveBackupCodes || 'Save these backup codes now. They will not be shown again.');
443 html += '</p><div class="vigilante-totp-codes-grid">';
444 $.each(codes, function(i, code) {
445 html += '<code>' + (i + 1) + '. ' + code + '</code>';
446 });
447 html += '</div></div>';
448 $container.html(html).show();
449 }
450 };
451
452 // =========================================================================
453 // Password expiration: per-user exclusion search
454 // Same UX as the 2FA exclusion picker, reusing the existing CSS classes.
455 // =========================================================================
456 var VigilantePwExpExclusion = {
457 searchTimeout: null,
458 minSearchLength: 2,
459
460 init: function() {
461 if (!$('#vigilante_pwexp_user_search').length) return;
462 this.bindEvents();
463 },
464
465 bindEvents: function() {
466 var self = this;
467
468 $(document).on('input', '#vigilante_pwexp_user_search', function() {
469 self.handleSearch($(this));
470 });
471 $(document).on('focus', '#vigilante_pwexp_user_search', function() {
472 var $r = $(this).siblings('.vigilante-pwexp-search-results');
473 if ($r.children().length > 0) $r.addClass('active');
474 });
475 $(document).on('click', '.vigilante-pwexp-search-results .search-result-item', function() {
476 self.addExcludedUser($(this));
477 });
478 $(document).on('click', '.vigilante-pwexp-excluded-user .remove-user', function() {
479 $(this).closest('.vigilante-pwexp-excluded-user').remove();
480 });
481 $(document).on('click', function(e) {
482 if (!$(e.target).closest('#vigilante_pwexp_user_search').length &&
483 !$(e.target).closest('.vigilante-pwexp-search-results').length) {
484 $('.vigilante-pwexp-search-results').removeClass('active');
485 }
486 });
487 },
488
489 handleSearch: function($input) {
490 var self = this;
491 var query = $input.val().trim();
492 var $results = $input.siblings('.vigilante-pwexp-search-results');
493
494 if (this.searchTimeout) clearTimeout(this.searchTimeout);
495 if (query.length < this.minSearchLength) {
496 $results.removeClass('active').empty();
497 return;
498 }
499
500 $results.addClass('active').html('<div class="searching">' + Vigilante2FA.str('searching') + '</div>');
501
502 this.searchTimeout = setTimeout(function() {
503 $.post(Vigilante2FA.ajaxUrl(), {
504 action: 'vigilante_search_users_password_reset',
505 nonce: Vigilante2FA.nonce(),
506 query: query
507 }, function(response) {
508 if (response.success && response.data.length > 0) {
509 Vigilante2FA.renderResults(response.data, $results);
510 } else {
511 $results.html('<div class="no-results">' + Vigilante2FA.str('noUsersFound') + '</div>');
512 }
513 });
514 }, 300);
515 },
516
517 addExcludedUser: function($item) {
518 var uid = $item.data('user-id');
519 if ($('.vigilante-pwexp-excluded-user[data-user-id="' + uid + '"]').length) return;
520
521 $('.vigilante-pwexp-excluded-users').append(
522 '<div class="vigilante-pwexp-excluded-user" data-user-id="' + uid + '">' +
523 '<span class="user-display">' + Vigilante2FA.esc($item.data('user-display')) + ' (' + Vigilante2FA.esc($item.data('user-email')) + ')</span>' +
524 '<button type="button" class="remove-user" aria-label="Remove">&times;</button>' +
525 '<input type="hidden" name="user_security[password_expiration][excluded_users][]" value="' + uid + '">' +
526 '</div>'
527 );
528 $('#vigilante_pwexp_user_search').val('');
529 $('.vigilante-pwexp-search-results').removeClass('active').empty();
530 }
531 };
532
533 $(document).ready(function() {
534 Vigilante2FA.init();
535 VigilanteTOTPProfile.init();
536 VigilantePwExpExclusion.init();
537 });
538
539 })(jQuery);