Files
Password-Manager/js
Zaki 4b15811221 feat(import): JSON / CSV vault import with heuristic column mapping
Round-trip companion to the existing doExport(). Supports two file
formats with auto-detection (extension + first-char sniff):

JSON
====
Native shape produced by doExport() AND a forgiving fallback for any
flat array of entry objects with site/url + password fields. Accepts:
  - { version, exported_at, entries: [...] }   (native)
  - [{ ... }, { ... }]                          (flat array)
  - mixed keys: site|url|name, username|user|login|email, etc.

CSV
===
RFC-4180-ish parser (~30 lines): quoted fields, escaped "", commas
inside quotes, CRLF line endings. No streaming since password-manager
imports are realistically MB-scale at most.

Heuristic column mapping (case + underscore tolerant) covers the
common exporters out of the box:

  Site/URL     : name, title, url, site, website, login_uri, login_url
  Username     : login_username, username, user, login, email
  Password     : login_password, password, pass, pwd
  Folder       : folder, group, category, path, collection
  Tags         : tags, labels (comma/semicolon-split)
  Notes        : notes, note, comment    (short notes joined into tags)
  TOTP         : login_totp, totp, otpauth, authenticator, two_factor

If the TOTP column holds a full otpauth:// URI it's parsed and only
the secret param is stored — same path used by the slide-over TOTP
field. Invalid base32 TOTP secrets are dropped silently rather than
failing the whole import.

Backend
=======
New endpoint: POST /entries/bulk-import
Body: { entries: [{ site, username, encrypted_password, iv, folder,
                    tags, totp_secret, totp_iv }, ... ] }
Caps at 10,000 entries per request as a sanity bound. Inserts inside
a single SQLite transaction — partial failure rolls back cleanly, the
user retries from the same source file. Returns { imported: N }.

Rows missing site or ciphertext are skipped within the transaction
(not failed) so one bad row in a 500-entry import doesn't blow up
the whole batch.

Client flow
===========
doImport():
  1. Hidden <input type="file" accept=".json,.csv"> picker
  2. Read text, detect format, route to parseEntriesFromJSON or CSV
  3. confirmDialog preview: count + first 3 sample sites + skipped rows
  4. On confirm: encryptImportEntry() each plaintext entry with the
     current vault key (reuses encryptPwd / base32Decode validation)
  5. Single POST to /entries/bulk-import
  6. Reload entries, refresh UI, trigger HIBP scan if enabled

UI
==
Two entry points (mirroring Export):
 - Sidebar "Import vault" nav item, next to "Export vault"
 - Settings panel "Import" section with descriptive blurb
Both call doImport(). New i-log-in icon added to the SVG sprite (mirror
of i-log-out used by Export).

Limitations
===========
 - No de-duplication: importing the same file twice yields duplicate
   entries. Trade-off to keep the v1 simple — the user can sort it
   out with the existing trash/multi-select UI.
 - No password-protected vault formats (Bitwarden encrypted JSON,
   KeePass kdbx). Only plaintext exports — same trade-off as
   doExport() which produces plaintext JSON.
2026-05-23 05:30:08 +01:00
..