Files
objects/wco/apxauth/apxauth.js
2025-07-01 11:09:51 +02:00

699 lines
23 KiB
JavaScript

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: {},
});
});
};