mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
fix: add support for KeePass CSV import format and enhance import parsing logic
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# CodeGraph data files
|
||||
# These are local to each machine and should not be committed
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Cache
|
||||
cache/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Hook markers
|
||||
.dirty
|
||||
*.pid
|
||||
@@ -91,6 +91,7 @@ const COMMON_IMPORT_SOURCE_IDS: ImportSourceId[] = [
|
||||
'lastpass',
|
||||
'dashlane_csv',
|
||||
'dashlane_json',
|
||||
'keepass_csv',
|
||||
'keepass_xml',
|
||||
'keepassx_csv',
|
||||
];
|
||||
|
||||
@@ -23,6 +23,7 @@ export const IMPORT_SOURCES = [
|
||||
{ id: 'lastpass', label: 'LastPass (csv)' },
|
||||
{ id: 'dashlane_csv', label: 'Dashlane (csv)' },
|
||||
{ id: 'dashlane_json', label: 'Dashlane (json)' },
|
||||
{ id: 'keepass_csv', label: 'KeePass 1.x (csv)' },
|
||||
{ id: 'keepass_xml', label: 'KeePass 2 (xml)' },
|
||||
{ id: 'keepassx_csv', label: 'KeePassX (csv)' },
|
||||
{ id: 'arc_csv', label: 'Arc (csv)' },
|
||||
|
||||
@@ -198,6 +198,7 @@ export function parseEncryptrCsv(textRaw: string): CiphersImportPayload {
|
||||
export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
|
||||
const rows = parseCsv(textRaw);
|
||||
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
||||
const standardColumns = new Set(['Group', 'Title', 'Username', 'Password', 'URL', 'Notes', 'TOTP']);
|
||||
for (const row of rows) {
|
||||
if (!txt(row.Title)) continue;
|
||||
const cipher = makeLoginCipher();
|
||||
@@ -209,12 +210,34 @@ export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
|
||||
login.totp = val(row.TOTP);
|
||||
const uri = normalizeUri(row.URL || '');
|
||||
login.uris = uri ? [{ uri, match: null }] : null;
|
||||
for (const [key, value] of Object.entries(row)) {
|
||||
if (standardColumns.has(key)) continue;
|
||||
processKvp(cipher, key, value, false);
|
||||
}
|
||||
const idx = result.ciphers.push(cipher) - 1;
|
||||
addFolder(result, txt(row.Group).replace(/^Root\//, ''), idx);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseKeePassCsv(textRaw: string): CiphersImportPayload {
|
||||
const rows = parseCsv(textRaw);
|
||||
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
||||
for (const row of rows) {
|
||||
if (!txt(row.Account)) continue;
|
||||
const cipher = makeLoginCipher();
|
||||
cipher.name = val(row.Account, '--');
|
||||
cipher.notes = val(row.Comments);
|
||||
const login = cipher.login as Record<string, unknown>;
|
||||
login.username = val(row['Login Name']);
|
||||
login.password = val(row.Password);
|
||||
const uri = normalizeUri(row['Web Site'] || '');
|
||||
login.uris = uri ? [{ uri, match: null }] : null;
|
||||
result.ciphers.push(cipher);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseLastPassCsv(textRaw: string): CiphersImportPayload {
|
||||
const rows = parseCsv(textRaw);
|
||||
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
||||
@@ -350,7 +373,8 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
|
||||
const cipher = makeLoginCipher();
|
||||
for (const s of qd(entry, 'String')) {
|
||||
const key = txt(qd(s, 'Key')[0]?.textContent);
|
||||
const value = txt(qd(s, 'Value')[0]?.textContent);
|
||||
const valueNode = qd(s, 'Value')[0];
|
||||
const value = txt(valueNode?.textContent);
|
||||
if (!value) continue;
|
||||
const login = cipher.login as Record<string, unknown>;
|
||||
if (key === 'Title') cipher.name = value;
|
||||
@@ -361,6 +385,11 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
|
||||
login.uris = uri ? [{ uri, match: null }] : null;
|
||||
} else if (key === 'otp') login.totp = value.replace('key=', '');
|
||||
else if (key === 'Notes') cipher.notes = `${txt(cipher.notes)}${txt(cipher.notes) ? '\n' : ''}${value}`;
|
||||
else {
|
||||
const hidden = ['True', 'true', '1'].includes(valueNode?.getAttribute('ProtectInMemory') || '')
|
||||
|| ['True', 'true', '1'].includes(valueNode?.getAttribute('Protected') || '');
|
||||
processKvp(cipher, key, value, hidden);
|
||||
}
|
||||
}
|
||||
const idx = result.ciphers.push(cipher) - 1;
|
||||
if (!isRoot && folder >= 0) result.folderRelationships.push({ key: idx, value: folder });
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
parseDashlaneCsv,
|
||||
parseDashlaneJson,
|
||||
parseEncryptrCsv,
|
||||
parseKeePassCsv,
|
||||
parseKeePassXCsv,
|
||||
parseKeePassXml,
|
||||
parseLastPassCsv,
|
||||
@@ -75,6 +76,7 @@ const IMPORT_SOURCE_PARSERS: Record<ImportSourceId, (textRaw: string) => Ciphers
|
||||
lastpass: parseLastPassCsv,
|
||||
dashlane_csv: parseDashlaneCsv,
|
||||
dashlane_json: parseDashlaneJson,
|
||||
keepass_csv: parseKeePassCsv,
|
||||
keepass_xml: parseKeePassXml,
|
||||
keepassx_csv: parseKeePassXCsv,
|
||||
arc_csv: parseArcCsv,
|
||||
|
||||
Reference in New Issue
Block a user