fix: preserve multiline values (e.g. SSH private keys) during CSV import

parseBitwardenCsvFieldLines previously discarded any field line that did not
contain the ': ' delimiter, truncating multiline values like OpenSSH private
keys to only their first line.

Replace the map+filter pipeline with a reduce that accumulates continuation
lines (lines without ': ') into the previous entry's value, joined by '\n'.
This preserves the full private key content through a CSV round-trip.

Fixes: CSV export to import of SSH key items where the private key body was
silently dropped.
This commit is contained in:
rootphantomer
2026-06-30 11:44:03 +08:00
committed by Shuai
parent 0d1bb196e2
commit 68c42a0330
+17 -5
View File
@@ -42,19 +42,31 @@ const NODEWARDEN_CSV_OBJECT_FIELDS: Record<'card' | 'identity' | 'sshKey', reado
sshKey: ['privateKey', 'publicKey', 'keyFingerprint', 'fingerprint'], sshKey: ['privateKey', 'publicKey', 'keyFingerprint', 'fingerprint'],
}; };
// Parse the `fields` CSV column into key-value pairs.
// Lines without a `: ` delimiter are treated as continuations of the previous
// line's value, preserving multiline content such as SSH private keys.
function parseBitwardenCsvFieldLines(rawFields: unknown): BitwardenCsvFieldLine[] { function parseBitwardenCsvFieldLines(rawFields: unknown): BitwardenCsvFieldLine[] {
return String(rawFields || '') return String(rawFields || '')
.split(/\r?\n/) .split(/\r?\n/)
.map((line) => line.trim()) .map((line) => line.trim())
.filter(Boolean) .filter(Boolean)
.map((line) => { .reduce<BitwardenCsvFieldLine[]>((acc, line) => {
const delim = line.lastIndexOf(': '); const delim = line.lastIndexOf(': ');
if (delim < 0) return null; if (delim < 0) {
// Continuation line — append to the previous entry's value.
if (acc.length > 0) {
acc[acc.length - 1].value += '\n' + line;
}
return acc;
}
// New key-value line.
const key = txt(line.slice(0, delim)); const key = txt(line.slice(0, delim));
const value = txt(line.slice(delim + 2)); const value = txt(line.slice(delim + 2));
return key && value ? { key, value } : null; if (key && value) {
}) acc.push({ key, value });
.filter((line): line is BitwardenCsvFieldLine => !!line); }
return acc;
}, []);
} }
function getNodeWardenCsvType(lines: BitwardenCsvFieldLine[]): number | null { function getNodeWardenCsvType(lines: BitwardenCsvFieldLine[]): number | null {