Privatri works locally.

This commit is contained in:
devpotatoes
2025-08-28 09:19:28 +02:00
parent 93842f84a7
commit d218c3b1d0
3 changed files with 134 additions and 96 deletions

View File

@@ -3,72 +3,74 @@ var apx = apx || {};
apx.crypto = apx.crypto || {}; apx.crypto = apx.crypto || {};
apx.crypto.genKey = async (uuid) => { apx.crypto.genKey = async (uuid) => {
return await openpgp.generateKey({ return await openpgp.generateKey({
type: "ecc", type: "ecc",
curve: "curve25519", curve: "curve25519",
userIDs: [ userIDs: [
{ {
alias: uuid, alias: uuid,
}, },
], ],
passphrase: "", passphrase: "",
format: "armored", format: "armored",
}); });
}; };
apx.crypto.encryptMessage = async (message, publicKey) => { apx.crypto.encryptMessage = async (message, publicKey) => {
publicKey = await openpgp.readKey({ publicKey = await openpgp.readKey({
armoredKey: publicKey, armoredKey: publicKey,
}); });
return await openpgp.encrypt({ return await openpgp.encrypt({
message: await openpgp.createMessage({ message: await openpgp.createMessage({
text: message, text: message,
}), }),
encryptionKeys: publicKey, encryptionKeys: publicKey,
}); });
}; };
apx.crypto.decryptMessage = async (encryptedMessage, privateKey) => { apx.crypto.decryptMessage = async (encryptedMessage, privateKey) => {
privateKey = await openpgp.readPrivateKey({ privateKey = await openpgp.readPrivateKey({
armoredKey: privateKey, armoredKey: privateKey,
}); });
const message = await openpgp.readMessage({ const message = await openpgp.readMessage({
armoredMessage: encryptedMessage, armoredMessage: encryptedMessage,
}); });
return await openpgp.decrypt({ return await openpgp.decrypt({
message, message,
decryptionKeys: privateKey, decryptionKeys: privateKey,
}); });
}; };
apx.crypto.isSignedby = async ( apx.crypto.isSignedby = async (
alias, alias,
publicKey, publicKey,
detachedSignature, detachedSignature,
message message
) => { ) => {
const publickey = await openpgp.readKey({ armoredKey: publicKey }); const publickey = await openpgp.readKey({ armoredKey: publicKey });
const msg = await openpgp.createMessage({ text: message }); const msg = await openpgp.createMessage({ text: message });
const signature = await openpgp.readSignature({ const signature = await openpgp.readSignature({
armoredSignature: atob(detachedSignature), // parse detached signature armoredSignature: atob(detachedSignature), // parse detached signature
}); });
const verificationResult = await openpgp.verify({ const verificationResult = await openpgp.verify({
msg, // Message object msg, // Message object
signature, signature,
verificationKeys: publickey, verificationKeys: publickey,
}); });
const { verified, keyID } = verificationResult.signatures[0]; const { verified, keyID } = verificationResult.signatures[0];
try { try {
await verified; // throws on invalid signature await verified; // throws on invalid signature
//console.log("Signed by key id " + keyID.toHex()); //console.log("Signed by key id " + keyID.toHex());
return KeyId.toHex().alias == alias; return KeyId.toHex().alias == alias;
} catch (e) { } catch (e) {
console.log("Signature could not be verified: " + e.message); console.log("Signature could not be verified: " + e.message);
return false; return false;
} }
}; };
apx.crypto.sign = async (message, privateKey) => { apx.crypto.sign = async (message, privateKey) => {
privateKey = await openpgp.readPrivateKey( privateKey = await openpgp.readPrivateKey(
{ {
@@ -118,4 +120,19 @@ apx.crypto.verifySignature = async (message, signature, publicKey) => {
}; };
}; };
export default apx; apx.crypto.genUUID = () => {
const uuidTemplate = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
return uuidTemplate.replace(/[xy]/g, (char) => {
const random = Math.random() * 16 | 0;
let value;
if (char === "x") {
value = random;
} else {
value = (random & 0x3) | 0x8;
};
return value.toString(16);
});
};

View File

@@ -14,8 +14,10 @@ apx.privatri.loadwco = async (id, dataObj = {}, ctx = null) => {
ctx ctx
)}` )}`
); );
const template = JSON.parse(localStorage.getItem("admin")).tpl[`privatri${id}`]; const templatesObj = JSON.parse(localStorage.getItem("admin")).tpl;
const template = Object.entries(templatesObj).find(([key]) => key.toLowerCase() === `privatri${id}`.toLocaleLowerCase())?.[1];
return Mustache.render(template, dataObj); return Mustache.render(template, dataObj);
}; };
@@ -42,11 +44,11 @@ apx.privatri.templates.scripts = {
document.querySelector("#createThreadBtn").addEventListener("click", async () => { document.querySelector("#createThreadBtn").addEventListener("click", async () => {
const { publicKey, privateKey } = await apx.crypto.genKey(); const { publicKey, privateKey } = await apx.crypto.genKey();
const messageObj = await (async (publicKey) => { const messageObj = await (async (publicKey) => {
const uuid = crypto.randomUUID(); const uuid = apx.crypto.genUUID();
const timestamp = Date.now(); const timestamp = Date.now();
const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("apx")).data.headers.xalias, publicKey); const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("admin")).headers.xalias, publicKey);
return { return {
privatriid: `${uuid}_${timestamp}`, privatriid: `${uuid}_${timestamp}`,
@@ -153,7 +155,7 @@ apx.privatri.templates.scripts = {
document.querySelectorAll("button.removeAliasBtn").forEach(btn => { document.querySelectorAll("button.removeAliasBtn").forEach(btn => {
btn.addEventListener("click", async () => { btn.addEventListener("click", async () => {
if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) { if (JSON.parse(localStorage.getItem("admin")).headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
if (btn.getAttribute("data-alias") !== ownerAlias) { if (btn.getAttribute("data-alias") !== ownerAlias) {
const alias = btn.getAttribute("data-alias"); const alias = btn.getAttribute("data-alias");
aliasesArray = aliasesArray.filter(a => a !== alias); aliasesArray = aliasesArray.filter(a => a !== alias);
@@ -222,7 +224,7 @@ apx.privatri.templates.scripts = {
}); });
applyModificationsBtnEl.addEventListener("click", async () => { applyModificationsBtnEl.addEventListener("click", async () => {
if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) { if (JSON.parse(localStorage.getItem("admin")).headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) {
messageObj.title = await apx.crypto.encryptMessage(threadNameInputEl.value, privateKey); messageObj.title = await apx.crypto.encryptMessage(threadNameInputEl.value, privateKey);
messageObj.dt_autodestruction = (() => { messageObj.dt_autodestruction = (() => {
const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200")); const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200"));
@@ -253,9 +255,26 @@ async function getOldestPrivatriids(dbName, storeName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, 1); const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("threads")) {
db.createObjectStore("threads", { keyPath: "uuid" });
};
if (!db.objectStoreNames.contains("messages")) {
db.createObjectStore("messages", { keyPath: "privatriid" });
};
};
request.onsuccess = (event) => { request.onsuccess = (event) => {
const db = event.target.result; const db = event.target.result;
if (!db.objectStoreNames.contains(storeName)) {
resolve([]);
return;
};
const transaction = db.transaction(storeName, "readonly"); const transaction = db.transaction(storeName, "readonly");
const store = transaction.objectStore(storeName); const store = transaction.objectStore(storeName);
const cursorRequest = store.openCursor(); const cursorRequest = store.openCursor();
@@ -288,10 +307,18 @@ async function getOldestPrivatriids(dbName, storeName) {
}; };
apx.ready(async () => { apx.ready(async () => {
await (async () => { document.querySelector("body").innerHTML = await apx.privatri.loadwco("main", JSON.parse(localStorage.getItem("admin")).tpldata.privatri_main_privatri);
console.log("ok")
document.querySelector("body").innerHTML = await apx.privatri.loadwco("main");
const bodyEl = document.querySelector("body");
const searchInputEl = document.querySelector("#threadSearchBar");
const threadFilterOptionsElArray = document.querySelector("#threadFilterOptions").querySelectorAll("li");
const threadsContainerEl = document.querySelector("#threadsContainer");
const threadPageEl = document.querySelector("#threadPage");
const messageInputEl = document.getElementById("messageInput");
const messagesContainerEl = document.querySelector("#messagesContainer");
const attachmentsInputEl = document.querySelector("#attachmentsInput");
await (async () => {
const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now(); const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now();
await apx.privatri.syncronizeBackend(apx.data.headers.xalias, lastConnection); await apx.privatri.syncronizeBackend(apx.data.headers.xalias, lastConnection);
@@ -299,7 +326,7 @@ apx.ready(async () => {
const privatriidArray = await getOldestPrivatriids("privatri", "messages"); const privatriidArray = await getOldestPrivatriids("privatri", "messages");
const thread = async (name, uuid) => { const thread = async (name, uuid) => {
return await apx.privatri.loadwco("threadAliasList", { uuid, name }); return await apx.privatri.loadwco("thread", { uuid, name });
}; };
for (const privatriid of privatriidArray) { for (const privatriid of privatriidArray) {
@@ -386,11 +413,13 @@ apx.ready(async () => {
}; };
}); });
}); });
threadsContainerEl.children[0]?.click();
document.querySelectorAll("a").forEach(link => { document.querySelectorAll("a").forEach(link => {
link.addEventListener("click", async (event) => { link.addEventListener("click", async (event) => {
event.preventDefault(); event.preventDefault();
window.history.replaceState({}, document.title, window.location.pathname); window.history.replaceState({}, document.title, window.location.pathname);
const templateName = link.getAttribute("data-template"); const templateName = link.getAttribute("data-template");
@@ -399,22 +428,13 @@ apx.ready(async () => {
window.history.pushState({}, "", `${window.location.pathname}?uuid=${threadPageEl.getAttribute("data-uuid")}`); window.history.pushState({}, "", `${window.location.pathname}?uuid=${threadPageEl.getAttribute("data-uuid")}`);
}; };
bodyEl.firstElementChild.innerHTML = await apx.privatri.loadwco(templateName); bodyEl.innerHTML = await apx.privatri.loadwco(templateName);
await apx.privatri.templates.scripts[templateName](); await apx.privatri.templates.scripts[templateName]();
}); });
}); });
})(); })();
const bodyEl = document.querySelector("body");
const searchInputEl = document.querySelector("#threadSearchBar");
const threadFilterOptionsElArray = document.querySelector("#threadFilterOptions").querySelectorAll("li");
const threadsContainerEl = document.querySelector("#threadsContainer");
const threadPageEl = document.querySelector("#threadPage");
const messageInputEl = document.getElementById("messageInput");
const messagesContainerEl = document.querySelector("#messagesContainer");
const attachmentsInputEl = document.querySelector("#attachmentsInput");
function formatDate(timestamp) { function formatDate(timestamp) {
const date = new Date(timestamp); const date = new Date(timestamp);
const day = String(date.getDate()).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0");
@@ -486,7 +506,7 @@ apx.ready(async () => {
if (message !== "") { if (message !== "") {
const messageObj = await (async (publicKey) => { const messageObj = await (async (publicKey) => {
const timestamp = Date.now(); const timestamp = Date.now();
const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("apx")).data.headers.xalias, publicKey); const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("admin")).headers.xalias, publicKey);
return { return {
privatriid: `${threadPageEl.getAttribute("data-uuid")}_${timestamp}`, privatriid: `${threadPageEl.getAttribute("data-uuid")}_${timestamp}`,
@@ -538,28 +558,28 @@ apx.ready(async () => {
const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey; const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey;
const signatureMessage = `${JSON.parse(localStorage.getItem("apx")).data.headers.xalias}_${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`; const signatureMessage = `${JSON.parse(localStorage.getItem("admin")).headers.xalias}_${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`;
const signature = await apx.crypto.sign(signatureMessage, privateKey); const signature = await apx.crypto.sign(signatureMessage, privateKey);
let verified = false; let verified = false;
try { // try {
verified = await fetch("", { // verified = await fetch("", {
method: "GET", // method: "GET",
body: JSON.stringify({ // body: JSON.stringify({
message: signatureMessage, // message: signatureMessage,
signature: signature, // signature: signature,
uuid: threadPageEl.getAttribute("data-uuid"), // uuid: threadPageEl.getAttribute("data-uuid"),
timestamp: messageEl.getAttribute("data-timestamp"), // timestamp: messageEl.getAttribute("data-timestamp"),
}) // })
}); // });
} catch (error) { // } catch (error) {
console.error("Error while verifying signature:", error); // console.error("Error while verifying signature:", error);
}; // };
const authorAlias = await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, privateKey).data; const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, privateKey)).data;
if ((JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) && (verified === true)) { if ((JSON.parse(localStorage.getItem("admin")).headers.xalias === authorAlias) && (verified === true)) {
await apx.indexedDB.del("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`); await apx.indexedDB.del("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`);
messageEl.remove(); messageEl.remove();
@@ -577,7 +597,7 @@ apx.ready(async () => {
const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey)).data; const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey)).data;
if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) { if (JSON.parse(localStorage.getItem("admin")).headers.xalias === authorAlias) {
const messageValue = messageEl.querySelector("div.message").innerText; const messageValue = messageEl.querySelector("div.message").innerText;
const attachmentsArray = (() => { const attachmentsArray = (() => {

View File

@@ -43,8 +43,9 @@
<script src="/apxtrilib/mustache/mustache.min.js"></script> <script src="/apxtrilib/mustache/mustache.min.js"></script>
<script src="/apxtrilib/qr-code-styling/lib/qr-code-styling.js"></script> <script src="/apxtrilib/qr-code-styling/lib/qr-code-styling.js"></script>
<script src="/apxtrilib/checkjson.js"></script> <script src="/apxtrilib/checkjson.js"></script>
<script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy"></script> <script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy"></script>
<script src="/api/apxtri/wwws/getwco/crypto.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy"></script>
<script src="/api/apxtri/wwws/getwco/privatri.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy&tagid=main"></script> <script src="/api/apxtri/wwws/getwco/privatri.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy&tagid=main"></script>
</head> </head>
<body class="bg-base-100 flex items-center justify-center h-screen w-screen "> <body class="bg-base-100 flex items-center justify-center h-screen w-screen ">