[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/media/plg_system_webauthn/js/ -> login.js (source)

   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);


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer