04/26 New features, new for Firefox

This commit is contained in:
2026-04-26 10:38:05 -04:00
parent d59fbaa9bd
commit 80cf8eb599
6 changed files with 398 additions and 8 deletions
+99 -3
View File
@@ -9,6 +9,41 @@ const Vault = (() => {
let _items = [];
let _folders = [];
let _currentView = "vault";
// ── Web-app inactivity / session timeout ──────────────────────────────────
// Mirrors the extension idle lock. Default 15 min; user can change in
// Account Settings. Zero means never. Stored in localStorage so it persists
// across page reloads without a server round-trip.
const WEB_IDLE_KEY = 'web_idle_minutes';
const WEB_IDLE_DEFAULT = 15; // minutes; 0 = never
let _webIdleTimer = null;
function _getWebIdleMinutes() {
const v = parseInt(localStorage.getItem(WEB_IDLE_KEY), 10);
return isNaN(v) ? WEB_IDLE_DEFAULT : v;
}
function _resetWebIdleTimer() {
if (_webIdleTimer) clearTimeout(_webIdleTimer);
const mins = _getWebIdleMinutes();
if (mins === 0) return;
_webIdleTimer = setTimeout(() => {
// Only lock if the vault is currently unlocked.
if (!VaultSession.getKey()) return;
console.log('[PassKeeper] Web inactivity lock after', mins, 'min');
VaultSession.clear();
showUnlockOverlay();
showToast('Vault locked due to inactivity.', 'info');
}, mins * 60_000);
}
function _startWebIdleTracking() {
const events = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll'];
const handler = () => _resetWebIdleTimer();
events.forEach((e) => document.addEventListener(e, handler, { passive: true }));
_resetWebIdleTimer();
}
let _activeFilter = null;
let _sortOrder = "name-asc";
@@ -197,7 +232,7 @@ const Vault = (() => {
// ── View switching ────────────────────────────────────────────────────────
function switchView(view) {
function switchView(view, { pushState = true } = {}) {
_currentView = view;
["vault", "security", "sharing", "emergency", "import-export"].forEach((v) => {
document
@@ -215,6 +250,12 @@ const Vault = (() => {
.forEach((el) => el.classList.remove("active"));
}
// Push to browser history so the back button navigates between views.
if (pushState) {
const hash = view === "vault" ? "" : `#${view}`;
history.pushState({ view }, "", hash || window.location.pathname);
}
if (view === "security") renderSecurityDashboard();
if (view === "sharing") loadSharingView();
if (view === "emergency") loadEmergencyView();
@@ -643,6 +684,19 @@ const Vault = (() => {
);
makeSection("Old Passwords", "🕐", old, "Weak or reused passwords not changed in over 180 days.");
// ── Missing 2FA warning ──────────────────────────────────────────────────
// Flag password items that have a URL but no TOTP URI saved.
// These accounts likely support 2FA but the user hasn't stored it.
const noTotp = pwItems.filter((i) =>
i.plain?.url && !extractTotpSecret(i.plain?.totp_uri)
);
makeSection(
'No 2FA Saved',
'🔓',
noTotp,
'These accounts may support two-factor authentication but have no TOTP code stored.',
);
// ── HaveIBeenPwned breach check ──────────────────────────────────────────
// Run after the synchronous sections are rendered so the UI is immediately
// useful. The HIBP API is queried in parallel for all passwords.
@@ -1798,6 +1852,9 @@ const Vault = (() => {
async function openSettingsModal() {
document.getElementById("settings-modal").classList.add("open");
// Populate web-app session timeout select with saved value.
const idleSel = document.getElementById("web-idle-select");
if (idleSel) idleSel.value = String(_getWebIdleMinutes());
// Reset change password fields
["cp-current", "cp-new", "cp-confirm"].forEach((id) => {
const el = document.getElementById(id);
@@ -2717,10 +2774,21 @@ const Vault = (() => {
return 30 - (Math.floor(Date.now() / 1000) % 30);
}
// Track the clipboard clear timer so multiple rapid copies don't stack.
let _clipboardClearTimer = null;
function copyToClipboard(text, msg) {
navigator.clipboard
.writeText(text)
.then(() => showToast(msg))
.then(() => {
showToast(msg);
// Auto-clear clipboard after 30 seconds — industry-standard hygiene.
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
_clipboardClearTimer = setTimeout(() => {
navigator.clipboard.writeText('').catch(() => {});
_clipboardClearTimer = null;
}, 30_000);
})
.catch(() => {});
}
@@ -2979,6 +3047,16 @@ const Vault = (() => {
document
.getElementById("btn-close-settings")
?.addEventListener("click", () => closeModal("settings-modal"));
// Web-app session timeout setting.
document.getElementById("web-idle-select")?.addEventListener("change", (e) => {
const mins = parseInt(e.target.value, 10);
localStorage.setItem(WEB_IDLE_KEY, mins);
_resetWebIdleTimer();
const label = mins === 0 ? 'never' : mins + ' min';
showToast('Auto-lock set to ' + label + '.');
console.log('[PassKeeper] Web idle timeout set to', label);
});
document
.getElementById("settings-modal")
?.addEventListener("click", (e) => {
@@ -3161,10 +3239,28 @@ const Vault = (() => {
document.getElementById("btn-add-item")?.click();
});
// ── Browser history / back-button support ─────────────────────────────────
const _hashToView = (hash) => {
const v = (hash || '').replace('#', '').trim();
const valid = ['vault', 'security', 'sharing', 'emergency', 'import-export'];
return valid.includes(v) ? v : 'vault';
};
const _initialView = _hashToView(window.location.hash);
history.replaceState({ view: _initialView }, '', window.location.href);
window.addEventListener('popstate', (e) => {
const view = e.state?.view || _hashToView(window.location.hash);
switchView(view, { pushState: false });
});
// Start web-app inactivity tracking.
_startWebIdleTracking();
if (!VaultSession.getKey()) {
showUnlockOverlay();
} else {
loadVault();
// Restore view from hash after vault loads (avoids rendering before decrypt).
if (_initialView !== 'vault') switchView(_initialView, { pushState: false });
}
}
@@ -3268,4 +3364,4 @@ const Vault = (() => {
}
})();
document.addEventListener("DOMContentLoaded", Vault.init);
document.addEventListener("DOMContentLoaded", Vault.init);
+19 -1
View File
@@ -853,6 +853,24 @@
</div>
<!-- Danger Zone -->
<div class="settings-section">
<h4 class="settings-section-title">⏱️ Auto-Lock</h4>
<p class="settings-desc">
Automatically lock the vault after a period of inactivity.
</p>
<div class="form-group">
<label for="web-idle-select">Lock after</label>
<select id="web-idle-select" class="form-control">
<option value="0">Never</option>
<option value="5">5 minutes</option>
<option value="10">10 minutes</option>
<option value="15" selected>15 minutes</option>
<option value="30">30 minutes</option>
<option value="60">1 hour</option>
</select>
</div>
</div>
<div class="settings-section settings-section-danger">
<h4 class="settings-section-title">⚠️ Danger Zone</h4>
<p class="settings-desc">
@@ -1005,4 +1023,4 @@
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
<script src="{{ url_for('static', filename='js/sharing.js') }}"></script>
<script src="{{ url_for('static', filename='js/vault.js') }}"></script>
{% endblock %}
{% endblock %}