1st commit
This commit is contained in:
698
wco/apxauth/apxauth.js
Normal file
698
wco/apxauth/apxauth.js
Normal file
@@ -0,0 +1,698 @@
|
||||
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: {},
|
||||
});
|
||||
});
|
||||
};
|
5
wco/apxauth/exampleapxauth_fr.json
Normal file
5
wco/apxauth/exampleapxauth_fr.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"aliasinvalid": "Combinaison de 3 à 150 caractères <br /> composée de minuscules (a à z) et/ou de chiffres (0 à 9)",
|
||||
"aliastitle": "Uniquement minuscules ou chiffres",
|
||||
"privatekeyplaceholder": "Votre clé privée"
|
||||
}
|
7
wco/apxauth/main_fr.mustache
Normal file
7
wco/apxauth/main_fr.mustache
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- screen action-->
|
||||
<div class="screenaction mt-5 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||
</div>
|
||||
<!-- feedback action-->
|
||||
<div class="my-5">
|
||||
<p class="msginfo text-center text-info"></p>
|
||||
</div>
|
44
wco/apxauth/screenforgetkey_fr.mustache
Normal file
44
wco/apxauth/screenforgetkey_fr.mustache
Normal file
@@ -0,0 +1,44 @@
|
||||
<div class="mt-1">
|
||||
<label class="input validator mbt-1">
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input id="inputaliasrecovery" type="text" placeholder="mail@site.com | alias" required />
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
Enter a valid email or an alias (lowercase a-z and 0-9)
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<p>
|
||||
Si vous avez fait confiance à ce domaine pour garder vos clés, un email va être envoyé avec vos clés.
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<button
|
||||
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="apx.apxauth.recoverykey('{{id}}',document.getElementById('inputaliasrecovery').value);"
|
||||
>
|
||||
M'envoyer un email avec mes clés
|
||||
</button>
|
||||
</div>
|
41
wco/apxauth/screeninformation_fr.mustache
Normal file
41
wco/apxauth/screeninformation_fr.mustache
Normal file
@@ -0,0 +1,41 @@
|
||||
<div class="space-y-6 text-justify">
|
||||
<h2>Qu'est-ce qu'une identité numérique décentralisée?</h2>
|
||||
<p>
|
||||
C'est <span class="text-secondary">un moyen de s'identifier en prouvant qu'on est le propriétaire
|
||||
d'un alias ou d'une clé publique</span>. Cette clé publique est accessible à tous et utilisée dans le
|
||||
monde numérique pour informer, payer, échanger,... et porte une
|
||||
réputation publique.
|
||||
</p>
|
||||
<p>
|
||||
Concrètement, c'est une paire de fichiers texte appelée clé publique
|
||||
et clé privée. La clé publique ne porte pas d'information
|
||||
personnelle autre que celles que vous avez bien voulu y associer.
|
||||
</p>
|
||||
<p>
|
||||
Une fonction mathématique permet au propriétaire de la clé privée de
|
||||
signer un message. Le destinataire dispose d'une autre fonction qui
|
||||
permet de vérifier que la signature a été faite avec la clé privée.
|
||||
</p>
|
||||
<p>
|
||||
Cette interface permet de créer une identité et de l'utiliser pour
|
||||
s'authentifier pour 24 heures. Elle n'envoie que le couple alias/clé
|
||||
publique sur internet, la clé privée est
|
||||
<span class="text-secondary">votre propriété et ne doit jamais être communiquée</span>. Si vous
|
||||
la perdez, vous ne pourrez plus récupérer les informations
|
||||
associées. Sauf si vous
|
||||
<span class="text-secondary">avez fait confiance à ce nom de domaine</span>, vous pourrez lui
|
||||
demander d'envoyer un email avec ces clés.
|
||||
</p>
|
||||
<p>
|
||||
Vous pouvez avoir autant d'identités que vous voulez, vous pouvez
|
||||
créer une identité pour des objets uniques. La seule limite est qu'à
|
||||
partir du moment où vous associez des informations personnelles à
|
||||
cette clé, le destinataire de ces informations peut les relier aux
|
||||
activités de cette identité inscrite dans la blockchain apxtri.
|
||||
</p>
|
||||
<p>
|
||||
Pour auditer le code js, utiliser l'outil de développement de votre
|
||||
navigateur. Pour toute remarque, question ou détection de failles :
|
||||
{{supportemail}}
|
||||
</p>
|
||||
</div>
|
39
wco/apxauth/screenlogout_fr.mustache
Normal file
39
wco/apxauth/screenlogout_fr.mustache
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="flex flex-col space-y-1 text-center">
|
||||
<div class="mt-1">
|
||||
<h1 class="mb-6">
|
||||
Bonjour {{xalias}},
|
||||
</h1>
|
||||
<p>
|
||||
Si cet appareil ne vous appartiens pas et que vous n'utilisez pas l'application, vous devriez vous deconnecter.
|
||||
</p>
|
||||
<p class="text-center text-gray-500">
|
||||
Nettoyer mes traces de cet appareil?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.logout('{{id}}','logout','logout','apxauth')">Se deconnecter</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-center text-gray-500">
|
||||
Voir mes échanges?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
href="https://wall-ants.ndda.fr/apxwallet_fr.html" >Mon activité </a>
|
||||
</p>
|
||||
{{#member}}
|
||||
<p>
|
||||
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||
</p>
|
||||
{{/member}}
|
||||
{{^member}}
|
||||
<p> Vous n'êtes pas encore membre de {{xtribe}} </p>
|
||||
<p class=" mt-1 text-center text-gray-500">
|
||||
Envie d'jouter cette tribut {{xtribe}}?
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.jointribe('{{id}}')">Rejoindre {{xtribe}}</a>
|
||||
</p>
|
||||
{{/member}}
|
||||
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||
</p>
|
||||
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||
</div>
|
||||
</div>
|
23
wco/apxauth/screenmytribes_fr.mustache
Normal file
23
wco/apxauth/screenmytribes_fr.mustache
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="flex flex-col space-y-1 text-center">
|
||||
<div class="mt-1">
|
||||
<h1 class="mb-6">
|
||||
Bonjour {{xalias}},
|
||||
</h1>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p class="text-center text-gray-500">
|
||||
Redirige vers
|
||||
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||
onclick="apx.apxauth.redirecturlwithauth('http://recruiter.smatchit.newdev.ants/src/offer_fr.html','smatchit','recruiter',true);" >Redirige vers recruiter.smatchit.io/offer_fr.html&xhash....</a>
|
||||
</p>
|
||||
{{#member}}
|
||||
<p>
|
||||
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||
</p>
|
||||
{{/member}}
|
||||
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||
</p>
|
||||
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||
</div>
|
||||
</div>
|
69
wco/apxauth/screensignin_fr.mustache
Normal file
69
wco/apxauth/screensignin_fr.mustache
Normal file
@@ -0,0 +1,69 @@
|
||||
<p data-wco="createid" class="text-center text-neutral-content">
|
||||
{{{signintitle}}}
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<label class="input validator">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
class="signinalias"
|
||||
type="input"
|
||||
required
|
||||
placeholder="alias"
|
||||
pattern="[a-z0-9\-]*"
|
||||
minlength="3"
|
||||
maxlength="30"
|
||||
title="{{{aliastitle}}}"
|
||||
/>
|
||||
</label>
|
||||
<p class="validator-hint hidden"> {{{aliasinvalid}}}</p>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input mt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input type="text" class="signinpassphrase" placeholder="passphrase (option)" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<textarea rows=5 class="mt-2 textarea signinprivatekey" placeholder="{{{privatekeyplaceholder}}}"></textarea>
|
||||
</div>
|
||||
<div class="flex m-6">
|
||||
<div class="w-14 flex-none">
|
||||
<input type="checkbox" checked="checked" class="checkbox signinrememberme" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-justify" >{{{remembermetext}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-4">
|
||||
<button
|
||||
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const loginid= document.getElementById('{{id}}');apx.apxauth.authentifyme(
|
||||
'{{id}}',
|
||||
loginid.querySelector('.signinalias').value,
|
||||
loginid.querySelector('.signinpassphrase').value,
|
||||
loginid.querySelector('.signinprivatekey').value,
|
||||
loginid.querySelector('.signinrememberme').checked
|
||||
)">
|
||||
{{{authentifyme}}}
|
||||
</button>
|
||||
</div>
|
121
wco/apxauth/screensignup_fr.mustache
Normal file
121
wco/apxauth/screensignup_fr.mustache
Normal file
@@ -0,0 +1,121 @@
|
||||
<p data-wco="createid" class="text-center text-neutral-content">
|
||||
{{{signuptitle}}}
|
||||
</p>
|
||||
<div class="paramid">
|
||||
<div class="mt-2">
|
||||
<label class="input validator mbt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input
|
||||
class="signupalias"
|
||||
type="input"
|
||||
required
|
||||
placeholder="alias"
|
||||
pattern="[a-z0-9\-]*"
|
||||
minlength="3"
|
||||
maxlength="30"
|
||||
title="{{{aliastitle}}}"
|
||||
/>
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
<p>{{{aliasinvalid}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input validator mt-1">
|
||||
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input class="signupemailrecovery" type="email" placeholder="mail@site.com" required />
|
||||
</label>
|
||||
<div class="validator-hint hidden">
|
||||
{{{emailinvalid}}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<label class="input mt-1">
|
||||
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<g
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke-width="1"
|
||||
fill="black"
|
||||
stroke="black"
|
||||
>
|
||||
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input type="text" class="signuppassphrase" placeholder="passphrase (option)" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button
|
||||
class="btncreatekey btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const authid=document.getElementById('{{id}}');console.log('{{id}}'); apx.apxauth.createIdentity(
|
||||
'{{id}}',
|
||||
authid.querySelector('.signupalias').value,
|
||||
authid.querySelector('.signupemailrecovery').value,
|
||||
authid.querySelector('.signuppassphrase').value
|
||||
)"
|
||||
>
|
||||
{{{createkey}}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="getmykeys hidden mt-1">
|
||||
<div class="flex m-6">
|
||||
<div class="w-14 flex-none">
|
||||
<input type="checkbox" checked="checked" class="signuptrustedcheck checkbox checkbox-secondary" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm text-justify" >{{{trusttext}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="downloadkeys text-center mt-1">
|
||||
<button
|
||||
class="signuppublickey btn btn-outline btn-accent text-white shadow-sm"
|
||||
>
|
||||
{{{downloadPuK}}}
|
||||
</button>
|
||||
<button
|
||||
class="signupprivatekey btn btn-outline btn-accent text-white shadow-sm"
|
||||
>
|
||||
{{{downloadPrK}}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
class="btncreateidentity btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="const authid=document.getElementById('{{id}}');apx.apxauth.registerIdentity(
|
||||
'{{id}}',
|
||||
authid.querySelector('.signuptrustedcheck').checked
|
||||
)"
|
||||
>{{{saveidentity}}}
|
||||
</button>
|
||||
<button
|
||||
class="signupbtnreload hidden btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||
onclick="location.reload(true)"
|
||||
>
|
||||
{{{nextpage}}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user