first commit
This commit is contained in:
233
media/plg_system_webauthn/js/login.js
Normal file
233
media/plg_system_webauthn/js/login.js
Normal 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);
|
||||
1
media/plg_system_webauthn/js/login.min.js
vendored
Normal file
1
media/plg_system_webauthn/js/login.min.js
vendored
Normal 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);
|
||||
BIN
media/plg_system_webauthn/js/login.min.js.gz
Normal file
BIN
media/plg_system_webauthn/js/login.min.js.gz
Normal file
Binary file not shown.
426
media/plg_system_webauthn/js/management.js
Normal file
426
media/plg_system_webauthn/js/management.js
Normal 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);
|
||||
1
media/plg_system_webauthn/js/management.min.js
vendored
Normal file
1
media/plg_system_webauthn/js/management.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
media/plg_system_webauthn/js/management.min.js.gz
Normal file
BIN
media/plg_system_webauthn/js/management.min.js.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user