first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,3 @@
button[class*=plg_system_webauthn_login_button] {
padding: 0.4rem;
}

View File

@ -0,0 +1 @@
button[class*=plg_system_webauthn_login_button]{padding:.4rem}

Binary file not shown.

View File

@ -0,0 +1 @@
<svg aria-hidden="true" id="Passkey" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g id="icon-passkey"><circle id="icon-passkey-head" cx="10.5" cy="6" r="4.5"/><path id="icon-passkey-key" d="M22.5,10.5a3.5,3.5,0,1,0-5,3.15V19L19,20.5,21.5,18,20,16.5,21.5,15l-1.24-1.24A3.5,3.5,0,0,0,22.5,10.5Zm-3.5,0a1,1,0,1,1,1-1A1,1,0,0,1,19,10.5Z"/><path id="icon-passkey-body" d="M14.44,12.52A6,6,0,0,0,12,12H9a6,6,0,0,0-6,6v2H16V14.49A5.16,5.16,0,0,1,14.44,12.52Z"/></g></svg>

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1 @@
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="2.5em"><path fill="currentColor" d="M15.287 3.63a8.407 8.407 0 00-8.051 7.593h.55a7.805 7.805 0 012.24-4.713 5.825 5.825 0 00.924.695c-.608 1.177-.98 2.556-1.082 4.018h.135c.105-1.467.485-2.819 1.065-3.947.745.434 1.623.754 2.577.94a27.83 27.83 0 00-.25 3.763h-.847v.135h.847c.003 1.334.09 2.617.25 3.764-.954.185-1.832.506-2.577.94a9.997 9.997 0 01-.978-3.137h-.137c.164 1.16.502 2.25.997 3.208a5.825 5.825 0 00-.924.695 7.805 7.805 0 01-2.255-4.875H7.22A8.407 8.407 0 0024 12.034a8.398 8.398 0 00-.688-3.333 8.407 8.407 0 00-8.025-5.072zm.315.546c.155 0 .31.005.464.014.365.34.708 1.07.983 2.114a16.518 16.518 0 01.357 1.79 10.173 10.173 0 01-1.804.16 10.173 10.173 0 01-1.805-.16 16.519 16.519 0 01.357-1.79c.275-1.045.618-1.775.983-2.114a7.97 7.97 0 01.465-.014zm-.665.028c-.345.392-.658 1.093-.913 2.065a16.639 16.639 0 00-.36 1.8c-.939-.183-1.802-.498-2.533-.926.686-1.283 1.635-2.264 2.73-2.775a7.874 7.874 0 011.076-.164zm1.33 0a7.856 7.856 0 011.084.168c1.092.513 2.037 1.492 2.721 2.771-.73.428-1.594.743-2.533.927a16.64 16.64 0 00-.36-1.8c-.255-.972-.568-1.673-.912-2.066zm-2.972.314c-.655.407-1.257.989-1.776 1.73a8.166 8.166 0 00-.506.825 5.69 5.69 0 01-.891-.67 7.814 7.814 0 013.173-1.885zm4.624.006a7.862 7.862 0 013.164 1.877 5.692 5.692 0 01-.893.672 8.166 8.166 0 00-.506-.825c-.516-.738-1.115-1.318-1.765-1.724zm3.26 1.985a7.858 7.858 0 011.638 2.419 7.802 7.802 0 01.642 3.051h-2.095c-.01-1.74-.398-3.396-1.11-4.774a5.823 5.823 0 00.925-.696zm-1.044.767c.679 1.32 1.084 2.945 1.094 4.703h-3.42a27.863 27.863 0 00-.251-3.763c.954-.186 1.833-.506 2.577-.94zm-6.357.965a10.299 10.299 0 001.824.16 10.299 10.299 0 001.823-.16c.16 1.138.246 2.413.249 3.738h-1.178a1.03 1.03 0 01-.093.135h1.27a27.71 27.71 0 01-.248 3.739 10.397 10.397 0 00-3.647 0 27.733 27.733 0 01-.248-3.739h1.294a.99.99 0 01-.09-.135H13.53c.003-1.325.088-2.6.248-3.738zM2.558 9.37a2.585 2.585 0 00-2.547 2.35c-.142 1.541 1.064 2.842 2.566 2.842 1.26 0 2.312-.917 2.533-2.124h4.44v.972h.946v-.972h.837v1.431h.945v-2.376H5.11A2.586 2.586 0 002.558 9.37zm-.058.965a1.639 1.639 0 011.707 1.637 1.64 1.64 0 01-1.639 1.638 1.639 1.639 0 01-.068-3.275zm13.09.388a.75.75 0 00-.345 1.404l-.383 1.958h1.5l-.383-1.958a.75.75 0 00.384-.654.75.75 0 00-.773-.75zm2.218 1.391h3.421c-.01 1.758-.415 3.384-1.094 4.704-.744-.434-1.623-.755-2.577-.94a27.81 27.81 0 00.25-3.764zm3.556 0h2.095a7.805 7.805 0 01-2.281 5.47 5.825 5.825 0 00-.924-.696c.712-1.378 1.1-3.033 1.11-4.774zm-5.52 3.703a10.284 10.284 0 011.562.156 16.518 16.518 0 01-.357 1.791c-.275 1.045-.618 1.774-.982 2.114a7.972 7.972 0 01-.93 0c-.365-.34-.708-1.07-.983-2.114a16.519 16.519 0 01-.357-1.79 10.284 10.284 0 012.048-.157zm1.695.181c.94.184 1.803.5 2.533.926-.686 1.284-1.635 2.265-2.73 2.776a7.874 7.874 0 01-1.075.164c.344-.393.657-1.094.913-2.065a16.64 16.64 0 00.359-1.8zm-3.874 0a16.648 16.648 0 00.359 1.8c.255.973.568 1.674.913 2.066a7.873 7.873 0 01-1.075-.164c-1.096-.511-2.045-1.492-2.731-2.775.73-.428 1.594-.743 2.534-.927zm-2.652.997a8.16 8.16 0 00.506.825c.52.741 1.121 1.323 1.776 1.73a7.814 7.814 0 01-3.174-1.884 5.694 5.694 0 01.892-.67zm9.178 0a5.694 5.694 0 01.891.67 7.814 7.814 0 01-3.173 1.885c.654-.407 1.256-.989 1.775-1.73a8.16 8.16 0 00.507-.825z"></path></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,233 @@
/**
* @package Joomla.Plugin
* @subpackage System.webauthn
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
window.Joomla = window.Joomla || {};
((Joomla, document) => {
/**
* Converts a simple object containing query string parameters to a single, escaped query string.
* This method is a necessary evil since Joomla.request can only accept data as a string.
*
* @param object {object} A plain object containing the query parameters to pass
* @param prefix {string} Prefix for array-type parameters
*
* @returns {string}
*/
const interpolateParameters = (object, prefix = '') => {
let encodedString = '';
Object.keys(object).forEach(prop => {
if (typeof object[prop] !== 'object') {
if (encodedString.length > 0) {
encodedString += '&';
}
if (prefix === '') {
encodedString += `${encodeURIComponent(prop)}=${encodeURIComponent(object[prop])}`;
} else {
encodedString += `${encodeURIComponent(prefix)}[${encodeURIComponent(prop)}]=${encodeURIComponent(object[prop])}`;
}
return;
}
// Objects need special handling
encodedString += `${interpolateParameters(object[prop], prop)}`;
});
return encodedString;
};
/**
* Finds the first field matching a selector inside a form
*
* @param {HTMLFormElement} form The FORM element
* @param {String} fieldSelector The CSS selector to locate the field
*
* @returns {Element|null} NULL when no element is found
*/
const findField = (form, fieldSelector) => {
const elInputs = form.querySelectorAll(fieldSelector);
if (!elInputs.length) {
return null;
}
return elInputs[0];
};
/**
* Find a form field described by the CSS selector fieldSelector.
* The field must be inside a <form> element which is either the
* outerElement itself or enclosed by outerElement.
*
* @param {Element} outerElement The element which is either our form or contains our form.
* @param {String} fieldSelector The CSS selector to locate the field
*
* @returns {null|Element} NULL when no element is found
*/
const lookForField = (outerElement, fieldSelector) => {
let elInput = null;
if (!outerElement) {
return elInput;
}
const elElement = outerElement.parentElement;
if (elElement.nodeName === 'FORM') {
elInput = findField(elElement, fieldSelector);
return elInput;
}
const elForms = elElement.querySelectorAll('form');
if (elForms.length) {
for (let i = 0; i < elForms.length; i += 1) {
elInput = findField(elForms[i], fieldSelector);
if (elInput !== null) {
return elInput;
}
}
}
return null;
};
/**
* A simple error handler.
*
* @param {String} message
*/
const handleLoginError = message => {
Joomla.renderMessages({
error: [message]
});
};
/**
* Handles the browser response for the user interaction with the authenticator. Redirects to an
* internal page which handles the login server-side.
*
* @param { Object} publicKey Public key request options, returned from the server
*/
const handleLoginChallenge = publicKey => {
const arrayToBase64String = a => btoa(String.fromCharCode(...a));
const base64url2base64 = input => {
let output = input.replace(/-/g, '+').replace(/_/g, '/');
const pad = output.length % 4;
if (pad) {
if (pad === 1) {
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
}
output += new Array(5 - pad).join('=');
}
return output;
};
if (!publicKey.challenge) {
handleLoginError(Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_INVALID_USERNAME'));
return;
}
publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), c => c.charCodeAt(0));
if (publicKey.allowCredentials) {
publicKey.allowCredentials = publicKey.allowCredentials.map(data => {
data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), c => c.charCodeAt(0));
return data;
});
}
navigator.credentials.get({
publicKey
}).then(data => {
const publicKeyCredential = {
id: data.id,
type: data.type,
rawId: arrayToBase64String(new Uint8Array(data.rawId)),
response: {
authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)),
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
signature: arrayToBase64String(new Uint8Array(data.response.signature)),
userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null
}
};
// Send the response to your server
const paths = Joomla.getOptions('system.paths');
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))}`;
}).catch(error => {
// Example: timeout, interaction refused...
handleLoginError(error);
});
};
/**
* Initialize the passwordless login, going through the server to get the registered certificates
* for the user.
*
* @param {string} formId The login form's or login module's HTML ID
*
* @returns {boolean} Always FALSE to prevent BUTTON elements from reloading the page.
*/
// eslint-disable-next-line no-unused-vars
Joomla.plgSystemWebauthnLogin = formId => {
// Get the username
const elFormContainer = document.getElementById(formId);
const elUsername = lookForField(elFormContainer, 'input[name=username]');
const elReturn = lookForField(elFormContainer, 'input[name=return]');
if (elUsername === null) {
Joomla.renderMessages({
error: [Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_CANNOT_FIND_USERNAME')]
});
return false;
}
const username = elUsername.value;
const returnUrl = elReturn ? elReturn.value : null;
// No username? We cannot proceed. We need a username to find the acceptable public keys :(
if (username === '') {
Joomla.renderMessages({
error: [Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_EMPTY_USERNAME')]
});
return false;
}
// Get the Public Key Credential Request Options (challenge and acceptable public keys)
const postBackData = {
option: 'com_ajax',
group: 'system',
plugin: 'webauthn',
format: 'raw',
akaction: 'challenge',
encoding: 'raw',
username,
returnUrl
};
postBackData[Joomla.getOptions('csrf.token')] = 1;
const paths = Joomla.getOptions('system.paths');
Joomla.request({
url: `${paths ? `${paths.base}/index.php` : window.location.pathname}?${Joomla.getOptions('csrf.token')}=1`,
method: 'POST',
data: interpolateParameters(postBackData),
onSuccess(rawResponse) {
let jsonData = {};
try {
jsonData = JSON.parse(rawResponse);
} catch (e) {
/**
* In case of JSON decoding failure fall through; the error will be handled in the login
* challenge handler called below.
*/
}
handleLoginChallenge(jsonData);
},
onError: xhr => {
handleLoginError(`${xhr.status} ${xhr.statusText}`);
}
});
return false;
};
// Initialization. Runs on DOM content loaded since this script is always loaded deferred.
const loginButtons = [].slice.call(document.querySelectorAll('.plg_system_webauthn_login_button'));
if (loginButtons.length) {
loginButtons.forEach(button => {
button.addEventListener('click', ({
currentTarget
}) => {
Joomla.plgSystemWebauthnLogin(currentTarget.getAttribute('data-webauthn-form'));
});
});
}
})(Joomla, document);

View File

@ -0,0 +1 @@
window.Joomla=window.Joomla||{},((e,t)=>{const n=(e,t="")=>{let r="";return Object.keys(e).forEach((o=>{if("object"!=typeof e[o])return r.length>0&&(r+="&"),void(r+=""===t?`${encodeURIComponent(o)}=${encodeURIComponent(e[o])}`:`${encodeURIComponent(t)}[${encodeURIComponent(o)}]=${encodeURIComponent(e[o])}`);r+=`${n(e[o],o)}`})),r},r=(e,t)=>{const n=e.querySelectorAll(t);return n.length?n[0]:null},o=(e,t)=>{let n=null;if(!e)return n;const o=e.parentElement;if("FORM"===o.nodeName)return n=r(o,t),n;const a=o.querySelectorAll("form");if(a.length)for(let e=0;e<a.length;e+=1)if(n=r(a[e],t),null!==n)return n;return null},a=t=>{e.renderMessages({error:[t]})};e.plgSystemWebauthnLogin=r=>{const l=t.getElementById(r),s=o(l,"input[name=username]"),i=o(l,"input[name=return]");if(null===s)return e.renderMessages({error:[e.Text._("PLG_SYSTEM_WEBAUTHN_ERR_CANNOT_FIND_USERNAME")]}),!1;const c=s.value,u=i?i.value:null;if(""===c)return e.renderMessages({error:[e.Text._("PLG_SYSTEM_WEBAUTHN_ERR_EMPTY_USERNAME")]}),!1;const d={option:"com_ajax",group:"system",plugin:"webauthn",format:"raw",akaction:"challenge",encoding:"raw",username:c,returnUrl:u};d[e.getOptions("csrf.token")]=1;const g=e.getOptions("system.paths");return e.request({url:`${g?`${g.base}/index.php`:window.location.pathname}?${e.getOptions("csrf.token")}=1`,method:"POST",data:n(d),onSuccess(t){let n={};try{n=JSON.parse(t)}catch(e){}(t=>{const n=e=>btoa(String.fromCharCode(...e)),r=e=>{let t=e.replace(/-/g,"+").replace(/_/g,"/");const n=t.length%4;if(n){if(1===n)throw new Error("InvalidLengthError: Input base64url string is the wrong length to determine padding");t+=new Array(5-n).join("=")}return t};t.challenge?(t.challenge=Uint8Array.from(window.atob(r(t.challenge)),(e=>e.charCodeAt(0))),t.allowCredentials&&(t.allowCredentials=t.allowCredentials.map((e=>(e.id=Uint8Array.from(window.atob(r(e.id)),(e=>e.charCodeAt(0))),e)))),navigator.credentials.get({publicKey:t}).then((t=>{const r={id:t.id,type:t.type,rawId:n(new Uint8Array(t.rawId)),response:{authenticatorData:n(new Uint8Array(t.response.authenticatorData)),clientDataJSON:n(new Uint8Array(t.response.clientDataJSON)),signature:n(new Uint8Array(t.response.signature)),userHandle:t.response.userHandle?n(new Uint8Array(t.response.userHandle)):null}},o=e.getOptions("system.paths");window.location=`${o?`${o.base}/index.php`:window.location.pathname}?${e.getOptions("csrf.token")}=1&option=com_ajax&group=system&plugin=webauthn&format=raw&akaction=login&encoding=redirect&data=${btoa(JSON.stringify(r))}`})).catch((e=>{a(e)}))):a(e.Text._("PLG_SYSTEM_WEBAUTHN_ERR_INVALID_USERNAME"))})(n)},onError:e=>{a(`${e.status} ${e.statusText}`)}}),!1};const l=[].slice.call(t.querySelectorAll(".plg_system_webauthn_login_button"));l.length&&l.forEach((t=>{t.addEventListener("click",(({currentTarget:t})=>{e.plgSystemWebauthnLogin(t.getAttribute("data-webauthn-form"))}))}))})(Joomla,document);

Binary file not shown.

View File

@ -0,0 +1,426 @@
/**
* @package Joomla.Plugin
* @subpackage System.webauthn
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
window.Joomla = window.Joomla || {};
((Joomla, document) => {
/**
* Converts a simple object containing query string parameters to a single, escaped query string.
* This method is a necessary evil since Joomla.request can only accept data as a string.
*
* @param object {object} A plain object containing the query parameters to pass
* @param prefix {string} Prefix for array-type parameters
*
* @returns {string}
*/
const interpolateParameters = (object, prefix = '') => {
let encodedString = '';
Object.keys(object).forEach(prop => {
if (typeof object[prop] !== 'object') {
if (encodedString.length > 0) {
encodedString += '&';
}
if (prefix === '') {
encodedString += `${encodeURIComponent(prop)}=${encodeURIComponent(object[prop])}`;
} else {
encodedString += `${encodeURIComponent(prefix)}[${encodeURIComponent(prop)}]=${encodeURIComponent(object[prop])}`;
}
return;
}
// Objects need special handling
encodedString += `${interpolateParameters(object[prop], prop)}`;
});
return encodedString;
};
/**
* A simple error handler
*
* @param {String} message
*/
const handleCreationError = message => {
Joomla.renderMessages({
error: [message]
});
};
/**
* Ask the user to link an authenticator using the provided public key (created server-side).
* Posts the credentials to the URL defined in post_url using AJAX.
* That URL must re-render the management interface.
* These contents will replace the element identified by the interface_selector CSS selector.
*/
// eslint-disable-next-line no-unused-vars
Joomla.plgSystemWebauthnInitCreateCredentials = () => {
// Make sure the browser supports Webauthn
if (!('credentials' in navigator)) {
Joomla.renderMessages({
error: [Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_NO_BROWSER_SUPPORT')]
});
return;
}
// Get the public key creation options through AJAX.
const paths = Joomla.getOptions('system.paths');
const postURL = `${paths ? `${paths.base}/index.php` : window.location.pathname}`;
const postBackData = {
option: 'com_ajax',
group: 'system',
plugin: 'webauthn',
format: 'json',
akaction: 'initcreate',
encoding: 'json'
};
postBackData[Joomla.getOptions('csrf.token')] = 1;
Joomla.request({
url: postURL,
method: 'POST',
data: interpolateParameters(postBackData),
onSuccess(response) {
try {
const publicKey = JSON.parse(response);
Joomla.plgSystemWebauthnCreateCredentials(publicKey);
} catch (exception) {
handleCreationError(Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_XHR_INITCREATE'));
}
},
onError: xhr => {
handleCreationError(`${xhr.status} ${xhr.statusText}`);
}
});
};
Joomla.plgSystemWebauthnCreateCredentials = publicKey => {
const paths = Joomla.getOptions('system.paths');
const postURL = `${paths ? `${paths.base}/index.php` : window.location.pathname}`;
const arrayToBase64String = a => btoa(String.fromCharCode(...a));
const base64url2base64 = input => {
let output = input.replace(/-/g, '+').replace(/_/g, '/');
const pad = output.length % 4;
if (pad) {
if (pad === 1) {
throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
}
output += new Array(5 - pad).join('=');
}
return output;
};
// Convert the public key information to a format usable by the browser's credentials manager
publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), c => c.charCodeAt(0));
publicKey.user.id = Uint8Array.from(window.atob(publicKey.user.id), c => c.charCodeAt(0));
if (publicKey.excludeCredentials) {
publicKey.excludeCredentials = publicKey.excludeCredentials.map(data => {
data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), c => c.charCodeAt(0));
return data;
});
}
// Ask the browser to prompt the user for their authenticator
navigator.credentials.create({
publicKey
}).then(data => {
const publicKeyCredential = {
id: data.id,
type: data.type,
rawId: arrayToBase64String(new Uint8Array(data.rawId)),
response: {
clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)),
attestationObject: arrayToBase64String(new Uint8Array(data.response.attestationObject))
}
};
// Send the response to your server
const postBackData = {
option: 'com_ajax',
group: 'system',
plugin: 'webauthn',
format: 'raw',
akaction: 'create',
encoding: 'raw',
data: btoa(JSON.stringify(publicKeyCredential))
};
postBackData[Joomla.getOptions('csrf.token')] = 1;
Joomla.request({
url: postURL,
method: 'POST',
data: interpolateParameters(postBackData),
onSuccess(responseHTML) {
const elements = document.querySelectorAll('#plg_system_webauthn-management-interface');
if (!elements) {
return;
}
const elContainer = elements[0];
elContainer.outerHTML = responseHTML;
Joomla.plgSystemWebauthnInitialize();
Joomla.plgSystemWebauthnReactivateTooltips();
},
onError: xhr => {
handleCreationError(`${xhr.status} ${xhr.statusText}`);
}
});
}).catch(error => {
// An error occurred: timeout, request to provide the authenticator refused, hardware /
// software error...
handleCreationError(error);
});
};
/**
* Edit label button
*
* @param {Element} that The button being clicked
* @param {String} storeID CSS ID for the element storing the configuration in its data
* properties
*/
// eslint-disable-next-line no-unused-vars
Joomla.plgSystemWebauthnEditLabel = that => {
const paths = Joomla.getOptions('system.paths');
const postURL = `${paths ? `${paths.base}/index.php` : window.location.pathname}`;
// Find the UI elements
const elTR = that.parentElement.parentElement;
const credentialId = elTR.dataset.credential_id;
const elTDs = elTR.querySelectorAll('.webauthnManagementCell');
const elLabelTD = elTDs[0];
const elButtonsTD = elTDs[1];
const elButtons = elButtonsTD.querySelectorAll('button');
const elEdit = elButtons[0];
const elDelete = elButtons[1];
// Show the editor
const oldLabel = elLabelTD.innerText;
const elContainer = document.createElement('div');
elContainer.className = 'webauthnManagementEditorRow d-flex gap-2';
const elInput = document.createElement('input');
elInput.type = 'text';
elInput.name = 'label';
elInput.defaultValue = oldLabel;
elInput.className = 'form-control';
const elSave = document.createElement('button');
elSave.className = 'btn btn-success btn-sm';
elSave.innerText = Joomla.Text._('PLG_SYSTEM_WEBAUTHN_MANAGE_BTN_SAVE_LABEL');
elSave.addEventListener('click', () => {
const elNewLabel = elInput.value;
if (elNewLabel !== '') {
const postBackData = {
option: 'com_ajax',
group: 'system',
plugin: 'webauthn',
format: 'json',
encoding: 'json',
akaction: 'savelabel',
credential_id: credentialId,
new_label: elNewLabel
};
postBackData[Joomla.getOptions('csrf.token')] = 1;
Joomla.request({
url: postURL,
method: 'POST',
data: interpolateParameters(postBackData),
onSuccess(rawResponse) {
let result = false;
try {
result = JSON.parse(rawResponse);
} catch (exception) {
result = rawResponse === 'true';
}
if (result !== true) {
handleCreationError(Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_LABEL_NOT_SAVED'));
}
},
onError: xhr => {
handleCreationError(`${Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_LABEL_NOT_SAVED')} -- ${xhr.status} ${xhr.statusText}`);
}
});
}
elLabelTD.innerText = elNewLabel;
elEdit.disabled = false;
elDelete.disabled = false;
return false;
}, false);
const elCancel = document.createElement('button');
elCancel.className = 'btn btn-danger btn-sm';
elCancel.innerText = Joomla.Text._('PLG_SYSTEM_WEBAUTHN_MANAGE_BTN_CANCEL_LABEL');
elCancel.addEventListener('click', () => {
elLabelTD.innerText = oldLabel;
elEdit.disabled = false;
elDelete.disabled = false;
return false;
}, false);
elLabelTD.innerHTML = '';
elContainer.appendChild(elInput);
elContainer.appendChild(elSave);
elContainer.appendChild(elCancel);
elLabelTD.appendChild(elContainer);
elEdit.disabled = true;
elDelete.disabled = true;
return false;
};
/**
* Delete button
*
* @param {Element} that The button being clicked
*/
// eslint-disable-next-line no-unused-vars
Joomla.plgSystemWebauthnDelete = that => {
if (!window.confirm(Joomla.Text._('JGLOBAL_CONFIRM_DELETE'))) {
return false;
}
const paths = Joomla.getOptions('system.paths');
const postURL = `${paths ? `${paths.base}/index.php` : window.location.pathname}`;
// Find the UI elements
const elTR = that.parentElement.parentElement;
const credentialId = elTR.dataset.credential_id;
const elTDs = elTR.querySelectorAll('.webauthnManagementCell');
const elButtonsTD = elTDs[1];
const elButtons = elButtonsTD.querySelectorAll('button');
const elEdit = elButtons[0];
const elDelete = elButtons[1];
elEdit.disabled = true;
elDelete.disabled = true;
// Delete the record
const postBackData = {
option: 'com_ajax',
group: 'system',
plugin: 'webauthn',
format: 'json',
encoding: 'json',
akaction: 'delete',
credential_id: credentialId
};
postBackData[Joomla.getOptions('csrf.token')] = 1;
Joomla.request({
url: postURL,
method: 'POST',
data: interpolateParameters(postBackData),
onSuccess(rawResponse) {
let result = false;
try {
result = JSON.parse(rawResponse);
} catch (e) {
result = rawResponse === 'true';
}
if (result !== true) {
handleCreationError(Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_NOT_DELETED'));
return;
}
elTR.parentElement.removeChild(elTR);
},
onError: xhr => {
elEdit.disabled = false;
elDelete.disabled = false;
handleCreationError(`${Joomla.Text._('PLG_SYSTEM_WEBAUTHN_ERR_NOT_DELETED')} -- ${xhr.status} ${xhr.statusText}`);
}
});
return false;
};
Joomla.plgSystemWebauthnReactivateTooltips = () => {
const tooltips = Joomla.getOptions('bootstrap.tooltip');
if (typeof tooltips === 'object' && tooltips !== null) {
Object.keys(tooltips).forEach(tooltip => {
const opt = tooltips[tooltip];
const options = {
animation: opt.animation ? opt.animation : true,
container: opt.container ? opt.container : false,
delay: opt.delay ? opt.delay : 0,
html: opt.html ? opt.html : false,
selector: opt.selector ? opt.selector : false,
trigger: opt.trigger ? opt.trigger : 'hover focus',
fallbackPlacement: opt.fallbackPlacement ? opt.fallbackPlacement : null,
boundary: opt.boundary ? opt.boundary : 'clippingParents',
title: opt.title ? opt.title : '',
customClass: opt.customClass ? opt.customClass : '',
sanitize: opt.sanitize ? opt.sanitize : true,
sanitizeFn: opt.sanitizeFn ? opt.sanitizeFn : null,
popperConfig: opt.popperConfig ? opt.popperConfig : null
};
if (opt.placement) {
options.placement = opt.placement;
}
if (opt.template) {
options.template = opt.template;
}
if (opt.allowList) {
options.allowList = opt.allowList;
}
const elements = Array.from(document.querySelectorAll(tooltip));
if (elements.length) {
elements.map(el => new window.bootstrap.Tooltip(el, options));
}
});
}
};
/**
* Add New Authenticator button click handler
*
* @param {MouseEvent} event The mouse click event
*
* @returns {boolean} Returns false to prevent the default browser button behavior
*/
Joomla.plgSystemWebauthnAddOnClick = event => {
event.preventDefault();
Joomla.plgSystemWebauthnInitCreateCredentials();
return false;
};
/**
* Edit Name button click handler
*
* @param {MouseEvent} event The mouse click event
*
* @returns {boolean} Returns false to prevent the default browser button behavior
*/
Joomla.plgSystemWebauthnEditOnClick = event => {
event.preventDefault();
Joomla.plgSystemWebauthnEditLabel(event.currentTarget);
return false;
};
/**
* Remove button click handler
*
* @param {MouseEvent} event The mouse click event
*
* @returns {boolean} Returns false to prevent the default browser button behavior
*/
Joomla.plgSystemWebauthnDeleteOnClick = event => {
event.preventDefault();
Joomla.plgSystemWebauthnDelete(event.currentTarget);
return false;
};
/**
* Initialization on page load.
*/
Joomla.plgSystemWebauthnInitialize = () => {
const addButton = document.getElementById('plg_system_webauthn-manage-add');
if (addButton) {
addButton.addEventListener('click', Joomla.plgSystemWebauthnAddOnClick);
}
const editLabelButtons = [].slice.call(document.querySelectorAll('.plg_system_webauthn-manage-edit'));
if (editLabelButtons.length) {
editLabelButtons.forEach(button => {
button.addEventListener('click', Joomla.plgSystemWebauthnEditOnClick);
});
}
const deleteButtons = [].slice.call(document.querySelectorAll('.plg_system_webauthn-manage-delete'));
if (deleteButtons.length) {
deleteButtons.forEach(button => {
button.addEventListener('click', Joomla.plgSystemWebauthnDeleteOnClick);
});
}
};
// Initialization. Runs on DOM content loaded since this script is always loaded deferred.
Joomla.plgSystemWebauthnInitialize();
})(Joomla, document);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,3 @@
button[class*=plg_system_webauthn_login_button] {
padding: .4rem;
}