05/18 Enhanced codes and functionalities 9 (extension)

This commit is contained in:
2026-05-18 21:13:43 -04:00
parent e8b386b2c8
commit 304881a6c2
3 changed files with 212 additions and 5 deletions
+107 -1
View File
@@ -617,6 +617,11 @@ async function fetchAndDecryptVault() {
vault_items: itemsForContentScript,
})
.catch(() => {});
// Run health checks in the background — sync metrics first, then HIBP.
// Results are sent to the background SW via HEALTH_UPDATE so the toolbar
// badge reflects the vault health even when the popup is closed.
_runPopupHealthCheck();
} catch (err) {
console.error("fetchAndDecryptVault:", err);
} finally {
@@ -624,6 +629,107 @@ async function fetchAndDecryptVault() {
}
}
// ── Vault health check ────────────────────────────────────────────────────────
/**
* Compute weak/reused counts synchronously, then run HIBP in parallel.
* Sends HEALTH_UPDATE to the background service worker so the toolbar
* badge is updated even after the popup closes.
* Called once per fetchAndDecryptVault() cycle.
*/
async function _runPopupHealthCheck() {
try {
const pwItems = _items.filter(
(i) => i.item_type === "password" && i.plain?.password,
);
if (!pwItems.length) {
chrome.runtime
.sendMessage({ type: "HEALTH_UPDATE", breached: 0, weak: 0, reused: 0 })
.catch(() => {});
return;
}
// Synchronous metrics.
const weak = pwItems.filter((i) => {
const p = i.plain.password;
if (p.length < 10) return true;
return (
[/[A-Z]/, /[a-z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(p))
.length < 2
);
});
const passCounts = {};
pwItems.forEach((i) => {
const p = i.plain.password;
(passCounts[p] = passCounts[p] || []).push(i);
});
const reused = Object.values(passCounts)
.filter((a) => a.length > 1)
.flat();
// Send sync results immediately so background badge updates quickly.
chrome.runtime
.sendMessage({
type: "HEALTH_UPDATE",
breached: 0,
weak: weak.length,
reused: reused.length,
})
.catch(() => {});
// HIBP — k-anonymity, parallel.
const hibpResults = await Promise.all(
pwItems.map(async (item) => ({
item,
count: await _popupCheckHibp(item.plain.password),
})),
);
const breachedCount = hibpResults.filter((r) => r.count > 0).length;
// Send final results with breach count.
chrome.runtime
.sendMessage({
type: "HEALTH_UPDATE",
breached: breachedCount,
weak: weak.length,
reused: reused.length,
})
.catch(() => {});
} catch (err) {
console.error("[PassKeeper] Popup health check failed:", err);
}
}
/**
* HIBP k-anonymity check — identical to the web app's checkHibp().
* Only the first 5 hex chars of SHA-1(password) are sent; the full
* hash never leaves the browser.
*/
async function _popupCheckHibp(password) {
try {
const enc = new TextEncoder().encode(password);
const hashBuf = await crypto.subtle.digest("SHA-1", enc);
const hashHex = Array.from(new Uint8Array(hashBuf))
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
const prefix = hashHex.slice(0, 5);
const suffix = hashHex.slice(5);
const res = await fetch(
`https://api.pwnedpasswords.com/range/${prefix}`,
{ headers: { "Add-Padding": "true" } },
);
if (!res.ok) return 0;
const text = await res.text();
const line = text.split("\n").find((l) => l.startsWith(suffix));
if (!line) return 0;
return parseInt(line.split(":")[1], 10) || 0;
} catch {
return 0;
}
}
// ── TOTP engine (RFC 6238) — pure Web Crypto, no library ─────────────────────
function _extractTotpSecret(uri) {
@@ -1655,4 +1761,4 @@ async function init() {
initTabs();
}
document.addEventListener("DOMContentLoaded", init);
document.addEventListener("DOMContentLoaded", init);