05/18 Enhanced codes and functionalities 12

This commit is contained in:
2026-05-18 22:05:01 -04:00
parent 6177b72ef8
commit ec1f1fe374
2 changed files with 78 additions and 12 deletions
+75 -10
View File
@@ -51,6 +51,45 @@ const Vault = (() => {
document.addEventListener(e, handler, { passive: true }),
);
_resetWebIdleTimer();
// Lock when the tab is hidden for longer than the idle timeout.
// This catches screen-lock, minimize, and long tab switches without
// requiring the user to wait for the inactivity timer to fire after
// returning to the tab.
let _hiddenAt = null;
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
_hiddenAt = Date.now();
// Pause the inactivity timer while hidden — user activity is impossible.
if (_webIdleTimer) {
clearTimeout(_webIdleTimer);
_webIdleTimer = null;
}
} else {
// Tab became visible again.
const mins = _getWebIdleMinutes();
if (mins > 0 && _hiddenAt !== null) {
const hiddenMs = Date.now() - _hiddenAt;
if (hiddenMs >= mins * 60_000 && VaultSession.getKey()) {
console.log(
"[PassKeeper] Locking — tab was hidden for",
Math.round(hiddenMs / 1000),
"s (idle timeout:",
mins,
"min)",
);
VaultSession.clear();
showUnlockOverlay();
showToast("Vault locked due to inactivity.", "info");
_hiddenAt = null;
return;
}
}
_hiddenAt = null;
// Resume the inactivity timer now that the user is back.
_resetWebIdleTimer();
}
});
}
let _activeFilter = null;
let _sortOrder = "name-asc";
@@ -863,16 +902,18 @@ const Vault = (() => {
const cutoff = Date.now() - 180 * 86400000;
// "Old" only penalises passwords that are ALSO weak or reused.
// A strong, unique password that hasn't changed in 200 days is fine —
// penalising it discourages good password hygiene.
// Uses plain.password_changed_at when available (set on create/edit),
// falling back to created_at for items saved before this feature.
const weakOrReusedIds = new Set([
...weak.map((i) => i.id),
...reused.map((i) => i.id),
]);
const old = pwItems.filter(
(i) =>
new Date(i.created_at).getTime() < cutoff && weakOrReusedIds.has(i.id),
);
const old = pwItems.filter((i) => {
const ageRef = i.plain?.password_changed_at
? new Date(i.plain.password_changed_at).getTime()
: new Date(i.created_at).getTime();
return ageRef < cutoff && weakOrReusedIds.has(i.id);
});
const total = pwItems.length;
const score = Math.max(
@@ -3495,10 +3536,34 @@ const Vault = (() => {
mode === "add"
? getVal("field-type")
: form.dataset.itemType || "password";
const { enc_data, iv } = await Crypto.encryptItem(
vaultKey,
buildPlainData(itemType),
);
const plainData = buildPlainData(itemType);
// Track when the password was last changed — stored inside the encrypted
// blob so the server never sees it. Used by the security dashboard to
// accurately flag old passwords vs. old items that had their password changed.
if (itemType === "password") {
if (mode === "add") {
// New item — set password_changed_at to now.
plainData.password_changed_at = new Date().toISOString();
} else {
// Edit — only update if the password field actually changed.
const existingItem = _items.find((i) => i.id === parseInt(itemId));
const existingPassword = existingItem?.plain?.password ?? null;
const existingChangedAt = existingItem?.plain?.password_changed_at ?? null;
if (plainData.password !== existingPassword) {
// Password changed — record now.
plainData.password_changed_at = new Date().toISOString();
} else if (existingChangedAt) {
// Password unchanged — preserve the existing timestamp.
plainData.password_changed_at = existingChangedAt;
}
// If no existing timestamp and password unchanged, leave it absent
// — the security dashboard will fall back to created_at.
}
}
const { enc_data, iv } = await Crypto.encryptItem(vaultKey, plainData);
// Encrypt the item name client-side so it is never stored in plaintext.
const { enc_name, iv_name } = await Crypto.encryptName(vaultKey, name);
const folderVal = getVal("field-folder");
+2 -1
View File
@@ -1273,6 +1273,7 @@ async function saveCredential(data) {
username: data.username || "",
password: data.password || "",
notes: "",
password_changed_at: new Date().toISOString(),
};
const name = $("save-name").value.trim() || data.siteName || "Untitled";
// Read selected folder — empty string means no folder (null).
@@ -1577,7 +1578,7 @@ async function addItemToVault() {
btn.textContent = "Saving…";
try {
const plain = { url, username, password, notes };
const plain = { url, username, password, notes, password_changed_at: new Date().toISOString() };
const { enc_data, iv } = await ExtCrypto.encryptItem(_vaultKey, plain);
const { enc_name, iv_name } = await ExtCrypto.encryptName(_vaultKey, name);
const res = await apiFetch("/api/vault", {