1import { browserSupportsWebAuthn, browserSupportsWebAuthnAutofill, startAuthentication } from "./index.js"; 2 3/** 4 * Authenticate Passkey. 5 */ 6async function authenticate( username, redirectTo ) { 7 let asseResp; 8 let requestId; 9 try { 10 const response = await wp.apiFetch({ 11 path: '/wp-2fa-passkeys/v1/singin/request', 12 method: 'POST', 13 data: { 'user': username }, 14 }); 15 16 const { options, request_id } = response; 17 18 requestId = request_id; 19 asseResp = await startAuthentication(options); 20 } catch (error) { 21 throw error; 22 } 23 24 // POST the response to the endpoint that calls. 25 try { 26 const response = await wp.apiFetch({ 27 path: '/wp-2fa-passkeys/v1/singin/response', 28 method: 'POST', 29 data: { 30 request_id: requestId, 31 asseResp, 32 'user': username, 33 'redirect_to': redirectTo, 34 }, 35 }); 36 37 if (response.status !== 'verified') { 38 throw new Error('Passkey authentication failed. Method is not set?'); 39 } 40 41 let iframe = !(window === window.parent); // interim login ? 42 43 if (iframe) { 44 var someIframe = window.parent.document.getElementById('wp-auth-check-wrap'); 45 someIframe.parentNode.removeChild(someIframe); 46 } else { 47 48 let redirect_to = ''; 49 50 if (response.redirect_to && '' !== response.redirect_to) { 51 redirect_to = response.redirect_to; 52 } else { 53 // Get redirect_to from query string. 54 const urlParams = new URLSearchParams(window.location.search); 55 redirect_to = urlParams.get('redirect_to') || '/wp-admin'; 56 } 57 58 // Redirect to redirect url or wp-admin as default. 59 window.location.href = redirect_to; 60 } 61 } catch (error) { 62 throw error; 63 } 64} 65 66/** 67 * Show error message. 68 * 69 * @param {string} message Error message. 70 */ 71function showError(message) { 72 let loginForm = document.getElementById('loginform'); 73 74 if ( !loginForm ) { 75 loginForm = document.getElementsByClassName('woocommerce-form woocommerce-form-login login')[0]; 76 } 77 78 if ( !loginForm ) { 79 return; 80 } 81 82 // Create Error element if not exists. 83 const errorElement = document.createElement('div'); 84 errorElement.id = 'login_error'; 85 errorElement.className = 'notice notice-error'; 86 errorElement.innerHTML = message; 87 88 // Add error element before login form. 89 loginForm.parentNode.insertBefore(errorElement, loginForm); 90 91 loginForm.classList.add('shake'); 92} 93 94async function delay(time) { 95 return new Promise(resolve => setTimeout(resolve, time)); 96} 97 98function onClick() { 99 100 // create invisible dummy input to receive the focus first 101 const fakeInput = document.createElement('input') 102 fakeInput.setAttribute('type', 'text') 103 fakeInput.style.position = 'absolute' 104 fakeInput.style.opacity = 0 105 fakeInput.style.height = 0 106 fakeInput.style.fontSize = '16px' // disable auto zoom 107
108 // you may need to append to another element depending on the browser's auto 109 // zoom/scroll behavior 110 document.body.prepend(fakeInput) 111 112 // focus so that subsequent async focus will work 113 fakeInput.focus() 114 115 setTimeout(() => { 116 117 // now we can focus on the target input 118 document.getElementById('user_login').focus() 119 120 // cleanup 121 fakeInput.remove() 122 123 }, 1000) 124 125} 126 127wp.domReady(async () => { 128 // If the browser doesn't support WebAuthn, don't do anything. 129 if (!browserSupportsWebAuthn()) { 130 return; 131 } 132 133 // var $user_password = jQuery( '.user-pass-wrap' ); 134 // $user_password.hide(); 135 136 let usernameField = document.getElementById('user_login'); 137 138 if ( ! usernameField ) { 139 usernameField = document.getElementById('username'); 140 } 141 142 if ( !usernameField ) { 143 return; 144 } 145 146 // add autocomplete="webauthn" to the username field. 147 if (usernameField) { 148 usernameField.setAttribute('autocomplete', 'username webauthn'); 149 } 150 151 if (browserSupportsWebAuthnAutofill()) { 152 153 const usePasskeysButton = document.querySelector('.wp-2fa-login-via-passkey'); 154 const useStandardButton = document.querySelector('.wp-2fa-login-standard'); 155 156 // Helper to detect if the password field is currently visible 157 const isPasswordVisible = () => { 158 let $user_password = jQuery('.user-pass-wrap'); 159 if (!$user_password.length) { 160 $user_password = jQuery(jQuery('.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 161 } 162 return $user_password.length ? $user_password.is(':visible') : false; 163 }; 164 165 if ( usePasskeysButton ) { 166 usePasskeysButton.addEventListener('click', async () => { 167 168 if ( useStandardButton ) { 169 const standardLoginWrap = jQuery( '#wp-2fa-standard-login-wrapper' ); 170 standardLoginWrap.show(); 171 } 172 173 let $user_password = jQuery( '.user-pass-wrap' ); 174 175 if ( ! $user_password.length ) { 176 $user_password = jQuery(jQuery( '.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 177 } 178 if ($user_password.is(":visible")) { 179 $user_password.hide(); 180 181 jQuery( 'p.forgetmenot' ).hide(); 182 jQuery( 'p.submit' ).hide(); 183 184 jQuery( 'button[name="login"]' ).parent().hide(); 185 186 return; 187 } 188 189 jQuery( '#user_login' ).prop( 'required', false ); 190 jQuery( '#user_pass' ).prop( 'required', false ); 191 192 if ('' === usernameField.value) { 193 showError('Please enter your username or email address to use Passkey login.'); 194 195 return; 196 } 197 198 // Collect redirect input value with fallbacks: redirect_to -> redirect -> 'wp-admin/' 199 let redirectTo = ''; 200 const redirectInput = document.querySelector('input[name="redirect_to"]') || document.querySelector('input[name="redirect"]'); 201 if (redirectInput && redirectInput.value && redirectInput.value.trim() !== '') { 202 redirectTo = redirectInput.value; 203 } else { 204 redirectTo = ''; 205 } 206 207 try { 208 await authenticate( usernameField.value, redirectTo ); 209 } catch (error) { 210 showError(error.message); 211 } 212 }); 213 } 214 if ( useStandardButton ) { 215 useStandardButton.addEventListener('click', async () => { 216 217 var $user_password = jQuery( '.user-pass-wrap' ); 218 219 if ( ! $user_password.length ) { 220 $user_password = jQuery(jQuery( '.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 221 } 222 223 $user_password.show(); 224 225 jQuery( '#user_login' ).prop( 'required', true ); 226 jQuery( '#user_pass' ).prop( 'required', true ); 227 228 jQuery( 'p.forgetmenot' ).show(); 229 jQuery( 'p.submit' ).show(); 230 231 jQuery( 'button[name="login"]' ).parent().show(); 232 233 const standardLoginWrap = jQuery( '#wp-2fa-standard-login-wrapper' ); 234 standardLoginWrap.hide(); 235 }); 236 } 237 238 // Trigger Passkey authentication when pressing Enter on the username field 239 // if the password field is hidden (i.e., passkey flow is active). 240 if ( usePasskeysButton && usernameField ) { 241 usernameField.addEventListener('keydown', (e) => { 242 const isEnter = (e.key && e.key.toLowerCase() === 'enter') || e.keyCode === 13; 243 if (!isEnter) return; 244 245 if (!isPasswordVisible()) { 246 e.preventDefault(); 247 usePasskeysButton.click(); 248 } 249 }); 250 } 251 252 } else { 253 const passkeyUseWrap = document.getElementById('wp-2fa-login-wrapper'); 254 passkeyUseWrap.style.display = 'none'; 255 } 256});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.