From 15a05da664f2f8a47c4ae97cdfac4978c4e5ba78 Mon Sep 17 00:00:00 2001 From: blacklizardcode Date: Sun, 19 Apr 2026 17:10:35 +0200 Subject: [PATCH 1/3] chore: implement encryption --- manifest.json | 2 +- popup/popup.html | 1 + popup/popup.js | 26 +++++++++++---- script/autologin.js | 22 +++++++++++-- script/sharedencryption.js | 67 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 script/sharedencryption.js diff --git a/manifest.json b/manifest.json index 1727d96..8da7938 100644 --- a/manifest.json +++ b/manifest.json @@ -11,7 +11,7 @@ "content_scripts": [ { "matches": ["*://*.magister.net/account/*"], - "js": ["script/autologin.js"], + "js": ["script/sharedencryption.js", "script/autologin.js"], "run_at": "document_idle" } ], diff --git a/popup/popup.html b/popup/popup.html index e0eed47..c0e9fc0 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -16,6 +16,7 @@

Magister Autologin

+ \ No newline at end of file diff --git a/popup/popup.js b/popup/popup.js index 7725681..afae0e5 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -1,18 +1,26 @@ +const keyPromise = getOrCreateCryptoKey(); + document.getElementById("saveBtn").addEventListener("click", async () => { + const key = await keyPromise; + const enabled = document.getElementById("enabled").checked; const username = document.getElementById("username").value; const password = document.getElementById("password").value; + const usernameEnc = await encryptString(key, username); + const passwordEnc = await encryptString(key, password); + await browser.storage.local.set({ "magister-autologin-enabled": enabled, - "magister-autologin-username": username, - "magister-autologin-password": password + "magister-autologin-username": usernameEnc, + "magister-autologin-password": passwordEnc }); }); document.addEventListener("DOMContentLoaded", async () => { + const key = await keyPromise; const enabledInput = document.getElementById("enabled"); const usernameInput = document.getElementById("username"); const passwordInput = document.getElementById("password"); @@ -27,11 +35,17 @@ document.addEventListener("DOMContentLoaded", async () => { enabledInput.checked = stored["magister-autologin-enabled"]; } - if (typeof stored["magister-autologin-username"] === "string") { - usernameInput.value = stored["magister-autologin-username"]; + const storedUsername = stored["magister-autologin-username"]; + if (storedUsername && typeof storedUsername === "object" && storedUsername.iv && storedUsername.data) { + usernameInput.value = await decryptString(key, storedUsername); + } else if (typeof storedUsername === "string") { + usernameInput.value = storedUsername; // fallback for old plain-text values } - if (typeof stored["magister-autologin-password"] === "string") { - passwordInput.value = stored["magister-autologin-password"]; + const storedPassword = stored["magister-autologin-password"]; + if (storedPassword && typeof storedPassword === "object" && storedPassword.iv && storedPassword.data) { + passwordInput.value = await decryptString(key, storedPassword); + } else if (typeof storedPassword === "string") { + passwordInput.value = storedPassword; // fallback for old plain-text values } }); diff --git a/script/autologin.js b/script/autologin.js index 9862bc8..e5eda7d 100644 --- a/script/autologin.js +++ b/script/autologin.js @@ -1,14 +1,32 @@ async function getCredentials() { + const key = await getOrCreateCryptoKey(); const data = await browser.storage.local.get([ "magister-autologin-enabled", "magister-autologin-username", "magister-autologin-password" ]); + const storedUsername = data["magister-autologin-username"]; + const storedPassword = data["magister-autologin-password"]; + + let username = ""; + if (storedUsername && typeof storedUsername === "object" && storedUsername.iv && storedUsername.data) { + username = await decryptString(key, storedUsername); + } else if (typeof storedUsername === "string") { + username = storedUsername; + } + + let password = ""; + if (storedPassword && typeof storedPassword === "object" && storedPassword.iv && storedPassword.data) { + password = await decryptString(key, storedPassword); + } else if (typeof storedPassword === "string") { + password = storedPassword; + } + return { enabled: data["magister-autologin-enabled"] || "", - username: data["magister-autologin-username"] || "", - password: data["magister-autologin-password"] || "" + username, + password }; } diff --git a/script/sharedencryption.js b/script/sharedencryption.js new file mode 100644 index 0000000..cd1203c --- /dev/null +++ b/script/sharedencryption.js @@ -0,0 +1,67 @@ +function arrayBufferToBase64(buffer) { + let binary = ''; + let bytes = new Uint8Array(buffer); + for (let b of bytes) binary += String.fromCharCode(b); + return btoa(binary); +} +function base64ToArrayBuffer(base64) { + let binary = atob(base64); + let bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes.buffer; +} + +async function getOrCreateCryptoKey() { + // Check if the key exists in storage + let data = await browser.storage.local.get("encryptionKey"); + let key; + if (!data.encryptionKey) { + // Generate a random AES key + key = await crypto.subtle.generateKey( + { name: "AES-GCM", length: 256 }, + true, + ["encrypt", "decrypt"] + ); + // Export key to base64 + const raw = await crypto.subtle.exportKey("raw", key); + const b64 = arrayBufferToBase64(raw); + await browser.storage.local.set({ encryptionKey: b64 }); + } else { + // Import the stored key + const raw = base64ToArrayBuffer(data.encryptionKey); + key = await crypto.subtle.importKey( + "raw", + raw, + { name: "AES-GCM" }, + false, + ["encrypt", "decrypt"] + ); + } + return key; +} + +async function encryptString(key, plaintext) { + const enc = new TextEncoder(); + const iv = crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + key, + enc.encode(plaintext) + ); + return { + iv: arrayBufferToBase64(iv.buffer), + data: arrayBufferToBase64(ciphertext), + }; +} + +async function decryptString(key, encryptedObj) { + const iv = base64ToArrayBuffer(encryptedObj.iv); + const data = base64ToArrayBuffer(encryptedObj.data); + const dec = new TextDecoder(); + const plaintext = await crypto.subtle.decrypt( + { name: "AES-GCM", iv: new Uint8Array(iv) }, + key, + data + ); + return dec.decode(plaintext); +} From 96c008215cc99457899cc6f9d2d50daa805f9ddd Mon Sep 17 00:00:00 2001 From: blacklizardcode Date: Sun, 19 Apr 2026 17:11:46 +0200 Subject: [PATCH 2/3] fix: `web-ext lint` now also functions on all branches --- .github/workflows/web-ext-lint.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/web-ext-lint.yaml b/.github/workflows/web-ext-lint.yaml index 52e9760..a2586c8 100644 --- a/.github/workflows/web-ext-lint.yaml +++ b/.github/workflows/web-ext-lint.yaml @@ -3,8 +3,6 @@ name: web-ext lint on: pull_request: push: - branches: - - main paths-ignore: - .gitignore - LICENCE From 109c01b256a32c07c0fa7d2ff6b9707a0bbb54d5 Mon Sep 17 00:00:00 2001 From: blacklizardcode Date: Sun, 19 Apr 2026 17:15:23 +0200 Subject: [PATCH 3/3] fix: version naming --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 8da7938..62bc5ed 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Magister-Autologin", - "version": "1.1.0", + "version": "1.2.0", "description": "Automatically logs into magister for you", "browser_action": {