var apx = apx || {}; apx.apxauth = {}; apx.apxauth.loadwco = async (id, ctx) => { // check if not authenticate, do nothing cause by default screensignin and wait authentification // if authenticate, if url xhash then redirect if no url then change wco-link=screenmytribes // if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth(); //load main.mustache of the component //when wco-xxx change it run this function console.log(`Load wconame:apxauth apx.apxauth.loadwco with id:${id} and ctx: ${JSON.stringify(ctx)}`); const tpldataname = `${apx.data.pagename}_${id}_apxauth`; const apxauthid = document.getElementById(id) const data = apx.apxauth.getdata(id, ctx); if (apxauthid.innerHTML.trim() === "") { apxauthid.innerHTML = Mustache.render( apx.data.tpl.apxauthmain, data ); } apxauthid.querySelector(`.screenaction`).innerHTML = Mustache.render( apx.data.tpl[`apxauthscreen${ctx.link}`], data ); apxauthid.querySelector(`.msginfo`).innerHTML = ""; }; apx.apxauth.getdata = (id, ctx) => { const tpldataname = `${apx.data.pagename}_${id}_apxauth`; const data = JSON.parse(JSON.stringify(apx.data.tpldata[tpldataname])); data.id = id; data.xalias = apx.data.headers.xalias; data.xtribe = apx.data.headers.xtribe; data.emailssuport = apx.data.appdata.emailsupport; switch (ctx.link) { case "logout": if (!data.profils) data.profils = []; apx.data.headers.xprofils.forEach((p) => { if (!["anonymous", "pagans", "persons"].includes(p)) { data.profils.push(apx.data.options.profil.itms[p].title); } }); data.noprofils = data.profils.length == 0; data.member = apx.data.headers.xprofils.includes("persons"); data.websites = apx.data.appdata.websites; // get tribes activities /*["", "https://wall-ants.ndda.fr"]; axios .get(`/api/apxtri/tribes/activities`, { headers: apx.data.headers, }) .then((rep) => {}) .catch((err) => {}); */ break; default: break; } console.log("data for tpl:", data); return data }; apx.apxauth.redirecturlwithauth = (url, tribe, webapp, newwindow, windowname = '_blank') => { url = url.replace(/_[a-zA-Z0-9]{2}\.html/, `_${apx.data.headers.xlang}.html`) url += `?xtribe=${tribe}&xapp=${webapp}&xalias=${apx.data.headers.xalias}` url += `&xdays=${apx.data.headers.xdays}&xhash=${apx.data.headers.xhash}` url += `&xprofils=${apx.data.headers.xprofils.join(',')}` url += `&xtrkversion=${apx.data.headers.xtrkversion}&xuuid=${apx.data.headers.xuuid}` if (newwindow) { try { const newwin = window.open(url, windowname) if (newwin === null || typeof newwin === 'undefined') { console.warn("L'ouverture de la fenêtre a été bloquée par un bloqueur de pop-up."); // Vous pouvez informer l'utilisateur ici qu'il doit désactiver son bloqueur de pop-up alert("Votre navigateur a bloqué l'ouverture d'un nouvel onglet. Veuillez autoriser les pop-ups pour ce site."); } else { // Optionnel: Mettre le focus sur la nouvelle fenêtre/onglet newwin.focus(); } return newwin; } catch (error) { console.error("Une erreur est survenue lors de l'ouverture de l'onglet :", error); return null; } } } /** * logout * Clean any private key into memory of this app and in the backend */ apx.apxauth.logout = () => { axios .get(`/api/apxtri/pagans/logout`, { headers: apx.data.headers, }) .then((rep) => { console.log("logout", rep); }) .catch((err) => { console.log("Erreur logout check:", err); }); apx.data = apxtri; apx.save(); if (apx.pagecontext.hash.url) { window.location.href = apx.pagecontext.hash.url; } else { location.reload(); } }; apx.apxauth.setheadersauth = async ( alias, passphrase, publickey, privatekey, rememberme ) => { /** * Set header with relevant authentification data * @return {status=200 if apx.data.headers and apx.data.auth properly set} * {status: 406 or 500 in case issue} */ //console.log(alias, passphrase, publickey, privatekey); if ( alias.length < 3 || publickey.length < 200 || (privatekey && privatekey.lengtht < 200) ) { return { status: 406, ref: "Pagans", msg: "aliasorprivkeytooshort", data: {}, }; } if (!passphrase) passphrase = ""; if (rememberme) { apx.data.auth = { alias: alias, publickey: publickey, privatekey: privatekey, passphrase: passphrase, }; } else if (apx.data.auth) { delete apx.data.auth; apx.save(); } apx.data.headers.xalias = alias; apx.data.headers.xdays = dayjs().valueOf(); const msg = `${alias}_${apx.data.headers.xdays}`; //console.log("pvk", privatekey); try { apx.data.headers.xhash = await apx.apxauth.clearmsgSignature( publickey, privatekey, passphrase, msg ); } catch (err) { return { status: 500, ref: "Middlewares", msg: "unconsistentpgp", data: { err: err }, }; } apx.save(); console.log("xhash set with:", apx.data.headers.xhash); return { status: 200 }; }; apx.apxauth.authentifyme = async ( id, alias, passphrase, privatekey, rememberme ) => { /** * Set apx.data.auth with pub, priv, passphrase alias that allow authentification * set headers with xdays (timestamp) and xhash of message: {alias}_{timestamp} generate with pub & priv key * * @Param {key} publickeycreate optional when alias does not exist */ //console.log(alias, passphrase); //console.log(privatekey); //clean previous answer if exist const idparent=document.getElementById(id).parentElement?.closest('[wco-name]').getAttribute('id') document.querySelector(`#${id} .msginfo`).innerHTML = ""; if (alias.length < 3 || privatekey.length < 200) { apx.notification(`#${id} .msginfo`, { status: 500, ref: "Pagans", msg: "aliasorprivkeytooshort", data: {}, }); return false; } console.log(`get /api/apxtri/pagans/alias/${alias}`); axios .get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.data.headers, }) .then(async (rep) => { //console.log(rep.data); const setheaders = await apx.apxauth.setheadersauth( alias, passphrase, rep.data.data.publickey, privatekey, rememberme ); if (setheaders.status != 200) { apx.notification(`#${id} .msginfo`, setheaders); } else { console.log("SetheadersOK"); console.log(`/api/apxtri/pagans/isauth`); axios .get(`/api/apxtri/pagans/isauth`, { headers: apx.data.headers, }) .then((rep) => { // Authenticate then store profils in header apx.data.headers.xprofils = rep.data.data.xprofils; apx.save(); // if this page is call with apxid_fr.html?url=httpsxxx then it redirect to this page. //alert(`${window.location.href.includes("/src/")?"/src/":""}${apx.pagecontext.hash.url}`) if (apx.pagecontext.hash.url) { window.location.href = `${apx.pagecontext.hash.url}`; } else { //location.reload(); document.getElementById(idparent).setAttribute('wco-link','mytribes'); } }) .catch((err) => { console.log("Not authentify:", err); delete apx.data.auth; apx.save(); document.getElementById(idparent).setAttribute("wco-link", "signin") if (err.response) { apx.notification(`#${id} .msginfo`, err.response.data); } else if (err.request) { apx.notification(`#${id} .msginfo`, { status: 500, ref: "Middlewares", msg: "errrequest", data: { err: err.request.response }, }); } }); } }) .catch((err) => { //console.log(err.response); //console.log(err.request); console.log("checkalias:", err); if (err.response && err.response.data.msg) { //remove auth if not well created previously //console.log(err.response.data.msg); if (err.response.data.msg == "aliasdoesnotexist") { delete apx.data.auth; apx.save(); apx.notification(`#${id} .msginfo`, { status: 404, ref: "Pagans", msg: "aliasdoesnotexist", data: { alias }, }); //document.getElementById("inputaliasauth").value=""; //document.getElementById("inputpassphraseauth").value=""; //document.getElementById("privatekeyauth").value="" //window.location.reload(); } apx.notification(`#${id} .msginfo`, err.response.data); } else { apx.notification(`#${id} .msginfo`, { status: 500, ref: "Middlewares", msg: "errrequest", data: { err }, }); } }); }; apx.apxauth.recoverykey = (id, aliasoremail) => { if (aliasoremail.length < 3) { apx.notification(`#${id} .msginfo`, { status: 406, ref: "Pagans", msg: "recoveryemailnotfound", data: { tribe: apx.data.headers.xtribe, search: aliasoremail }, }); return false; } const recodata = { tribe: apx.data.headers.xtribe, search: aliasoremail }; recodata.emailalias = Checkjson.testformat(aliasoremail, "email") ? "email" : "alias"; document.querySelector(`#${id} .msginfo`).innerHTML = ""; axios .post(`/api/apxtri/pagans/keyrecovery`, recodata, { headers: apx.data.headers, }) .then((rep) => { rep.data.data.search = aliasoremail; apx.notification(`#${id} .msginfo`, rep.data, true); }) .catch((err) => { //console.log("error:", err); const dataerr = err.response && err.response.data ? err.response.data : { status: 500, ref: "Pagans", msg: "checkconsole", data: {} }; dataerr.data.search = aliasoremail; apx.notification(`#${id} .msginfo`, dataerr, true); }); }; apx.apxauth.generateKey = async (alias, passphrase) => { /** * @param {string} alias a unique alias that identify an identity * @param {string} passphrase a string to cipher the publickey (can be empty, less secure but simpler) * @return {publickey,privatekey} with userIds = [{alias}] */ const pgpparam = { type: "ecc", // Type of the key, defaults to ECC curve: "curve25519", // ECC curve name, defaults to curve25519 userIDs: [{ alias: alias }], // you can pass multiple user IDs passphrase: passphrase, // protects the private key format: "armored", // output key format, defaults to 'armored' (options: 'armored', 'binary' or 'object') }; const { privateKey, publicKey } = await openpgp.generateKey(pgpparam); // key start by '-----BEGIN PGP PRIVATE KEY BLOCK ... ' // get liste of alias:pubklickey await axios.get('api/v0/pagans') // check alias does not exist return { alias, privatekey: privateKey, publickey: publicKey }; }; apx.apxauth.verifyKeys = async ( publicKeyArmored, privateKeyArmored, passphrase ) => { try { // Charger la clé publique const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored }); // Charger la clé privée const privateKey = await openpgp.decryptKey({ privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored, }), passphrase: passphrase, // Passphrase de la clé privée (si nécessaire) }); // Créer un message simple à signer const message = await openpgp.createMessage({ text: "Test message" }); // Signer le message avec la clé privée const signedMessage = await openpgp.sign({ message: message, // Message à signer signingKeys: privateKey, // Clé privée pour signer }); // Vérifier la signature avec la clé publique const verificationResult = await openpgp.verify({ message: await openpgp.readCleartextMessage({ cleartextMessage: signedMessage, }), verificationKeys: publicKey, // Clé publique pour vérifier }); // Vérifier si la signature est valide const { verified } = verificationResult.signatures[0]; await verified; // Resolve la promesse console.log("Les clés correspondent et sont valides !"); return true; } catch (error) { console.error("Erreur lors de la vérification des clés : ", error); return false; } }; apx.apxauth.testcreatekey = async (alias, passphrase) => { const pgpparam = { type: "ecc", // Type of the key, defaults to ECC curve: "curve25519", // ECC curve name, defaults to curve25519 userIDs: [{ alias: alias }], // you can pass multiple user IDs passphrase: passphrase, // protects the private key format: "armored", // output key format, defaults to 'armored' (options: 'armored', 'binary' or 'object') }; const { privateKey, publicKey } = await openpgp.generateKey(pgpparam); console.log(verifyKeys(publicKey, privateKey, passphrase)); }; apx.apxauth.detachedSignature = async (privK, passphrase, message) => { /** * @privK {string} a test priv key * @passphrase {string} used to read privK * @message {string} message to sign * @Return a detached Signature of the message */ let privatekey; if (passphrase == "" || passphrase == undefined) { privatekey = await openpgp.readKey({ armoredKey: privK }); } else { privatekey = await openpgp.decryptKey({ privateKey: await openpgp.readPrivateKey({ armoredKey: privK }), passphrase, }); } //console.log(message); const msg = await openpgp.createMessage({ text: message }); //console.log(msg); const sig = await openpgp.sign({ message: msg, signingKeys: privatekey, detached: true, }); return btoa(sig); }; apx.apxauth.clearmsgSignature = async (pubK, privK, passphrase, message) => { /** * @privK {string} a test priv key * @passphrase {string} used to read privK * @message {string} message to sign * @Return an base64 Signature of the message or error */ const publickey = await openpgp.readKey({ armoredKey: pubK }); let privatekey; if (passphrase == "" || passphrase == undefined) { privatekey = await openpgp.readKey({ armoredKey: privK }); } else { privatekey = await openpgp.decryptKey({ privateKey: await openpgp.readPrivateKey({ armoredKey: privK }), passphrase, }); } const cleartextMessage = await openpgp.sign({ message: await openpgp.createCleartextMessage({ text: message }), signingKeys: privatekey, }); console.log(cleartextMessage); const verificationResult = await openpgp.verify({ message: await openpgp.readCleartextMessage({ cleartextMessage }), verificationKeys: publickey, }); const verified = verificationResult.signatures[0]; const validity = await verified.verified; if (!validity) throw new Error("invalidsignature"); return btoa(cleartextMessage); }; apx.apxauth.authenticatedetachedSignature = async ( alias, pubK, detachedSignature, message ) => { /** * Check that alias (pubkey) signe a message * @alias {string} alias link to the publickey * @pubK {string} publiKey text format * @detachedSignature {string} a detachedsignatured get from apx.apxauth.detachedSignature * @message {string} the message signed * @return {boolean} true the message was signed by alias * false the message was not signed by alias */ const publickey = await openpgp.readKey({ armoredKey: pubK }); const msg = await openpgp.createMessage({ text: message }); const signature = await openpgp.readSignature({ armoredSignature: atob(detachedSignature), // parse detached signature }); const verificationResult = await openpgp.verify({ msg, // Message object signature, verificationKeys: publickey, }); const { verified, keyID } = verificationResult.signatures[0]; try { await verified; // throws on invalid signature //console.log("Signed by key id " + keyID.toHex()); return KeyId.toHex().alias == alias; } catch (e) { console.log("Signature could not be verified: " + e.message); return false; } }; apx.apxauth.createIdentity = async ( id, alias, recoemail, passphrase = "" ) => { document.querySelector(`#${id} .msginfo`).innerHTML = "" const aliasregex = /^[a-z0-9]*$/; //console.log(aliasregex.test(alias)); if (!(alias && alias.length > 3 && aliasregex.test(alias))) { apx.notification( `#${id} .msginfo`, { status: "406", ref: "Pagans", msg: "invalidalias", data: {}, }, true ); return false; } if (recoemail.length > 0 && !Checkjson.testformat(recoemail, "email")) { apx.notification(`#${id} .msginfo`, { status: 406, ref: "Pagans", msg: "invalidemail", data: {}, }); return false; } axios .get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.data.headers, }) .then((rep) => { console.log(rep); apx.notification( `#${id} .msginfo`, { ref: "Pagans", msg: "aliasexist", data: { alias }, }, true ); }) .catch(async (err) => { console.log("checkalias:", err); if (err.response && err.response.status == 404) { // alias does not exist create it is possible const keys = await apx.apxauth.generateKey(alias, passphrase); apx.data.tmpauth = { keys, recoemail, passphrase }; //console.log(apx.data.tmpauth); ["publickey", "privatekey"].forEach((k) => { console.log(`${id} button.signup${k}`); const btn = document.querySelector( `#${id} button.signup${k}` ); btn.addEventListener("click", () => { const blob = new Blob([keys[k]], { type: "text/plain" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${alias}_${k}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); }); document .querySelectorAll( `#${id} .signupalias, #${id} .signupemailrecovery, #${id} .signuppassphrase` ) .forEach((e) => e.setAttribute("disabled", "disabled")); document .querySelector(`#${id} .getmykeys`) .classList.remove("hidden"); document .querySelector(`#${id} .btncreatekey`) .classList.add("hidden"); } else { apx.notification( `#${id} .msginfo`, { ref: "Middlewares", msg: "errrequest", data: {}, }, true ); } }); }; /** * * @param {string} alias to create * @param {string} publickey * @param {string} trustedtribe if none => means no passphrase, no privatekey, no trustedtribe * @param {string} passphrase * @param {string} privatekey * @param {string} email if none => means no passphrase, no privatekey, no trustedtribe * * if email!=none and trustedtribe!= none create a person with parson profil in trustedtribe * if email!=none and trustedtribe==none then send an email at registration with all element but doi not store in backend for futur recovery * */ apx.apxauth.test = () => { //"apx.apxauth.registerIdentity(document.getElementById('inputalias').value,document.getElementById('publickey').document.getElementById('inputpassphrase').value)" console.log(apx.data.tmpauth); }; apx.apxauth.registerIdentity = async (id, trustedtribe) => { const authid = document.getElementById(id); // trustedtribe boolean //previously store in apx.data.tmpauth={keys:{alias,privatekey,publickey},recoemail,passphrase} const setheaders = await apx.apxauth.setheadersauth( apx.data.tmpauth.keys.alias, apx.data.tmpauth.passphrase, apx.data.tmpauth.keys.publickey, apx.data.tmpauth.keys.privatekey, false ); if (setheaders.status != 200) { apx.notification(`#${id} .msginfo`, setheaders); } else { // add withpublickeyforcreate to check isAuthenticated alias does not already exist const data = {}; data.alias = apx.data.tmpauth.keys.alias; data.publickey = apx.data.tmpauth.keys.publickey; console.log(apx.data.tmpauth.recoemail, Checkjson.testformat(apx.data.tmpauth.recoemail, "email")) if (apx.data.tmpauth.recoemail && Checkjson.testformat(apx.data.tmpauth.recoemail, "email")) { data.passphrase = apx.data.tmpauth.keyspassphrase; data.privatekey = apx.data.tmpauth.keysprivatekey; data.email = apx.data.tmpauth.recoemail; } data.trustedtribe = trustedtribe; axios .post(`/api/apxtri/pagans`, data, { headers: apx.data.headers }) .then((reppagan) => { //console.log(reppagan.data); apx.notification(`#${id} .msginfo`, reppagan.data); authid.querySelector(`.btncreateidentity`) .classList.add("hidden"); authid.querySelector(`.signupbtnreload`) .classList.remove("hidden"); //remove tmp cause create phc change to keep tplauth in memory and avoid asking again the pasword //delete apx.data.tmpauth; //apx.save(); }) .catch((err) => { console.log("error:", err); const dataerr = err.response && err.response.data ? err.response.data : { status: 500, ref: "Pagans", msg: "", data: {} }; apx.notification(`#${id} .msginfo`, dataerr); }); } }; apx.apxauth.jointribe = (id) => { /** * Allow a pagan to register as a person into a tribe * header must be authenticated with alias into an app belonging to xtribe AND schema person must have apxaccessright with role "pagan": {"C": []} */ //console.log(apx.data); if (!apx.data.headers.xprofils.includes("persons")) { apx.data.headers.xprofils.push("persons"); } const data = { alias: apx.data.headers.xalias, profils: apx.data.headers.xprofils, }; axios .put(`/api/apxtri/pagans/person/${apx.data.headers.xtribe}`, data, { headers: apx.data.headers, }) .then((rep) => { apx.notification(`#${id} .msginfo`, rep.data); axios .get(`/api/apxtri/pagans/logout`, { headers: apx.data.headers, }) .then((rep) => { console.log("logout", rep); apx.apxauth.authentifyme( id, apx.data.auth.alias, apx.data.auth.passphrase, apx.data.auth.privatekey ); }) .catch((err) => { console.log("Erreur logout check:", err); }); }) .catch((err) => { console.log("sorry", err); if (err.response && err.response.data) apx.notification("#msginfo", err.response.data); else apx.notification("#msginfo", { status: 500, ref: "Pagans", msg: "errcreate", data: {}, }); }); };