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 / Settings / templates / passkeys / passkeys-settings.php
wp-2fa / includes / classes / Admin / Settings / templates / passkeys Last commit date
index.php 5 days ago passkeys-settings.php 5 days ago
passkeys-settings.php
301 lines
1 <?php
2 /**
3 * Passkeys Settings Template
4 *
5 * @package wp-2fa
6 */
7
8 use WP2FA\WP2FA;
9 use WP2FA\Admin\Settings_Builder;
10 use WP2FA\Methods\Passkeys;
11 use WP2FA\Utils\Settings_Utils;
12 use WP2FA\Admin\Helpers\WP_Helper;
13 use WP2FA\Extensions\RoleSettings\Role_Settings_Controller;
14 use WP2FA\Licensing\Licensing_Factory;
15
16 $roles_controller = class_exists( Role_Settings_Controller::class, false ) && Licensing_Factory::has_active_valid_license();
17
18 // Determine current state.
19 $enabled = (bool) Settings_Utils::get_setting_role( null, Passkeys::POLICY_SETTINGS_NAME );
20 $certain_roles = (bool) Settings_Utils::get_setting_role( null, Passkeys::POLICY_SETTINGS_NAME . '_certain_roles' );
21
22 // If "certain roles" is set, the global enable is actually off, and vice versa.
23 if ( ! $enabled && $certain_roles ) {
24 $passkeys_on = true;
25 $all_roles_mode = false;
26 } elseif ( $enabled ) {
27 $passkeys_on = true;
28 $all_roles_mode = true;
29 } else {
30 $passkeys_on = false;
31 $all_roles_mode = true;
32 }
33
34 // Get roles for the role selector.
35 if ( $roles_controller ) {
36 $roles = array_flip( WP_Helper::get_roles() );
37 $checked_roles = array();
38 foreach ( $roles as $role_slug => $role_name ) {
39 if ( Passkeys::is_enabled( $role_slug ) ) {
40 $checked_roles[] = $role_slug;
41 }
42 }
43 }
44
45 $skip_2fa = Settings_Utils::string_to_bool( WP2FA::get_wp2fa_general_setting( 'skip_2fa_for_passkeys' ) );
46 $is_free = ! Licensing_Factory::has_active_valid_license();
47 ?>
48
49 <div class="passkeys-settings-main-wrapper">
50 <?php
51 Settings_Builder::build_option(
52 array(
53 'title' => \esc_html__( 'Passkeys', 'wp-2fa' ),
54 'id' => 'passkeys-page-title',
55 'type' => 'tab-title',
56 )
57 );
58 ?>
59
60 <div class="settings-card">
61 <?php
62 Settings_Builder::build_option(
63 array(
64 'text' => \esc_html__( 'Passkeys are not a two-factor authentication (2FA) method. They are a passwordless multi-factor authentication (MFA) solution that allows users to securely log in without using a password.', 'wp-2fa' ),
65 'class' => 'description-settings-card',
66 'id' => 'passkeys-description',
67 'type' => 'description',
68 )
69 );
70 ?>
71
72 <div class="form-group settings-row">
73 <?php
74 Settings_Builder::build_option(
75 array(
76 'id' => 'passkeys-enabled',
77 'type' => 'toggle-checkbox',
78 'option_name' => 'wp_2fa_passkeys[' . Passkeys::POLICY_SETTINGS_NAME . ']',
79 'default' => $passkeys_on,
80 'text' => \esc_html__( 'Allow users to register Passkeys', 'wp-2fa' ),
81 )
82 );
83 ?>
84 </div>
85 </div>
86
87 <?php if ( $roles_controller ) { ?>
88 <div class="settings-card passkeys-enforcement-card" id="passkeys-enforcement-card" style="<?php echo ! $passkeys_on ? 'display:none;' : ''; ?>">
89 <?php
90 Settings_Builder::build_option(
91 array(
92 'title' => \esc_html__( 'Enforce Passkeys on:', 'wp-2fa' ),
93 'id' => 'passkeys-enforce-title',
94 'type' => 'section-title',
95 )
96 );
97 ?>
98
99 <div class="form-group settings-row">
100 <?php
101 Settings_Builder::build_option(
102 array(
103 'id' => 'passkeys-enforcement-policy',
104 'type' => 'radio',
105 'option_name' => 'wp_2fa_passkeys[enforcement-policy]',
106 'default' => $all_roles_mode ? 'all-roles' : 'certain-roles-only',
107 'options' => array(
108 'all-roles' => \esc_html__( 'Allow all users with any roles to setup and use Passkeys', 'wp-2fa' ),
109 'certain-roles-only' => \esc_html__( 'Only for specific roles', 'wp-2fa' ),
110 ),
111 )
112 );
113 ?>
114 </div>
115
116 <div class="passkeys-roles-selector" id="passkeys-roles-selector" style="<?php echo $all_roles_mode ? 'display:none;' : ''; ?>">
117 <?php
118 Settings_Builder::build_option(
119 array(
120 'id' => 'passkeys-roles',
121 'type' => 'user-roles',
122 'roles' => $roles,
123 'checked' => $checked_roles,
124 'name_prefix' => 'wp_2fa_passkeys[enabled_roles]',
125 )
126 );
127 ?>
128 </div>
129 </div>
130 <?php } else { ?>
131 <div class="settings-card passkeys-enforcement-card" id="passkeys-enforcement-card" style="<?php echo $is_free ? '' : ( ! $passkeys_on ? 'display:none;' : '' ); ?>">
132 <?php
133 Settings_Builder::build_option(
134 array(
135 'title' => \esc_html__( 'Enforce Passkeys on:', 'wp-2fa' ),
136 'id' => 'passkeys-enforce-title',
137 'type' => 'section-title',
138 )
139 );
140 Settings_Builder::premium_gate_open( false );
141 echo Settings_Builder::get_premium_badge_html( 'wp-2fa-passkeys:enforce-roles' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
142 ?>
143
144 <div class="form-group settings-row">
145 <?php
146 Settings_Builder::build_option(
147 array(
148 'id' => 'passkeys-enforcement-policy-locked',
149 'type' => 'radio',
150 'option_name' => 'wp_2fa_passkeys_locked[enforcement-policy]',
151 'default' => 'all-roles',
152 'options' => array(
153 'all-roles' => \esc_html__( 'Allow all users with any roles to setup and use Passkeys', 'wp-2fa' ),
154 'certain-roles-only' => \esc_html__( 'Only for specific roles', 'wp-2fa' ),
155 ),
156 )
157 );
158 ?>
159 </div>
160 <?php Settings_Builder::premium_gate_close(); ?>
161 </div>
162 <?php } ?>
163
164 <?php
165 // @free:start
166 ?>
167 <?php if ( ! Licensing_Factory::has_active_valid_license() ) : ?>
168 <input type="hidden" name="wp_2fa_settingspass[skip_2fa_for_passkeys]" value="1">
169 <?php endif; ?>
170 <?php
171 // @free:end
172 ?>
173
174 <?php
175 ?>
176
177 <?php if ( ! Licensing_Factory::has_active_valid_license() ) : ?>
178 <div class="settings-card" id="passkeys-bypass-card" style="<?php echo $is_free ? '' : ( ! $passkeys_on ? 'display:none;' : '' ); ?>">
179 <?php
180 Settings_Builder::premium_gate_open( false );
181
182 ?>
183 <div style="margin-left:18px">
184 <?php echo Settings_Builder::get_premium_badge_html( 'wp-2fa-passkeys:bypass-2fa' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
185 </div>
186 <div class="form-group settings-row">
187 <?php
188 Settings_Builder::build_option(
189 array(
190 'id' => 'skip_2fa_for_passkeys_locked',
191 'type' => 'toggle-checkbox',
192 'option_name' => 'wp_2fa_settingspass_locked[skip_2fa_for_passkeys]',
193 'default' => false,
194 'text' => \esc_html__( 'Bypass 2FA after login with Passkey', 'wp-2fa' ),
195 )
196 );
197 ?>
198 </div>
199 <?php
200 Settings_Builder::build_option(
201 array(
202 'text' => \esc_html__( 'When enabled, users who have both Passkeys and 2FA configured won\'t be asked to complete an additional 2FA verification step after signing in with a Passkey. Disable this option to require both Passkey authentication and 2FA for enhanced account security.', 'wp-2fa' ),
203 'class' => 'description-settings-card',
204 'id' => 'bypass-2fa-description',
205 'type' => 'description',
206 )
207 );
208 Settings_Builder::premium_gate_close();
209 ?>
210 </div>
211 <?php endif; ?>
212 <?php
213 // @premium:end
214 ?>
215
216 <script>
217 document.addEventListener('DOMContentLoaded', function() {
218 var isFree = <?php echo $is_free ? 'true' : 'false'; ?>;
219 var passkeysToggle = document.getElementById('passkeys-enabled');
220 var enforcementCard = document.getElementById('passkeys-enforcement-card');
221 var bypassCard = document.getElementById('passkeys-bypass-card');
222 var rolesSelector = document.getElementById('passkeys-roles-selector');
223 var form = document.getElementById('wp-2fa-admin-settings');
224 var submitBtn = form ? form.querySelector('#submit, [type="submit"]') : null;
225 var noteText = form ? (form.dataset.disabledNote || '<?php echo \esc_js( \__( 'Please select at least one role to save.', 'wp-2fa' ) ); ?>') : '';
226 var submitNoteEl = null;
227
228 function ensureSubmitNote() {
229 if (!submitBtn) return;
230 if (!submitNoteEl) {
231 submitNoteEl = document.createElement('span');
232 submitNoteEl.className = 'wp2fa-submit-disabled-note description';
233 submitNoteEl.style.marginLeft = '8px';
234 submitNoteEl.style.color = '#646970';
235 submitNoteEl.style.fontSize = '12px';
236 submitNoteEl.style.fontStyle = 'italic';
237 submitBtn.insertAdjacentElement('afterend', submitNoteEl);
238 }
239 }
240
241 function updateSubmitState() {
242 if (!submitBtn) return;
243 var certainRadio = document.querySelector('input[name="wp_2fa_passkeys[enforcement-policy]"][value="certain-roles-only"]');
244 var requireSelection = !!(passkeysToggle && passkeysToggle.checked && certainRadio && certainRadio.checked && rolesSelector && rolesSelector.style.display !== 'none');
245 var checkedCount = 0;
246 if (requireSelection) {
247 checkedCount = rolesSelector.querySelectorAll('input[type="checkbox"]:checked').length;
248 }
249 var shouldDisable = requireSelection && checkedCount === 0;
250 submitBtn.disabled = shouldDisable;
251 ensureSubmitNote();
252 if (submitNoteEl) {
253 submitNoteEl.textContent = shouldDisable ? noteText : '';
254 submitNoteEl.style.display = shouldDisable ? 'inline' : 'none';
255 }
256 }
257
258 function toggleVisibility() {
259 if (!passkeysToggle) return;
260 var isOn = passkeysToggle.checked;
261
262 if (!isFree) {
263 if (enforcementCard) enforcementCard.style.display = isOn ? '' : 'none';
264 if (bypassCard) bypassCard.style.display = isOn ? '' : 'none';
265 }
266
267 if (isOn && rolesSelector) {
268 var certainRadio = document.querySelector('input[name="wp_2fa_passkeys[enforcement-policy]"][value="certain-roles-only"]');
269 rolesSelector.style.display = (certainRadio && certainRadio.checked) ? '' : 'none';
270 }
271
272 updateSubmitState();
273 }
274
275 if (passkeysToggle) {
276 passkeysToggle.addEventListener('change', toggleVisibility);
277 }
278
279 // Radio change toggles roles selector
280 document.querySelectorAll('input[name="wp_2fa_passkeys[enforcement-policy]"]').forEach(function(radio) {
281 radio.addEventListener('change', function() {
282 if (rolesSelector) {
283 rolesSelector.style.display = (this.value === 'certain-roles-only') ? '' : 'none';
284 }
285 updateSubmitState();
286 });
287 });
288
289 // Role checkbox listeners
290 if (rolesSelector) {
291 rolesSelector.querySelectorAll('input[type="checkbox"]').forEach(function(cb) {
292 cb.addEventListener('change', updateSubmitState);
293 });
294 }
295
296 // Initial state
297 toggleVisibility();
298 });
299 </script>
300 </div>
301