1 import capitalize from 'lodash/capitalize';
2 import { c } from 'ttag';
4 import { ImportProviderError, ImportReaderError } from '@proton/pass/lib/import/helpers/error';
11 } from '@proton/pass/lib/import/helpers/transformers';
12 import type { ImportPayload, ImportVault } from '@proton/pass/lib/import/types';
13 import type { ItemImportIntent, Maybe } from '@proton/pass/types';
14 import { truthy } from '@proton/pass/utils/fp/predicates';
15 import { logger } from '@proton/pass/utils/logger';
16 import { isObject } from '@proton/pass/utils/object/is-object';
18 import type { EnpassItem } from './enpass.types';
19 import { EnpassCategory, type EnpassData } from './enpass.types';
23 extractEnpassExtraFields,
24 extractEnpassIdentity,
27 } from './enpass.utils';
29 const processLoginItem = (
30 item: EnpassItem<EnpassCategory.LOGIN> | EnpassItem<EnpassCategory.PASSWORD>
31 ): ItemImportIntent<'login'> => {
32 const { extracted, remaining } = extractEnpassLogin(item.fields ?? []);
34 return importLoginItem({
37 trashed: isTrashedEnpassItem(item),
38 createTime: item.createdAt,
39 modifyTime: item.updated_at,
40 extraFields: extractEnpassExtraFields(remaining).concat(
41 extracted.username && extracted.email
42 ? [{ data: { content: extracted.email }, fieldName: 'E-mail', type: 'text' }]
45 email: extracted.email,
46 username: extracted.username,
47 password: extracted.password,
49 urls: extracted.url ? [extracted.url] : [],
53 const processNoteItem = (item: EnpassItem<EnpassCategory.NOTE>): ItemImportIntent<'note'> =>
57 trashed: item.archived !== 0 || item.trashed !== 0,
58 createTime: item.createdAt,
59 modifyTime: item.updated_at,
62 const processCreditCardItem = (item: EnpassItem<EnpassCategory.CREDIT_CARD>): ItemImportIntent[] => {
63 const { extracted: extractedCCData, remaining } = extractEnpassCC(item.fields ?? []);
65 const ccItem = importCreditCardItem({
68 trashed: isTrashedEnpassItem(item),
69 createTime: item.createdAt,
70 modifyTime: item.updated_at,
71 cardholderName: extractedCCData.ccName,
72 pin: extractedCCData.ccPin,
73 expirationDate: extractedCCData.ccExpiry,
74 number: extractedCCData.ccNumber,
75 verificationNumber: extractedCCData.ccCvc,
78 const hasLoginFields = remaining.some(({ type }) => (<readonly string[]>ENPASS_FIELD_TYPES.login).includes(type));
81 const enpassLoginItem: EnpassItem<EnpassCategory.LOGIN> = {
83 category: EnpassCategory.LOGIN,
87 const loginItem = processLoginItem(enpassLoginItem);
88 return [ccItem, loginItem];
94 const processIdentityItem = (item: EnpassItem<EnpassCategory.IDENTITY>): ItemImportIntent<'identity'> =>
98 ...extractEnpassIdentity(item),
101 const validateEnpassData = (data: any): data is EnpassData =>
102 isObject(data) && 'items' in data && Array.isArray(data.items);
104 export const readEnpassData = ({ data }: { data: string }): ImportPayload => {
106 const result = JSON.parse(data);
107 const valid = validateEnpassData(result);
109 if (!valid) throw new ImportReaderError(c('Error').t`File does not match expected format`);
111 const items = result.items.map((i) => i);
112 const ignored: string[] = [];
114 const vaults: ImportVault[] = [
116 name: getImportedVaultName(),
119 .flatMap((item): Maybe<ItemImportIntent | ItemImportIntent[]> => {
120 const type = capitalize(item?.category ?? c('Label').t`Unknown`);
121 const title = item?.title ?? '';
124 switch (item.category) {
125 case EnpassCategory.LOGIN:
126 case EnpassCategory.PASSWORD:
127 return processLoginItem(item);
128 case EnpassCategory.NOTE:
129 return processNoteItem(item);
130 case EnpassCategory.CREDIT_CARD:
131 return processCreditCardItem(item);
132 case EnpassCategory.IDENTITY:
133 return processIdentityItem(item);
135 ignored.push(`[${type}] ${title}`);
139 ignored.push(`[${type}] ${title}`);
140 logger.warn('[Importer::Enpass]', err);
147 return { vaults, ignored, warnings: [] };
149 logger.warn('[Importer::Enpass]', e);
150 throw new ImportProviderError('Enpass', e);