feat(entries): encrypt template at rest, guided tour, import fixes, cleanup
Batched session work sharing app.js / index.html / Entries.pas, so it can't
split cleanly without interactive hunk staging.
- feat: encrypt `template` metadata at rest (template_enc/iv, added to
ENCRYPTED_META_FIELDS). withEncryptedMeta skips an absent template key so
partial re-ships (add-tag, move-to-folder) don't wipe it via LHasTemplate.
Cleartext column kept as migration fallback. +3 unit tests.
- feat: first-run guided tour ("How it works") — spotlight + bubble, no GIFs,
re-launchable from Settings, seen-flag in DPAPI prefs.
- fix(import): preserve original created_at on restore (was stamped to import
time); restore entry icons on overwrite (PUT ignores icon_b64).
- fix(settings): correct clipboard-privacy copy (already excluded from Win+V);
PIN text 4-6 -> 4-12; reorder Set-PIN above unlock-method; move tray/startup
toggles to General; dedicated backup-password button + warning status; tab icons.
- chore: remove dead legacy monolith (app-legacy.js, index-legacy.html,
style-legacy.css) + unused passkeyBtn stub.
- docs: full-source review (CODE_AUDIT 6b), template + favorite/pinned notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1493,7 +1493,7 @@ async function decryptEntryMeta(list) {
|
||||
// (blanked on write) + ciphertext `f_enc`/`f_iv`. Search/sort/render all run
|
||||
// client-side on the decrypted in-memory value, so encrypting these is
|
||||
// transparent. `folder` stays cleartext (server folder-reassign query).
|
||||
const ENCRYPTED_META_FIELDS = ['username', 'site', 'title', 'tags'];
|
||||
const ENCRYPTED_META_FIELDS = ['username', 'site', 'title', 'tags', 'template'];
|
||||
|
||||
// Choke point for the write path: take an entry body object whose metadata
|
||||
// fields hold PLAINTEXT, encrypt each into <f>_enc/<f>_iv, and blank the
|
||||
@@ -1503,6 +1503,12 @@ const ENCRYPTED_META_FIELDS = ['username', 'site', 'title', 'tags'];
|
||||
async function withEncryptedMeta(obj) {
|
||||
if (!obj) return obj;
|
||||
for (const f of ENCRYPTED_META_FIELDS) {
|
||||
// Partial re-ships (add-tag, move-to-folder, batch ops) omit `template`
|
||||
// on purpose — the server preserves it when the key is absent
|
||||
// (LHasTemplate). Synthesising an empty one here would blank the key
|
||||
// and wipe the stored template. The 4 core fields are always present,
|
||||
// so this only ever skips `template`.
|
||||
if (!(f in obj)) continue;
|
||||
const plain = obj[f] || '';
|
||||
if (plain) {
|
||||
const c = await encryptPwd(plain);
|
||||
@@ -8061,6 +8067,8 @@ async function enterApp() {
|
||||
// Fire-and-forget periodic backup. Defer a few seconds so the unlock
|
||||
// path isn't blocked by file I/O + AES-GCM over the full vault.
|
||||
setTimeout(() => { runAutoBackupIfDue(); }, 5000);
|
||||
// First-run guided tour (spotlights the headline features once).
|
||||
maybeStartTour();
|
||||
}
|
||||
|
||||
async function autoPurgeTrashIfNeeded() {
|
||||
@@ -9123,8 +9131,12 @@ async function init() {
|
||||
// Autofill picker modal close button
|
||||
$$('#autofillPickerModal [data-close]').forEach(b =>
|
||||
b.addEventListener('click', closeAutofillPicker));
|
||||
$('#startTourBtn').addEventListener('click', () => {
|
||||
closeSettings();
|
||||
setTimeout(startTour, 250); // let the panel slide out first
|
||||
});
|
||||
$('#openClipboardSettings').addEventListener('click', () => {
|
||||
toast('Open Windows Settings → System → Clipboard → turn off "Clipboard history"', 'warning');
|
||||
toast('Copies use the ExcludeClipboardContentFromMonitorProcessing flag, so Windows skips them in Win+V history and cloud sync.');
|
||||
});
|
||||
$('#exportBtn').addEventListener('click', doExport);
|
||||
$('#exportCsvBtn').addEventListener('click', doExportCSV);
|
||||
|
||||
Reference in New Issue
Block a user