pwstore: generic csvManager for 8 password-manager CSV adapters
One data-driven csvManager (layout = header + record func) covers edge/brave (chrome layout), safari, lastpass, dashlane, protonpass, nordpass, roboform, and read/guide-only enpass — replacing 8 near-identical files. Export is header-name driven (parseLoginCSV + new aliases); import stages a per-manager-layout CSV on tmpfs and shreds it, gated by --allow-csv. Notes now round-trip through Meta. Reimporter exposes per-manager re-import hints. Column layouts are MOCK-ONLY (unit-validated) on the same LIVE-VM staging machinery; recorded as DATA in BROWSER-ROTATION.md §8. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-6
@@ -35,11 +35,13 @@ func colsFromHeader(header []string) csvCols {
|
||||
}
|
||||
return csvCols{
|
||||
name: idx("name", "title"),
|
||||
url: idx("url", "login_uri", "web site", "website"),
|
||||
username: idx("username", "login_username", "user"),
|
||||
password: idx("password", "login_password", "pass"),
|
||||
note: idx("note", "notes"),
|
||||
group: idx("group"), // KeePassXC export column; absent elsewhere (-1)
|
||||
url: idx("url", "login_uri", "web site", "website", "matchurl"),
|
||||
username: idx("username", "login_username", "user", "login"),
|
||||
password: idx("password", "login_password", "pass", "pwd"),
|
||||
note: idx("note", "notes", "extra"),
|
||||
// group: KeePassXC "group"; LastPass "grouping"; Dashlane "category";
|
||||
// NordPass/RoboForm "folder"; Proton "vault". Absent elsewhere (-1).
|
||||
group: idx("group", "grouping", "category", "folder", "vault"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +87,11 @@ func parseLoginCSV(v *vault.Vault, data []byte) ([]Account, error) {
|
||||
if g := get(rec, cols.group); g != "" {
|
||||
a.Meta["group"] = g
|
||||
}
|
||||
// Preserve notes so a manager round-trip (export -> import CSV) does not wipe
|
||||
// the user's existing note on the entry. Notes are non-secret metadata.
|
||||
if nt := get(rec, cols.note); nt != "" {
|
||||
a.Meta["note"] = nt
|
||||
}
|
||||
pw := get(rec, cols.password)
|
||||
a.Secret = v.Store([]byte(pw))
|
||||
out = append(out, a)
|
||||
@@ -110,7 +117,7 @@ type csvLayout struct {
|
||||
var chromeLayout = csvLayout{
|
||||
header: chromeCSVHeader,
|
||||
record: func(a Account, pw string) []string {
|
||||
return []string{a.Meta["name"], a.URL, a.Username, pw, ""}
|
||||
return []string{a.Meta["name"], a.URL, a.Username, pw, a.Meta["note"]}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user