309 lines
7.3 KiB
TypeScript
309 lines
7.3 KiB
TypeScript
import { loadAsync } from 'jszip';
|
|
|
|
export type OnePuxItemDetailsLoginField = {
|
|
value: string;
|
|
id: string;
|
|
name: string;
|
|
fieldType: 'A' | 'B' | 'C' | 'E' | 'I' | 'N' | 'P' | 'R' | 'S' | 'T' | 'U';
|
|
designation?: 'username' | 'password';
|
|
};
|
|
|
|
export type OnePuxItemDetailsSection = {
|
|
title: string;
|
|
name: string;
|
|
fields: [
|
|
{
|
|
title: string;
|
|
id: string;
|
|
value: {
|
|
concealed?: string;
|
|
reference?: string;
|
|
string?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
url?: string;
|
|
totp?: string;
|
|
gender?: string;
|
|
creditCardType?: string;
|
|
creditCardNumber?: string;
|
|
monthYear?: number;
|
|
date?: number;
|
|
};
|
|
indexAtSource: number;
|
|
guarded: boolean;
|
|
multiline: boolean;
|
|
dontGenerate: boolean;
|
|
inputTraits: {
|
|
keyboard: string;
|
|
correction: string;
|
|
capitalization: string;
|
|
};
|
|
},
|
|
];
|
|
};
|
|
|
|
export type OnePuxItemDetailsPasswordHistory = {
|
|
value: string;
|
|
time: number;
|
|
};
|
|
|
|
export type OnePuxItemOverviewUrl = {
|
|
label: string;
|
|
url: string;
|
|
};
|
|
|
|
export type OnePuxItem = {
|
|
item?: {
|
|
uuid: string;
|
|
favIndex: number;
|
|
createdAt: number;
|
|
updatedAt: number;
|
|
trashed: boolean;
|
|
categoryUuid: string;
|
|
details: {
|
|
loginFields: OnePuxItemDetailsLoginField[];
|
|
notesPlain?: string;
|
|
sections: OnePuxItemDetailsSection[];
|
|
passwordHistory: OnePuxItemDetailsPasswordHistory[];
|
|
documentAttributes?: {
|
|
fileName: string;
|
|
documentId: string;
|
|
decryptedSize: number;
|
|
};
|
|
};
|
|
overview: {
|
|
subtitle: string;
|
|
urls?: OnePuxItemOverviewUrl[];
|
|
title: string;
|
|
url: string;
|
|
ps?: number;
|
|
pbe?: number;
|
|
pgrng?: boolean;
|
|
tags?: string[];
|
|
};
|
|
};
|
|
file?: {
|
|
attrs: {
|
|
uuid: string;
|
|
name: string;
|
|
type: string;
|
|
};
|
|
path: string;
|
|
};
|
|
};
|
|
|
|
export type OnePuxVault = {
|
|
attrs: {
|
|
uuid: string;
|
|
desc: string;
|
|
avatar: string;
|
|
name: string;
|
|
type: 'P' | 'E' | 'U';
|
|
};
|
|
items: OnePuxItem[];
|
|
};
|
|
|
|
export type OnePuxAccount = {
|
|
attrs: {
|
|
accountName: string;
|
|
name: string;
|
|
avatar: string;
|
|
email: string;
|
|
uuid: string;
|
|
domain: string;
|
|
};
|
|
vaults: OnePuxVault[];
|
|
};
|
|
|
|
export type OnePuxData = {
|
|
accounts: OnePuxAccount[];
|
|
};
|
|
|
|
export type OnePuxAttributes = {
|
|
version: number;
|
|
description: string;
|
|
createdAt: number;
|
|
};
|
|
|
|
export type OnePuxExport = {
|
|
attributes: OnePuxAttributes;
|
|
data: OnePuxData;
|
|
};
|
|
|
|
export const parse1PuxFile = async (
|
|
fileContents: string | ArrayBuffer,
|
|
) => {
|
|
try {
|
|
const zip = await loadAsync(fileContents);
|
|
|
|
const attributesContent = await zip
|
|
.file('export.attributes')!
|
|
.async('string');
|
|
const attributes = JSON.parse(attributesContent);
|
|
const dataContent = await zip.file('export.data')!.async('string');
|
|
const data = JSON.parse(dataContent);
|
|
|
|
return {
|
|
attributes,
|
|
data,
|
|
} as OnePuxExport;
|
|
} catch (error) {
|
|
console.error('Failed to parse .1pux file');
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
type RowData = {
|
|
name: string;
|
|
tags: string;
|
|
url: string;
|
|
username: string;
|
|
password: string;
|
|
notes: string;
|
|
extraFields: ExtraField[];
|
|
};
|
|
|
|
type ExtraFieldType =
|
|
| 'username'
|
|
| 'password'
|
|
| 'url'
|
|
| 'email'
|
|
| 'date'
|
|
| 'month'
|
|
| 'credit'
|
|
| 'phone'
|
|
| 'totp'
|
|
| 'text';
|
|
|
|
type ExtraField = { name: string; value: string; type: ExtraFieldType };
|
|
|
|
type ParseFieldTypeToExtraFieldType = (
|
|
field: OnePuxItemDetailsLoginField,
|
|
) => ExtraFieldType;
|
|
|
|
const parseFieldTypeToExtraFieldType: ParseFieldTypeToExtraFieldType = (
|
|
field,
|
|
) => {
|
|
if (field.designation === 'username') {
|
|
return 'username';
|
|
} else if (field.designation === 'password') {
|
|
return 'password';
|
|
} else if (field.fieldType === 'E') {
|
|
return 'email';
|
|
} else if (field.fieldType === 'U') {
|
|
return 'url';
|
|
}
|
|
return 'text';
|
|
};
|
|
|
|
export const parseToRowData = (
|
|
item: OnePuxItem['item'],
|
|
defaultTags?: string[],
|
|
) => {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
const rowData: RowData = {
|
|
name: item.overview.title,
|
|
tags: [...(defaultTags || []), ...(item.overview.tags || [])].join(','),
|
|
url: item.overview.url || '',
|
|
username: '',
|
|
password: '',
|
|
notes: item.details.notesPlain || '',
|
|
extraFields: [],
|
|
};
|
|
|
|
// Skip documents
|
|
if (
|
|
item.details.documentAttributes &&
|
|
item.details.loginFields.length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Extract username, password, and some extraFields
|
|
item.details.loginFields.forEach((field) => {
|
|
if (field.designation === 'username') {
|
|
rowData.username = field.value;
|
|
} else if (field.designation === 'password') {
|
|
rowData.password = field.value;
|
|
} else if (
|
|
field.fieldType === 'I' ||
|
|
field.fieldType === 'C' ||
|
|
field.id.includes(';opid=__') ||
|
|
field.value === ''
|
|
) {
|
|
// Skip these noisy form-fields
|
|
return;
|
|
} else {
|
|
rowData.extraFields.push({
|
|
name: field.name || field.id,
|
|
value: field.value,
|
|
type: parseFieldTypeToExtraFieldType(field),
|
|
});
|
|
}
|
|
});
|
|
|
|
// Extract some more extraFields
|
|
item.details.sections.forEach((section) => {
|
|
section.fields.forEach((field) => {
|
|
let value = '';
|
|
let type: ExtraFieldType = 'text';
|
|
|
|
if (Object.prototype.hasOwnProperty.call(field.value, 'concealed')) {
|
|
value = field.value.concealed || '';
|
|
} else if (
|
|
Object.prototype.hasOwnProperty.call(field.value, 'reference')
|
|
) {
|
|
value = field.value.reference || '';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'string')) {
|
|
value = field.value.string || '';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'email')) {
|
|
value = field.value.email || '';
|
|
type = 'email';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'phone')) {
|
|
value = field.value.phone || '';
|
|
type = 'phone';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'url')) {
|
|
value = field.value.url || '';
|
|
type = 'url';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'totp')) {
|
|
value = field.value.totp || '';
|
|
type = 'totp';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'gender')) {
|
|
value = field.value.gender || '';
|
|
} else if (
|
|
Object.prototype.hasOwnProperty.call(field.value, 'creditCardType')
|
|
) {
|
|
value = field.value.creditCardType || '';
|
|
} else if (
|
|
Object.prototype.hasOwnProperty.call(field.value, 'creditCardNumber')
|
|
) {
|
|
value = field.value.creditCardNumber || '';
|
|
type = 'credit';
|
|
} else if (
|
|
Object.prototype.hasOwnProperty.call(field.value, 'monthYear')
|
|
) {
|
|
value =
|
|
(field.value.monthYear && field.value.monthYear.toString()) || '';
|
|
type = 'month';
|
|
} else if (Object.prototype.hasOwnProperty.call(field.value, 'date')) {
|
|
value = (field.value.date && field.value.date.toString()) || '';
|
|
type = 'date';
|
|
} else {
|
|
// Default, so no data is lost when something new comes up
|
|
value = JSON.stringify(field.value);
|
|
}
|
|
|
|
rowData.extraFields.push({
|
|
name: field.title || field.id,
|
|
value,
|
|
type,
|
|
});
|
|
});
|
|
});
|
|
|
|
return rowData;
|
|
};
|