mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
585 lines
22 KiB
TypeScript
585 lines
22 KiB
TypeScript
import type { CiphersImportPayload } from '@/lib/api/vault';
|
|
import {
|
|
addFolder,
|
|
cardBrand,
|
|
type CsvRow,
|
|
convertToNoteIfNeeded,
|
|
makeLoginCipher,
|
|
normalizeUri,
|
|
parseCsv,
|
|
parseCsvRows,
|
|
processKvp,
|
|
txt,
|
|
val,
|
|
} from '@/lib/import-format-shared';
|
|
|
|
function splitPipedField(raw: string): string {
|
|
const s = txt(raw);
|
|
if (!s) return '';
|
|
const p = s.split('|');
|
|
if (p.length <= 2) return s;
|
|
return [...p.slice(0, 2), p.slice(2).join('|')].pop() || '';
|
|
}
|
|
|
|
export function parseMSecureCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsvRows(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const row of rows) {
|
|
if (row.length < 3) continue;
|
|
const folderName = txt(row[2]) && txt(row[2]) !== 'Unassigned' ? row[2] : '';
|
|
const type = txt(row[1]);
|
|
const cipher = makeLoginCipher();
|
|
cipher.name = val(txt(row[0]).split('|')[0], '--');
|
|
|
|
if (type === 'Web Logins' || type === 'Login') {
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(splitPipedField(row[5] || ''));
|
|
login.password = val(splitPipedField(row[6] || ''));
|
|
const uri = normalizeUri(splitPipedField(row[4] || '') || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
cipher.notes = val((row[3] || '').split('\\n').join('\n'));
|
|
} else if (type === 'Credit Card') {
|
|
cipher.type = 3;
|
|
cipher.login = null;
|
|
const cardNumber = val(splitPipedField(row[4] || ''));
|
|
let expMonth: string | null = null;
|
|
let expYear: string | null = null;
|
|
const exp = splitPipedField(row[5] || '');
|
|
const m = exp.match(/^(\d{1,2})\s*\/\s*(\d{2,4})$/);
|
|
if (m) {
|
|
expMonth = m[1];
|
|
expYear = m[2].length === 2 ? `20${m[2]}` : m[2];
|
|
}
|
|
let code: string | null = null;
|
|
let holder: string | null = null;
|
|
for (const entry of row) {
|
|
if (/^Security Code\|\d*\|/.test(entry)) code = val(splitPipedField(entry));
|
|
if (/^Name on Card\|\d*\|/.test(entry)) holder = val(splitPipedField(entry));
|
|
}
|
|
const noteRegex = /\|\d*\|/;
|
|
const rawNotes = row.slice(2).filter((entry) => txt(entry) && !noteRegex.test(entry));
|
|
const indexedNotes = [8, 10, 11]
|
|
.filter((idx) => row[idx] && noteRegex.test(row[idx]))
|
|
.map((idx) => `${txt(row[idx]).split('|')[0]}: ${splitPipedField(row[idx])}`);
|
|
cipher.notes = [...rawNotes, ...indexedNotes].join('\n') || null;
|
|
cipher.card = {
|
|
number: cardNumber,
|
|
cardholderName: holder,
|
|
code,
|
|
expMonth,
|
|
expYear,
|
|
brand: cardBrand(cardNumber),
|
|
};
|
|
} else if (row.length > 3) {
|
|
cipher.type = 2;
|
|
cipher.login = null;
|
|
cipher.secureNote = { type: 0 };
|
|
const noteLines: string[] = [];
|
|
for (let i = 3; i < row.length; i++) {
|
|
if (txt(row[i])) noteLines.push(row[i]);
|
|
}
|
|
cipher.notes = noteLines.join('\n') || null;
|
|
}
|
|
|
|
if (txt(type) && Number(cipher.type) !== 1 && Number(cipher.type) !== 3) {
|
|
cipher.name = `${type}: ${txt(cipher.name)}`;
|
|
}
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
addFolder(result, folderName, idx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseMykiCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsv(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
const mappedBase = new Set(['nickname', 'additionalInfo']);
|
|
|
|
function unmapped(cipher: Record<string, unknown>, row: CsvRow, mapped: Set<string>): void {
|
|
for (const key of Object.keys(row)) {
|
|
if (mapped.has(key)) continue;
|
|
processKvp(cipher, key, row[key], false);
|
|
}
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const cipher = makeLoginCipher();
|
|
cipher.name = val(row.nickname, '--');
|
|
cipher.notes = val(txt(row.additionalInfo).replace(/\s+$/g, ''));
|
|
|
|
if (row.url !== undefined) {
|
|
const mapped = new Set([...mappedBase, 'url', 'username', 'password', 'twofaSecret']);
|
|
const login = cipher.login as Record<string, unknown>;
|
|
const uri = normalizeUri(row.url || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
login.username = val(row.username);
|
|
login.password = val(row.password);
|
|
login.totp = val(row.twofaSecret);
|
|
unmapped(cipher, row, mapped);
|
|
} else if (row.authToken !== undefined) {
|
|
const mapped = new Set([...mappedBase, 'authToken']);
|
|
(cipher.login as Record<string, unknown>).totp = val(row.authToken);
|
|
unmapped(cipher, row, mapped);
|
|
} else if (row.cardNumber !== undefined) {
|
|
const mapped = new Set([...mappedBase, 'cardNumber', 'cardName', 'exp_month', 'exp_year', 'cvv']);
|
|
cipher.type = 3;
|
|
cipher.login = null;
|
|
cipher.card = {
|
|
cardholderName: val(row.cardName),
|
|
number: val(row.cardNumber),
|
|
brand: cardBrand(val(row.cardNumber)),
|
|
expMonth: val(row.exp_month),
|
|
expYear: val(row.exp_year),
|
|
code: val(row.cvv),
|
|
};
|
|
unmapped(cipher, row, mapped);
|
|
} else if (row.firstName !== undefined) {
|
|
const mapped = new Set([
|
|
...mappedBase,
|
|
'title',
|
|
'firstName',
|
|
'middleName',
|
|
'lastName',
|
|
'email',
|
|
'firstAddressLine',
|
|
'secondAddressLine',
|
|
'city',
|
|
'country',
|
|
'zipCode',
|
|
]);
|
|
cipher.type = 4;
|
|
cipher.login = null;
|
|
cipher.identity = {
|
|
title: val(row.title),
|
|
firstName: val(row.firstName),
|
|
middleName: val(row.middleName),
|
|
lastName: val(row.lastName),
|
|
phone: val((row as Record<string, string>).number),
|
|
email: val(row.email),
|
|
address1: val(row.firstAddressLine),
|
|
address2: val(row.secondAddressLine),
|
|
city: val(row.city),
|
|
country: val(row.country),
|
|
postalCode: val(row.zipCode),
|
|
};
|
|
unmapped(cipher, row, mapped);
|
|
} else if (row.idType !== undefined) {
|
|
const mapped = new Set([...mappedBase, 'idName', 'idNumber', 'idCountry']);
|
|
const fullName = txt((row as Record<string, string>).idName);
|
|
const parts = fullName.split(/\s+/).filter(Boolean);
|
|
const idType = txt((row as Record<string, string>).idType);
|
|
const idNumber = val((row as Record<string, string>).idNumber);
|
|
cipher.type = 4;
|
|
cipher.login = null;
|
|
cipher.identity = {
|
|
firstName: parts[0] || null,
|
|
middleName: parts.length >= 3 ? parts[1] : null,
|
|
lastName: parts.length >= 2 ? parts.slice(parts.length >= 3 ? 2 : 1).join(' ') : null,
|
|
country: val((row as Record<string, string>).idCountry),
|
|
passportNumber: idType === 'Passport' ? idNumber : null,
|
|
ssn: idType === 'Social Security' ? idNumber : null,
|
|
licenseNumber: idType !== 'Passport' && idType !== 'Social Security' ? idNumber : null,
|
|
};
|
|
unmapped(cipher, row, mapped);
|
|
} else if (row.content !== undefined) {
|
|
const mapped = new Set([...mappedBase, 'content']);
|
|
cipher.type = 2;
|
|
cipher.login = null;
|
|
cipher.secureNote = { type: 0 };
|
|
cipher.notes = val(txt(row.content).replace(/\s+$/g, ''));
|
|
unmapped(cipher, row, mapped);
|
|
} else {
|
|
continue;
|
|
}
|
|
result.ciphers.push(cipher);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseNetwrixCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsv(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
const mapped = new Set(['Organisationseinheit', 'Informationen', 'Beschreibung', 'Benutzername', 'Passwort', 'Internetseite', 'One-Time Passwort']);
|
|
for (const row of rows) {
|
|
const cipher = makeLoginCipher();
|
|
cipher.notes = val(txt(row.Informationen).replace(/\s+$/g, ''));
|
|
cipher.name = val(row.Beschreibung, '--');
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(row.Benutzername);
|
|
login.password = val(row.Passwort);
|
|
login.totp = val((row as Record<string, string>)['One-Time Passwort']);
|
|
const uri = normalizeUri(row.Internetseite || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
for (const key of Object.keys(row)) {
|
|
if (mapped.has(key)) continue;
|
|
processKvp(cipher, key, row[key], false);
|
|
}
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
addFolder(result, row.Organisationseinheit, idx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseRoboFormCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsv(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const row of rows) {
|
|
const cipher = makeLoginCipher();
|
|
const folder = txt(row.Folder).startsWith('/') ? txt(row.Folder).slice(1) : txt(row.Folder);
|
|
cipher.notes = val(row.Note);
|
|
cipher.name = val(row.Name, '--');
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(row.Login);
|
|
login.password = val(row.Pwd, val(row.Password));
|
|
const uri = normalizeUri(row.Url || row.URL || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
if (txt(row.Rf_fields)) processKvp(cipher, 'Rf_fields', txt(row.Rf_fields), true);
|
|
if (txt(row.RfFieldsV2)) processKvp(cipher, 'RfFieldsV2', txt(row.RfFieldsV2), true);
|
|
|
|
convertToNoteIfNeeded(cipher);
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
addFolder(result, folder, idx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseZohoVaultCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsv(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const row of rows) {
|
|
if (!txt(row['Password Name']) && !txt(row['Secret Name'])) continue;
|
|
const cipher = makeLoginCipher();
|
|
cipher.favorite = txt(row.Favorite) === '1';
|
|
cipher.notes = val(row.Notes);
|
|
cipher.name = val(row['Password Name'], val(row['Secret Name'], '--'));
|
|
const login = cipher.login as Record<string, unknown>;
|
|
const uri = normalizeUri(txt(row['Password URL']) || txt(row['Secret URL']));
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
login.totp = val(row.login_totp);
|
|
|
|
const parseData = (data: string) => {
|
|
if (!txt(data)) return;
|
|
for (const line of data.split(/\r?\n/)) {
|
|
const pos = line.indexOf(':');
|
|
if (pos < 0) continue;
|
|
const key = txt(line.slice(0, pos));
|
|
const value = txt(line.slice(pos + 1));
|
|
if (!key || !value || key === 'SecretType') continue;
|
|
const low = key.toLowerCase();
|
|
if (!txt(login.username) && ['username', 'user', 'email', 'login', 'id'].includes(low)) login.username = value;
|
|
else if (!txt(login.password) && ['password', 'pass', 'passwd'].includes(low)) login.password = value;
|
|
else processKvp(cipher, key, value, false);
|
|
}
|
|
};
|
|
parseData(txt(row.SecretData));
|
|
parseData(txt(row.CustomData));
|
|
|
|
convertToNoteIfNeeded(cipher);
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
addFolder(result, row['Folder Name'], idx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parseNordpassCsv(textRaw: string): CiphersImportPayload {
|
|
const rows = parseCsv(textRaw);
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const r of rows) {
|
|
const t = txt(r.type);
|
|
if (!t) continue;
|
|
if (t === 'password') {
|
|
const cipher = makeLoginCipher();
|
|
cipher.name = val(r.name, '--');
|
|
cipher.notes = val(r.note);
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(r.username);
|
|
login.password = val(r.password);
|
|
const uris: string[] = [];
|
|
const main = normalizeUri(r.url || '');
|
|
if (main) uris.push(main);
|
|
if (txt(r.additional_urls)) {
|
|
try {
|
|
const extra = JSON.parse(r.additional_urls) as string[];
|
|
for (const u of extra || []) {
|
|
const n = normalizeUri(u || '');
|
|
if (n) uris.push(n);
|
|
}
|
|
} catch {}
|
|
}
|
|
login.uris = uris.length ? uris.map((u) => ({ uri: u, match: null })) : null;
|
|
if (txt(r.custom_fields)) {
|
|
try {
|
|
const cfs = JSON.parse(r.custom_fields) as any[];
|
|
for (const cf of cfs || []) processKvp(cipher, cf.label || '', cf.value || '', cf.type === 'hidden');
|
|
} catch {}
|
|
}
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
addFolder(result, r.folder, idx);
|
|
continue;
|
|
}
|
|
if (t === 'note') {
|
|
const idx =
|
|
result.ciphers.push({
|
|
type: 2,
|
|
name: val(r.name, '--'),
|
|
notes: val(r.note),
|
|
favorite: false,
|
|
reprompt: 0,
|
|
key: null,
|
|
login: null,
|
|
card: null,
|
|
identity: null,
|
|
secureNote: { type: 0 },
|
|
fields: null,
|
|
passwordHistory: null,
|
|
sshKey: null,
|
|
}) - 1;
|
|
addFolder(result, r.folder, idx);
|
|
continue;
|
|
}
|
|
if (t === 'credit_card') {
|
|
const idx =
|
|
result.ciphers.push({
|
|
type: 3,
|
|
name: val(r.name, '--'),
|
|
notes: val(r.note),
|
|
favorite: false,
|
|
reprompt: 0,
|
|
key: null,
|
|
login: null,
|
|
card: {
|
|
cardholderName: val(r.cardholdername),
|
|
number: val(r.cardnumber),
|
|
brand: cardBrand(val(r.cardnumber)),
|
|
code: val(r.cvc),
|
|
expMonth: val(r.expiry_month),
|
|
expYear: val(r.expiry_year),
|
|
},
|
|
identity: null,
|
|
secureNote: null,
|
|
fields: null,
|
|
passwordHistory: null,
|
|
sshKey: null,
|
|
}) - 1;
|
|
addFolder(result, r.folder, idx);
|
|
continue;
|
|
}
|
|
if (t === 'personal_info') {
|
|
const identity = {
|
|
title: val(r.title),
|
|
firstName: val(r.first_name),
|
|
middleName: val(r.middle_name),
|
|
lastName: val(r.last_name),
|
|
phone: val(r.phone_number),
|
|
email: val(r.email),
|
|
address1: val(r.address1),
|
|
address2: val(r.address2),
|
|
city: val(r.city),
|
|
state: val(r.state),
|
|
postalCode: val(r.postal_code),
|
|
country: val(r.country),
|
|
username: val(r.username),
|
|
company: val(r.company),
|
|
};
|
|
const idx =
|
|
result.ciphers.push({
|
|
type: 4,
|
|
name: val(r.name, '--'),
|
|
notes: val(r.note),
|
|
favorite: false,
|
|
reprompt: 0,
|
|
key: null,
|
|
login: null,
|
|
card: null,
|
|
identity,
|
|
secureNote: null,
|
|
fields: null,
|
|
passwordHistory: null,
|
|
sshKey: null,
|
|
}) - 1;
|
|
addFolder(result, r.folder, idx);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parsePassmanJson(textRaw: string): CiphersImportPayload {
|
|
const rows = JSON.parse(textRaw) as any[];
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const c of rows || []) {
|
|
const cipher = makeLoginCipher();
|
|
cipher.name = val(c.label, '--');
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(c.username, val(c.email));
|
|
login.password = val(c.password);
|
|
const uri = normalizeUri(c.url || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
login.totp = val(c?.otp?.secret);
|
|
const email = txt(c.email);
|
|
const desc = txt(c.description);
|
|
cipher.notes = `${login.username && email && txt(login.username) !== email ? `Email: ${email}\n` : ''}${desc}` || null;
|
|
for (const cf of c.custom_fields || []) {
|
|
const t = txt(cf.field_type);
|
|
if (t === 'text' || t === 'password') processKvp(cipher, cf.label || '', cf.value || '', false);
|
|
}
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
const folder = c?.tags?.[0]?.text;
|
|
if (folder) addFolder(result, String(folder), idx);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parsePasskyJson(textRaw: string): CiphersImportPayload {
|
|
const parsed = JSON.parse(textRaw) as { encrypted?: boolean; passwords?: any[] };
|
|
if (parsed.encrypted === true) throw new Error('Unable to import an encrypted passky backup.');
|
|
const list = Array.isArray(parsed.passwords) ? parsed.passwords : [];
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
for (const p of list) {
|
|
const cipher = makeLoginCipher();
|
|
cipher.name = val(p.website, '--');
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(p.username);
|
|
login.password = val(p.password);
|
|
const uri = normalizeUri(String(p.website || ''));
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
cipher.notes = val(p.message);
|
|
result.ciphers.push(cipher);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function parsePsonoJson(textRaw: string): CiphersImportPayload {
|
|
const parsed = JSON.parse(textRaw) as any;
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
|
|
function parseItem(item: any, folderName: string | null) {
|
|
if (!item || typeof item !== 'object') return;
|
|
const type = txt(item.type);
|
|
const cipher = makeLoginCipher();
|
|
if (type === 'website_password') {
|
|
cipher.name = val(item.website_password_title, '--');
|
|
cipher.notes = val(item.website_password_notes);
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(item.website_password_username);
|
|
login.password = val(item.website_password_password);
|
|
const uri = normalizeUri(item.website_password_url || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
if (folderName) addFolder(result, folderName, idx);
|
|
return;
|
|
}
|
|
if (type === 'application_password') {
|
|
cipher.name = val(item.application_password_title, '--');
|
|
cipher.notes = val(item.application_password_notes);
|
|
const login = cipher.login as Record<string, unknown>;
|
|
login.username = val(item.application_password_username);
|
|
login.password = val(item.application_password_password);
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
if (folderName) addFolder(result, folderName, idx);
|
|
return;
|
|
}
|
|
if (type === 'totp') {
|
|
cipher.name = val(item.totp_title, '--');
|
|
cipher.notes = val(item.totp_notes);
|
|
(cipher.login as Record<string, unknown>).totp = val(item.totp_code);
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
if (folderName) addFolder(result, folderName, idx);
|
|
return;
|
|
}
|
|
if (type === 'bookmark') {
|
|
cipher.name = val(item.bookmark_title, '--');
|
|
cipher.notes = val(item.bookmark_notes);
|
|
const uri = normalizeUri(item.bookmark_url || '');
|
|
(cipher.login as Record<string, unknown>).uris = uri ? [{ uri, match: null }] : null;
|
|
const idx = result.ciphers.push(cipher) - 1;
|
|
if (folderName) addFolder(result, folderName, idx);
|
|
return;
|
|
}
|
|
if (type === 'note' || type === 'environment_variables') {
|
|
const secure = {
|
|
type: 2,
|
|
name: val(type === 'note' ? item.note_title : item.environment_variables_title, '--'),
|
|
notes: val(type === 'note' ? item.note_notes : item.environment_variables_notes),
|
|
favorite: false,
|
|
reprompt: 0,
|
|
key: null,
|
|
login: null,
|
|
card: null,
|
|
identity: null,
|
|
secureNote: { type: 0 },
|
|
fields: null,
|
|
passwordHistory: null,
|
|
sshKey: null,
|
|
} as Record<string, unknown>;
|
|
const idx = result.ciphers.push(secure) - 1;
|
|
if (folderName) addFolder(result, folderName, idx);
|
|
}
|
|
}
|
|
|
|
function walkFolders(folders: any[], parent: string | null) {
|
|
for (const f of folders || []) {
|
|
const name = parent ? `${parent}/${txt(f.name)}` : txt(f.name);
|
|
for (const item of f.items || []) parseItem(item, name);
|
|
if (Array.isArray(f.folders)) walkFolders(f.folders, name);
|
|
}
|
|
}
|
|
|
|
for (const item of parsed.items || []) parseItem(item, null);
|
|
walkFolders(parsed.folders || [], null);
|
|
return result;
|
|
}
|
|
|
|
export function parsePasswordBossJson(textRaw: string): CiphersImportPayload {
|
|
const parsed = JSON.parse(textRaw) as { folders?: any[]; items?: any[] };
|
|
const result: CiphersImportPayload = { ciphers: [], folders: [], folderRelationships: [] };
|
|
const folderNameById = new Map<string, string>();
|
|
for (const f of parsed.folders || []) {
|
|
if (f?.id && f?.name) folderNameById.set(String(f.id), String(f.name));
|
|
}
|
|
for (const item of parsed.items || []) {
|
|
const ids = item?.identifiers || {};
|
|
const isCard = txt(item?.type) === 'CreditCard';
|
|
const base = isCard
|
|
? {
|
|
type: 3,
|
|
name: val(item?.name, '--'),
|
|
notes: val(ids.notes),
|
|
favorite: false,
|
|
reprompt: 0,
|
|
key: null,
|
|
login: null,
|
|
card: {
|
|
number: val(ids.cardNumber),
|
|
cardholderName: val(ids.nameOnCard),
|
|
code: val(ids.security_code),
|
|
brand: cardBrand(val(ids.cardNumber)),
|
|
expMonth: null,
|
|
expYear: null,
|
|
},
|
|
identity: null,
|
|
secureNote: null,
|
|
fields: [],
|
|
passwordHistory: null,
|
|
sshKey: null,
|
|
}
|
|
: makeLoginCipher();
|
|
if (!isCard) {
|
|
base.name = val(item?.name, '--');
|
|
base.notes = val(ids.notes);
|
|
const login = base.login as Record<string, unknown>;
|
|
login.username = val(ids.username, val(ids.email));
|
|
login.password = val(ids.password);
|
|
login.totp = val(ids.totp);
|
|
const uri = normalizeUri(item?.login_url || ids.url || '');
|
|
login.uris = uri ? [{ uri, match: null }] : null;
|
|
}
|
|
if (Array.isArray(ids.custom_fields)) {
|
|
for (const cf of ids.custom_fields) processKvp(base as Record<string, unknown>, cf?.name || '', cf?.value || '', false);
|
|
}
|
|
const idx = result.ciphers.push(base as Record<string, unknown>) - 1;
|
|
const folderId = item?.folder;
|
|
if (folderId && folderNameById.has(String(folderId))) addFolder(result, folderNameById.get(String(folderId)) || '', idx);
|
|
}
|
|
return result;
|
|
}
|