Avoid Pass importer failing during line parsing
[ProtonMail-WebClient.git] / packages / pass / lib / import / providers / enpass / enpass.reader.ts
blob64a0919f0a12d849a57ee345a155634b1b1fb785
1 import capitalize from 'lodash/capitalize';
2 import { c } from 'ttag';
4 import { ImportProviderError, ImportReaderError } from '@proton/pass/lib/import/helpers/error';
5 import {
6     getImportedVaultName,
7     importCreditCardItem,
8     importIdentityItem,
9     importLoginItem,
10     importNoteItem,
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';
20 import {
21     ENPASS_FIELD_TYPES,
22     extractEnpassCC,
23     extractEnpassExtraFields,
24     extractEnpassIdentity,
25     extractEnpassLogin,
26     isTrashedEnpassItem,
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({
35         name: item.title,
36         note: item.note,
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' }]
43                 : []
44         ),
45         email: extracted.email,
46         username: extracted.username,
47         password: extracted.password,
48         totp: extracted.totp,
49         urls: extracted.url ? [extracted.url] : [],
50     });
53 const processNoteItem = (item: EnpassItem<EnpassCategory.NOTE>): ItemImportIntent<'note'> =>
54     importNoteItem({
55         name: item.title,
56         note: item.note,
57         trashed: item.archived !== 0 || item.trashed !== 0,
58         createTime: item.createdAt,
59         modifyTime: item.updated_at,
60     });
62 const processCreditCardItem = (item: EnpassItem<EnpassCategory.CREDIT_CARD>): ItemImportIntent[] => {
63     const { extracted: extractedCCData, remaining } = extractEnpassCC(item.fields ?? []);
65     const ccItem = importCreditCardItem({
66         name: item.title,
67         note: item.note,
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,
76     });
78     const hasLoginFields = remaining.some(({ type }) => (<readonly string[]>ENPASS_FIELD_TYPES.login).includes(type));
80     if (hasLoginFields) {
81         const enpassLoginItem: EnpassItem<EnpassCategory.LOGIN> = {
82             ...item,
83             category: EnpassCategory.LOGIN,
84             fields: remaining,
85         };
87         const loginItem = processLoginItem(enpassLoginItem);
88         return [ccItem, loginItem];
89     }
91     return [ccItem];
94 const processIdentityItem = (item: EnpassItem<EnpassCategory.IDENTITY>): ItemImportIntent<'identity'> =>
95     importIdentityItem({
96         name: item.title,
97         note: item.note,
98         ...extractEnpassIdentity(item),
99     });
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 => {
105     try {
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[] = [
115             {
116                 name: getImportedVaultName(),
117                 shareId: null,
118                 items: items
119                     .flatMap((item): Maybe<ItemImportIntent | ItemImportIntent[]> => {
120                         const type = capitalize(item?.category ?? c('Label').t`Unknown`);
121                         const title = item?.title ?? '';
123                         try {
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);
134                                 default:
135                                     ignored.push(`[${type}] ${title}`);
136                                     return;
137                             }
138                         } catch (err) {
139                             ignored.push(`[${type}] ${title}`);
140                             logger.warn('[Importer::Enpass]', err);
141                         }
142                     })
143                     .filter(truthy),
144             },
145         ];
147         return { vaults, ignored, warnings: [] };
148     } catch (e) {
149         logger.warn('[Importer::Enpass]', e);
150         throw new ImportProviderError('Enpass', e);
151     }