diff --git a/locks.json b/locks.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/locks.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/ui/app.js b/ui/app.js
index a4b7f5a..e9fafb0 100644
--- a/ui/app.js
+++ b/ui/app.js
@@ -77,24 +77,43 @@ let state = {
reliabilityDiagnostics: null,
};
-const unlockCredentials = new Map();
+// ─── Cryptography & Credentials ─────────────────────────────
+
+async function hashPassword(plainText) {
+ if (!plainText) return '';
+ const encoder = new TextEncoder();
+ const data = encoder.encode(plainText);
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
+}
+
+const CredentialStore = (() => {
+ const store = new Map();
+ return {
+ set: (path, passHash) => store.set(path, passHash),
+ get: (path) => store.get(path) || '',
+ delete: (path) => store.delete(path),
+ clear: () => store.clear()
+ };
+})();
function isScriptUnlocked(relPath) {
return !!state.unlockedScripts[relPath];
}
function getUnlockPassword(relPath) {
- return unlockCredentials.get(relPath) || '';
+ return CredentialStore.get(relPath);
}
-function markScriptUnlocked(relPath, password) {
+function markScriptUnlocked(relPath, passHash) {
state.unlockedScripts[relPath] = true;
- if (password) unlockCredentials.set(relPath, password);
+ if (passHash) CredentialStore.set(relPath, passHash);
}
function clearScriptUnlock(relPath) {
delete state.unlockedScripts[relPath];
- unlockCredentials.delete(relPath);
+ CredentialStore.delete(relPath);
}
function serializeUnlockedScripts() {
@@ -110,7 +129,7 @@ function restoreUnlockedScripts(raw = {}) {
for (const [path, val] of Object.entries(raw)) {
if (val) state.unlockedScripts[path] = true;
}
- unlockCredentials.clear();
+ CredentialStore.clear();
}
const RUN_BUTTON_IDLE_HTML = `Run`;
@@ -3041,11 +3060,12 @@ async function selectScript(relPath) {
unlockBtn.parentNode.replaceChild(newUnlockBtn, unlockBtn);
const unlockAction = async () => {
- const content = await fetchScriptContent(relPath, passInput.value);
+ const passHash = await hashPassword(passInput.value);
+ const content = await fetchScriptContent(relPath, passHash);
if (content.locked) {
notify('Incorrect password.', 'error');
} else {
- markScriptUnlocked(relPath, passInput.value);
+ markScriptUnlocked(relPath, passHash);
passInput.value = '';
selectScript(relPath);
}
@@ -3757,14 +3777,14 @@ function bindEvents() {
}
}
- let oldPass = '', newPass = '';
+ let oldPassPlain = '', newPassPlain = '';
if (isLocked) {
- oldPass = document.getElementById('lock-current-pass').value;
- newPass = ''; // meaning remove lock
+ oldPassPlain = document.getElementById('lock-current-pass').value;
+ newPassPlain = ''; // meaning remove lock
} else {
- oldPass = '';
- newPass = document.getElementById('lock-new-pass').value;
- if (!newPass) {
+ oldPassPlain = '';
+ newPassPlain = document.getElementById('lock-new-pass').value;
+ if (!newPassPlain) {
return notify('Password cannot be empty when setting a lock.', 'warning');
}
}