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 // Validate redirect is same-origin to prevent open redirect attacks. 59 try { 60 const parsed = new URL(redirect_to, window.location.origin); 61 if (parsed.origin !== window.location.origin) { 62 redirect_to = '/wp-admin'; 63 } 64 } catch (e) { 65 if (!redirect_to.startsWith('/')) { 66 redirect_to = '/wp-admin'; 67 } 68 } 69 70 // Redirect to redirect url or wp-admin as default. 71 window.location.href = redirect_to; 72 } 73 } catch (error) { 74 throw error; 75 } 76} 77 78/** 79 * Show error message. 80 * 81 * @param {string} message Error message. 82 */ 83function showError(message) { 84 let loginForm = document.getElementById('loginform'); 85 86 if ( !loginForm ) { 87 loginForm = document.getElementsByClassName('woocommerce-form woocommerce-form-login login')[0]; 88 } 89 90 if ( !loginForm ) { 91 return; 92 } 93 94 // Create Error element if not exists. 95 const errorElement = document.createElement('div'); 96 errorElement.id = 'login_error'; 97 errorElement.className = 'notice notice-error'; 98 errorElement.textContent = message; 99 100 // Add error element before login form. 101 loginForm.parentNode.insertBefore(errorElement, loginForm); 102 103 loginForm.classList.add('shake'); 104} 105 106async function delay(time) { 107 return new Promise(resolve => setTimeout(resolve, time)); 108} 109 110function onClick() { 111 112 // create invisible dummy input to receive the focus first 113 const fakeInput = document.createElement('input') 114 fakeInput.setAttribute('type', 'text') 115 fakeInput.style.position = 'absolute' 116 fakeInput.style.opacity = 0 117 fakeInput.style.height = 0 118 fakeInput.style.fontSize = '16px' // disable auto zoom 119
120 // you may need to append to another element depending on the browser's auto 121 // zoom/scroll behavior 122 document.body.prepend(fakeInput) 123 124 // focus so that subsequent async focus will work 125 fakeInput.focus() 126 127 setTimeout(() => { 128 129 // now we can focus on the target input 130 document.getElementById('user_login').focus() 131 132 // cleanup 133 fakeInput.remove() 134 135 }, 1000) 136 137} 138 139wp.domReady(async () => { 140 // If the browser doesn't support WebAuthn, don't do anything. 141 if (!browserSupportsWebAuthn()) { 142 return; 143 } 144 145 // var $user_password = jQuery( '.user-pass-wrap' ); 146 // $user_password.hide(); 147 148 let usernameField = document.getElementById('user_login'); 149 150 if ( ! usernameField ) { 151 usernameField = document.getElementById('username'); 152 } 153 154 if ( !usernameField ) { 155 return; 156 } 157 158 // add autocomplete="webauthn" to the username field. 159 if (usernameField) { 160 usernameField.setAttribute('autocomplete', 'username webauthn'); 161 } 162 163 if (browserSupportsWebAuthnAutofill()) { 164 165 const usePasskeysButton = document.querySelector('.wp-2fa-login-via-passkey'); 166 const useStandardButton = document.querySelector('.wp-2fa-login-standard'); 167 168 // Helper to detect if the password field is currently visible 169 const isPasswordVisible = () => { 170 let $user_password = jQuery('.user-pass-wrap'); 171 if (!$user_password.length) { 172 $user_password = jQuery(jQuery('.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 173 } 174 return $user_password.length ? $user_password.is(':visible') : false; 175 }; 176 177 if ( usePasskeysButton ) { 178 usePasskeysButton.addEventListener('click', async () => { 179 180 if ( useStandardButton ) { 181 const standardLoginWrap = jQuery( '#wp-2fa-standard-login-wrapper' ); 182 standardLoginWrap.show(); 183 } 184 185 let $user_password = jQuery( '.user-pass-wrap' ); 186 187 if ( ! $user_password.length ) { 188 $user_password = jQuery(jQuery( '.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 189 } 190 if ($user_password.is(":visible")) { 191 $user_password.hide(); 192 193 jQuery( 'p.forgetmenot' ).hide(); 194 jQuery( 'p.submit' ).hide(); 195 196 jQuery( 'button[name="login"]' ).parent().hide(); 197 198 return; 199 } 200 201 jQuery( '#user_login' ).prop( 'required', false ); 202 jQuery( '#user_pass' ).prop( 'required', false ); 203 204 if ('' === usernameField.value) { 205 showError('Please enter your username or email address to use Passkey login.'); 206 207 return; 208 } 209 210 // Collect redirect input value with fallbacks: redirect_to -> redirect -> 'wp-admin/' 211 let redirectTo = ''; 212 const redirectInput = document.querySelector('input[name="redirect_to"]') || document.querySelector('input[name="redirect"]'); 213 if (redirectInput && redirectInput.value && redirectInput.value.trim() !== '') { 214 redirectTo = redirectInput.value; 215 } else { 216 redirectTo = ''; 217 } 218 219 try { 220 await authenticate( usernameField.value, redirectTo ); 221 } catch (error) { 222 showError(error.message); 223 } 224 }); 225 } 226 if ( useStandardButton ) { 227 useStandardButton.addEventListener('click', async () => { 228 229 var $user_password = jQuery( '.user-pass-wrap' ); 230 231 if ( ! $user_password.length ) { 232 $user_password = jQuery(jQuery( '.woocommerce-form-row.woocommerce-form-row--wide.form-row.form-row-wide')[1]); 233 } 234 235 $user_password.show(); 236 237 jQuery( '#user_login' ).prop( 'required', true ); 238 jQuery( '#user_pass' ).prop( 'required', true ); 239 240 jQuery( 'p.forgetmenot' ).show(); 241 jQuery( 'p.submit' ).show(); 242 243 jQuery( 'button[name="login"]' ).parent().show(); 244 245 const standardLoginWrap = jQuery( '#wp-2fa-standard-login-wrapper' ); 246 standardLoginWrap.hide(); 247 }); 248 } 249 250 // Trigger Passkey authentication when pressing Enter on the username field 251 // if the password field is hidden (i.e., passkey flow is active). 252 if ( usePasskeysButton && usernameField ) { 253 usernameField.addEventListener('keydown', (e) => { 254 const isEnter = (e.key && e.key.toLowerCase() === 'enter') || e.keyCode === 13; 255 if (!isEnter) return; 256 257 if (!isPasswordVisible()) { 258 e.preventDefault(); 259 usePasskeysButton.click(); 260 } 261 }); 262 } 263 264 } else { 265 const passkeyUseWrap = document.getElementById('wp-2fa-login-wrapper'); 266 passkeyUseWrap.style.display = 'none'; 267 } 268});
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.