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',
|
'lastpass',
|
||||||
'dashlane_csv',
|
'dashlane_csv',
|
||||||
'dashlane_json',
|
'dashlane_json',
|
||||||
|
'keepass_csv',
|
||||||
'keepass_xml',
|
'keepass_xml',
|
||||||
'keepassx_csv',
|
'keepassx_csv',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const IMPORT_SOURCES = [
|
|||||||
{ id: 'lastpass', label: 'LastPass (csv)' },
|
{ id: 'lastpass', label: 'LastPass (csv)' },
|
||||||
{ id: 'dashlane_csv', label: 'Dashlane (csv)' },
|
{ id: 'dashlane_csv', label: 'Dashlane (csv)' },
|
||||||
{ id: 'dashlane_json', label: 'Dashlane (json)' },
|
{ id: 'dashlane_json', label: 'Dashlane (json)' },
|
||||||
|
{ id: 'keepass_csv', label: 'KeePass 1.x (csv)' },
|
||||||
{ id: 'keepass_xml', label: 'KeePass 2 (xml)' },
|
{ id: 'keepass_xml', label: 'KeePass 2 (xml)' },
|
||||||
{ id: 'keepassx_csv', label: 'KeePassX (csv)' },
|
{ id: 'keepassx_csv', label: 'KeePassX (csv)' },
|
||||||
{ id: 'arc_csv', label: 'Arc (csv)' },
|
{ id: 'arc_csv', label: 'Arc (csv)' },
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ export function parseEncryptrCsv(textRaw: string): CiphersImportPayload {
|
|||||||
export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
|
export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
|
||||||
const rows = parseCsv(textRaw);
|
const rows = parseCsv(textRaw);
|
||||||
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
||||||
|
const standardColumns = new Set(['Group', 'Title', 'Username', 'Password', 'URL', 'Notes', 'TOTP']);
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
if (!txt(row.Title)) continue;
|
if (!txt(row.Title)) continue;
|
||||||
const cipher = makeLoginCipher();
|
const cipher = makeLoginCipher();
|
||||||
@@ -209,12 +210,34 @@ export function parseKeePassXCsv(textRaw: string): CiphersImportPayload {
|
|||||||
login.totp = val(row.TOTP);
|
login.totp = val(row.TOTP);
|
||||||
const uri = normalizeUri(row.URL || '');
|
const uri = normalizeUri(row.URL || '');
|
||||||
login.uris = uri ? [{ uri, match: null }] : null;
|
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;
|
const idx = result.ciphers.push(cipher) - 1;
|
||||||
addFolder(result, txt(row.Group).replace(/^Root\//, ''), idx);
|
addFolder(result, txt(row.Group).replace(/^Root\//, ''), idx);
|
||||||
}
|
}
|
||||||
return result;
|
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 {
|
export function parseLastPassCsv(textRaw: string): CiphersImportPayload {
|
||||||
const rows = parseCsv(textRaw);
|
const rows = parseCsv(textRaw);
|
||||||
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
||||||
@@ -350,7 +373,8 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
|
|||||||
const cipher = makeLoginCipher();
|
const cipher = makeLoginCipher();
|
||||||
for (const s of qd(entry, 'String')) {
|
for (const s of qd(entry, 'String')) {
|
||||||
const key = txt(qd(s, 'Key')[0]?.textContent);
|
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;
|
if (!value) continue;
|
||||||
const login = cipher.login as Record<string, unknown>;
|
const login = cipher.login as Record<string, unknown>;
|
||||||
if (key === 'Title') cipher.name = value;
|
if (key === 'Title') cipher.name = value;
|
||||||
@@ -361,6 +385,11 @@ export function parseKeePassXml(textRaw: string): CiphersImportPayload {
|
|||||||
login.uris = uri ? [{ uri, match: null }] : null;
|
login.uris = uri ? [{ uri, match: null }] : null;
|
||||||
} else if (key === 'otp') login.totp = value.replace('key=', '');
|
} else if (key === 'otp') login.totp = value.replace('key=', '');
|
||||||
else if (key === 'Notes') cipher.notes = `${txt(cipher.notes)}${txt(cipher.notes) ? '\n' : ''}${value}`;
|
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;
|
const idx = result.ciphers.push(cipher) - 1;
|
||||||
if (!isRoot && folder >= 0) result.folderRelationships.push({ key: idx, value: folder });
|
if (!isRoot && folder >= 0) result.folderRelationships.push({ key: idx, value: folder });
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
parseDashlaneCsv,
|
parseDashlaneCsv,
|
||||||
parseDashlaneJson,
|
parseDashlaneJson,
|
||||||
parseEncryptrCsv,
|
parseEncryptrCsv,
|
||||||
|
parseKeePassCsv,
|
||||||
parseKeePassXCsv,
|
parseKeePassXCsv,
|
||||||
parseKeePassXml,
|
parseKeePassXml,
|
||||||
parseLastPassCsv,
|
parseLastPassCsv,
|
||||||
@@ -75,6 +76,7 @@ const IMPORT_SOURCE_PARSERS: Record<ImportSourceId, (textRaw: string) => Ciphers
|
|||||||
lastpass: parseLastPassCsv,
|
lastpass: parseLastPassCsv,
|
||||||
dashlane_csv: parseDashlaneCsv,
|
dashlane_csv: parseDashlaneCsv,
|
||||||
dashlane_json: parseDashlaneJson,
|
dashlane_json: parseDashlaneJson,
|
||||||
|
keepass_csv: parseKeePassCsv,
|
||||||
keepass_xml: parseKeePassXml,
|
keepass_xml: parseKeePassXml,
|
||||||
keepassx_csv: parseKeePassXCsv,
|
keepassx_csv: parseKeePassXCsv,
|
||||||
arc_csv: parseArcCsv,
|
arc_csv: parseArcCsv,
|
||||||
|
|||||||
Reference in New Issue
Block a user