1const publicKeyCredentialAllowed = window.PublicKeyCredential ? true : false; 2 3document.addEventListener("DOMContentLoaded", function () { 4 const box = document.getElementById("secure-passkey-login-wrapper"); 5 const submit = document.querySelector(".submit"); 6 if (box && submit) { 7 submit.insertAdjacentElement("afterend", box); 8 } 9 if (box) { 10 box.style.display = "block"; 11 } 12}); 13 14document.addEventListener("DOMContentLoaded", function () { 15 document 16 .querySelectorAll( 17 'input[name="log"], input[name="username"], input[name="user_login"], #username, #user_login', 18 ) 19 .forEach((field) => { 20 if (!field.getAttribute("autocomplete")?.includes("webauthn")) { 21 field.setAttribute("autocomplete", "username webauthn"); 22 } 23 }); 24 25 let conditionalAbortController = null; 26 let conditionalRequest = null; 27 let isAuthenticating = false; 28 29 document 30 .getElementById("login-via-passkey") 31 .addEventListener("click", async (event) => { 32 event.preventDefault(); 33 await loginWithPasskey(); 34 }); 35 36 async function loginWithPasskey() { 37 if (isAuthenticating) { 38 return; 39 } 40 isAuthenticating = true; 41 42 if (conditionalAbortController) { 43 conditionalAbortController.abort(); 44 conditionalAbortController = null; 45 } 46 47 if (conditionalRequest) { 48 try { 49 await conditionalRequest; 50 } catch (e) {} 51 conditionalRequest = null; 52 } 53 54 const errorMessage = document.getElementById("errorMessage"); 55 const successMessage = document.getElementById("successMessage"); 56 const passkeySigninButton = document.getElementById("login-via-passkey"); 57 const spinnerText = document.getElementById("spinnerText"); 58 const buttonText = document.getElementById("buttonText"); 59 60 errorMessage.style.display = "none"; 61 successMessage.style.display = "none"; 62 spinnerText.style.display = "inline-block"; 63 buttonText.style.display = "none"; 64 passkeySigninButton.disabled = true; 65 66 if (!publicKeyCredentialAllowed) { 67 errorMessage.textContent = 68 secure_passkeys_object.i18n.passkeys_not_supported_in_browser; 69 errorMessage.style.display = "block"; 70 spinnerText.style.display = "none"; 71 buttonText.style.display = "inline"; 72 passkeySigninButton.disabled = false; 73 isAuthenticating = false; 74 return; 75 } 76 77 try { 78 const options = await getLoginOptions(); 79 let challenge = options.challenge; 80 81 options.challenge = base64ToUint8Array(options.challenge); 82 if (options.allowCredentials && Array.isArray(options.allowCredentials)) { 83 options.allowCredentials = options.allowCredentials.map((cred) => ({ 84 ...cred, 85 id: base64ToUint8Array(cred.id), 86 })); 87 } 88 89 const credential = await navigator.credentials.get({ 90 publicKey: options, 91 }); 92 const data = prepareLoginData(credential); 93 data.challenge = challenge; 94 await postLoginData(data); 95 } catch (error) { 96 if (error instanceof DOMException && error.name === "NotAllowedError") { 97 errorMessage.textContent = 98 secure_passkeys_object.i18n.cancelled_or_no_passkey; 99 } else if (error instanceof DOMException && error.name === "AbortError") { 100 errorMessage.textContent = secure_passkeys_object.i18n.cancelled_login; 101 } else if (error instanceof Error && error.message) { 102 errorMessage.textContent = error.message; 103 } else { 104 errorMessage.textContent = secure_passkeys_object.i18n.failed_login; 105 } 106 errorMessage.style.display = "block"; 107 spinnerText.style.display = "none"; 108 buttonText.style.display = "inline"; 109 passkeySigninButton.disabled = false; 110 isAuthenticating = false; 111 } 112 } 113 114 async function getLoginOptions() { 115 let params = { 116 nonce: secure_passkeys_object.nonce, 117 action: "secure_passkeys_frontend_get_login_options", 118 }; 119 120 try { 121 const response = await fetch(secure_passkeys_object.url, { 122 method: "POST", 123 body: new URLSearchParams(params), 124 }); 125 const data = await response.json(); 126 127 if (data.success) { 128 return data.data; 129 } else if (!data.success && data?.data) { 130 throw new Error(data.data); 131 } else { 132 throw new Error(secure_passkeys_object.i18n.failed_load_options); 133 } 134 } catch (error) { 135 if (error instanceof Error && error.message) { 136 throw error; 137 } 138 throw new Error(secure_passkeys_object.i18n.failed_load_options); 139 } 140 } 141 142 async function postLoginData(params) { 143 params.nonce = secure_passkeys_object.nonce; 144 params.action = "secure_passkeys_frontend_login"; 145 146 if (secure_passkeys_object.redirect_to) { 147 params.redirect_to = secure_passkeys_object.redirect_to; 148 } 149 150 const formData = appendParamsToFormData(params); 151 152 try { 153 const response = await fetch(secure_passkeys_object.url, { 154 method: "POST", 155 body: formData, 156 }); 157 const data = await response.json(); 158 159 if (data.success) { 160 document.getElementById("successMessage").textContent = 161 secure_passkeys_object.i18n.success_login; 162 document.getElementById("successMessage").style.display = "block"; 163 164 if (data.data.redirect_url) { 165 window.location.href = data.data.redirect_url; 166 } else { 167 window.location.reload(); 168 } 169 } else { 170 throw new Error(data.data); 171 } 172 } catch (error) { 173 if (error instanceof Error && error.message) { 174 throw error; 175 } 176 throw new Error(secure_passkeys_object.i18n.failed_load_options); 177 } 178 } 179 180 function prepareLoginData(credential) { 181 return { 182 id: credential.id, 183 rawId: decodeBase64EncodedASCII(credential.rawId), 184 type: credential.type, 185 response: { 186 clientDataJSON: decodeBase64EncodedASCII( 187 credential.response.clientDataJSON, 188 ), 189 authenticatorData: decodeBase64EncodedASCII( 190 credential.response.authenticatorData, 191 ), 192 signature: decodeBase64EncodedASCII(credential.response.signature), 193 userHandle: credential.response.userHandle 194 ? decodeBase64EncodedASCII(credential.response.userHandle) 195 : null, 196 }, 197 }; 198 } 199 200 function base64ToUint8Array(value) { 201 return Uint8Array.from(atob(value), (c) =>
201 c.charCodeAt(0)); 202 } 203 204 function decodeBase64EncodedASCII(data) { 205 return btoa(String.fromCharCode(...new Uint8Array(data))); 206 } 207 208 function appendParamsToFormData(params) { 209 const formData = new FormData(); 210 211 for (let key in params) { 212 if (params.hasOwnProperty(key)) { 213 let paramValue = params[key]; 214 215 if (typeof paramValue === "object" && paramValue !== null) { 216 if (Array.isArray(paramValue)) { 217 paramValue.forEach((item, index) => { 218 formData.append(`${key}[${index}]`, item); 219 }); 220 } else { 221 for (let subKey in paramValue) { 222 if (paramValue.hasOwnProperty(subKey)) { 223 const subValue = paramValue[subKey]; 224 formData.append(`${key}[${subKey}]`, subValue); 225 } 226 } 227 } 228 } else { 229 formData.append(key, paramValue); 230 } 231 } 232 } 233 234 return formData; 235 } 236 237 async function initConditionalUI() { 238 if ( 239 !publicKeyCredentialAllowed || 240 !window.PublicKeyCredential.isConditionalMediationAvailable 241 ) { 242 return; 243 } 244 245 const isCMA = 246 await window.PublicKeyCredential.isConditionalMediationAvailable(); 247 if (!isCMA) { 248 return; 249 } 250 251 try { 252 const options = await getLoginOptions(); 253 let challenge = options.challenge; 254 255 options.challenge = base64ToUint8Array(options.challenge); 256 if (options.allowCredentials && Array.isArray(options.allowCredentials)) { 257 options.allowCredentials = options.allowCredentials.map((cred) => ({ 258 ...cred, 259 id: base64ToUint8Array(cred.id), 260 })); 261 } 262 263 conditionalAbortController = new AbortController(); 264 265 conditionalRequest = navigator.credentials.get({ 266 publicKey: options, 267 mediation: "conditional", 268 signal: conditionalAbortController.signal, 269 }); 270 271 const credential = await conditionalRequest; 272 273 if (credential) { 274 const data = prepareLoginData(credential); 275 data.challenge = challenge; 276 await postLoginData(data); 277 } 278 } catch (error) { 279 // Ignore errors for conditional UI as they are expected when user logs in manually or aborts 280 } 281 } 282 283 initConditionalUI(); 284});
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.