First working prototype

This commit is contained in:
RochesterX
2026-03-09 16:31:40 -04:00
parent 66b7eec952
commit 2c896e711a
10 changed files with 534 additions and 559 deletions

View File

@@ -6,11 +6,28 @@ const error = document.querySelector(".error");
const errorMessage = document.querySelector(".error p");
const success = document.querySelector(".success");
openKeyDB();
if (loginForm) loginForm.onsubmit = async e => {
e.preventDefault();
let saltObject = await postData("/getSalt", {
username: document.getElementById("logUser").value
});
if (saltObject.message == "User not found") {
errorMessage.innerHTML = saltObject.message;
error.style.display = "flex";
return;
}
const { authenticationKeyString, encryptionKeyObject } = await generateKeys(document.getElementById("logPass").value, hexToUint8Array(saltObject.salt));
saveEncryptionKey(encryptionKeyObject);
let resultObject = await postData("/login", {
username: document.getElementById("logUser").value,
password: document.getElementById("logPass").value
password: authenticationKeyString
});
if (resultObject.message.includes("success")) {
error.style.display = "none";
@@ -24,3 +41,102 @@ if (loginForm) loginForm.onsubmit = async e => {
localStorage.setItem("token", resultObject.token);
}
};
async function generateKeys(password, salt) {
const encoder = new TextEncoder();
const base = await window.crypto.subtle.importKey(
"raw",
encoder.encode(password),
"PBKDF2",
false,
["deriveBits", "deriveKey"]
);
const authSalt = new Uint8Array([...salt, ...encoder.encode("auth")]);
const authKeyBits = await window.crypto.subtle.deriveBits(
{
name: "PBKDF2",
salt: authSalt,
iterations: 100000,
hash: "SHA-256"
},
base,
256
);
const encryptionSalt = new Uint8Array([...salt, ...encoder.encode("enc")]);
const encryptionKey = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: encryptionSalt,
iterations: 100000,
hash: "SHA-256"
},
base,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
console.log(convertBufferToHex(authKeyBits));
console.log(encryptionKey);
return {
authenticationKeyString: convertBufferToHex(authKeyBits),
encryptionKeyObject: encryptionKey
};
}
function openKeyDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("PasswordManagerVault", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("cryptoKeys")) {
db.createObjectStore("cryptoKeys");
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function saveEncryptionKey(cryptoKeyObject) {
const db = await openKeyDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(["cryptoKeys"], "readwrite");
const store = transaction.objectStore("cryptoKeys");
// We store the object under the label "masterEncryptionKey"
const request = store.put(cryptoKeyObject, "masterEncryptionKey");
request.onsuccess = () => resolve(true);
request.onerror = () => reject(request.error);
});
}
function convertBufferToHex(buffer) {
const bytes = new Uint8Array(buffer);
return Array.from(bytes)
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
}
function hexToUint8Array(hexString) {
if (hexString.length % 2 !== 0) {
throw new Error("Invalid hex string");
}
const array = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
array[i / 2] = parseInt(hexString.substring(i, i + 2), 16);
}
return array;
}