[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 /** 2 * @package Joomla.Plugin 3 * @subpackage System.webauthn 4 * 5 * @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org> 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 */ 8 window.Joomla = window.Joomla || {}; 9 10 ((Joomla, document) => { 11 /** 12 * Converts a simple object containing query string parameters to a single, escaped query string. 13 * This method is a necessary evil since Joomla.request can only accept data as a string. 14 * 15 * @param object {object} A plain object containing the query parameters to pass 16 * @param prefix {string} Prefix for array-type parameters 17 * 18 * @returns {string} 19 */ 20 21 const interpolateParameters = (object, prefix = '') => { 22 let encodedString = ''; 23 Object.keys(object).forEach(prop => { 24 if (typeof object[prop] !== 'object') { 25 if (encodedString.length > 0) { 26 encodedString += '&'; 27 } 28 29 if (prefix === '') { 30 encodedString += `$encodeURIComponent(prop)}=$encodeURIComponent(object[prop])}`; 31 } else { 32 encodedString += `$encodeURIComponent(prefix)}[$encodeURIComponent(prop)}]=$encodeURIComponent(object[prop])}`; 33 } 34 35 return; 36 } // Objects need special handling 37 38 39 encodedString += `$interpolateParameters(object[prop], prop)}`; 40 }); 41 return encodedString; 42 }; 43 /** 44 * Finds the first field matching a selector inside a form 45 * 46 * @param {HTMLFormElement} form The FORM element 47 * @param {String} fieldSelector The CSS selector to locate the field 48 * 49 * @returns {Element|null} NULL when no element is found 50 */ 51 52 53 const findField = (form, fieldSelector) => { 54 const elInputs = form.querySelectorAll(fieldSelector); 55 56 if (!elInputs.length) { 57 return null; 58 } 59 60 return elInputs[0]; 61 }; 62 /** 63 * Find a form field described by the CSS selector fieldSelector. 64 * The field must be inside a <form> element which is either the 65 * outerElement itself or enclosed by outerElement. 66 * 67 * @param {Element} outerElement The element which is either our form or contains our form. 68 * @param {String} fieldSelector The CSS selector to locate the field 69 * 70 * @returns {null|Element} NULL when no element is found 71 */ 72 73 74 const lookForField = (outerElement, fieldSelector) => { 75 let elInput = null; 76 77 if (!outerElement) { 78 return elInput; 79 } 80 81 const elElement = outerElement.parentElement; 82 83 if (elElement.nodeName === 'FORM') { 84 elInput = findField(elElement, fieldSelector); 85 return elInput; 86 } 87 88 const elForms = elElement.querySelectorAll('form'); 89 90 if (elForms.length) { 91 for (let i = 0; i < elForms.length; i += 1) { 92 elInput = findField(elForms[i], fieldSelector); 93 94 if (elInput !== null) { 95 return elInput; 96 } 97 } 98 } 99 100 return null; 101 }; 102 /** 103 * A simple error handler. 104 * 105 * @param {String} message 106 */ 107 108 109 const handleLoginError = message => { 110 Joomla.renderMessages({ 111 error: [message] 112 }); 113 }; 114 /** 115 * Handles the browser response for the user interaction with the authenticator. Redirects to an 116 * internal page which handles the login server-side. 117 * 118 * @param { Object} publicKey Public key request options, returned from the server 119 */ 120 121 122 const handleLoginChallenge = publicKey => { 123 const arrayToBase64String = a => btoa(String.fromCharCode(...a)); 124 125 const base64url2base64 = input => { 126 let output = input.replace(/-/g, '+').replace(/_/g, '/'); 127 const pad = output.length % 4; 128 129 if (pad) { 130 if (pad === 1) { 131 throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); 132 } 133 134 output += new Array(5 - pad).join('='); 135 } 136 137 return output; 138 }; 139 140 if (!publicKey.challenge) { 141 handleLoginError(Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_INVALID_USERNAME')); 142 return; 143 } 144 145 publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), c => c.charCodeAt(0)); 146 147 if (publicKey.allowCredentials) { 148 publicKey.allowCredentials = publicKey.allowCredentials.map(data => { 149 data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), c => c.charCodeAt(0)); 150 return data; 151 }); 152 } 153 154 navigator.credentials.get({ 155 publicKey 156 }).then(data => { 157 const publicKeyCredential = { 158 id: data.id, 159 type: data.type, 160 rawId: arrayToBase64String(new Uint8Array(data.rawId)), 161 response: { 162 authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)), 163 clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), 164 signature: arrayToBase64String(new Uint8Array(data.response.signature)), 165 userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null 166 } 167 }; // Send the response to your server 168 169 const paths = Joomla.getOptions('system.paths'); 170 window.location = `$paths ? `$paths.base}/index.php` : window.location.pathname}?$Joomla.getOptions('csrf.token')}=1&option=com_ajax&group=system&plugin=webauthn&` + `format=raw&akaction=login&encoding=redirect&data=$btoa(JSON.stringify(publicKeyCredential))}`; 171 }).catch(error => { 172 // Example: timeout, interaction refused... 173 handleLoginError(error); 174 }); 175 }; 176 /** 177 * Initialize the passwordless login, going through the server to get the registered certificates 178 * for the user. 179 * 180 * @param {string} formId The login form's or login module's HTML ID 181 * 182 * @returns {boolean} Always FALSE to prevent BUTTON elements from reloading the page. 183 */ 184 // eslint-disable-next-line no-unused-vars 185 186 187 Joomla.plgSystemWebauthnLogin = formId => { 188 // Get the username 189 const elFormContainer = document.getElementById(formId); 190 const elUsername = lookForField(elFormContainer, 'input[name=username]'); 191 const elReturn = lookForField(elFormContainer, 'input[name=return]'); 192 193 if (elUsername === null) { 194 Joomla.renderMessages({ 195 error: [Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_CANNOT_FIND_USERNAME')] 196 }); 197 return false; 198 } 199 200 const username = elUsername.value; 201 const returnUrl = elReturn ? elReturn.value : null; // No username? We cannot proceed. We need a username to find the acceptable public keys :( 202 203 if (username === '') { 204 Joomla.renderMessages({ 205 error: [Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_EMPTY_USERNAME')] 206 }); 207 return false; 208 } // Get the Public Key Credential Request Options (challenge and acceptable public keys) 209 210 211 const postBackData = { 212 option: 'com_ajax', 213 group: 'system', 214 plugin: 'webauthn', 215 format: 'raw', 216 akaction: 'challenge', 217 encoding: 'raw', 218 username, 219 returnUrl 220 }; 221 postBackData[Joomla.getOptions('csrf.token')] = 1; 222 const paths = Joomla.getOptions('system.paths'); 223 Joomla.request({ 224 url: `$paths ? `$paths.base}/index.php` : window.location.pathname}?$Joomla.getOptions('csrf.token')}=1`, 225 method: 'POST', 226 data: interpolateParameters(postBackData), 227 228 onSuccess(rawResponse) { 229 let jsonData = {}; 230 231 try { 232 jsonData = JSON.parse(rawResponse); 233 } catch (e) { 234 /** 235 * In case of JSON decoding failure fall through; the error will be handled in the login 236 * challenge handler called below. 237 */ 238 } 239 240 handleLoginChallenge(jsonData); 241 }, 242 243 onError: xhr => { 244 handleLoginError(`$xhr.status} $xhr.statusText}`); 245 } 246 }); 247 return false; 248 }; // Initialization. Runs on DOM content loaded since this script is always loaded deferred. 249 250 251 const loginButtons = [].slice.call(document.querySelectorAll('.plg_system_webauthn_login_button')); 252 253 if (loginButtons.length) { 254 loginButtons.forEach(button => { 255 button.addEventListener('click', ({ 256 currentTarget 257 }) => { 258 Joomla.plgSystemWebauthnLogin(currentTarget.getAttribute('data-webauthn-form')); 259 }); 260 }); 261 } 262 })(Joomla, document);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |