1 import JSZip from 'jszip';
2 import Papa from 'papaparse';
4 import { CryptoProxy } from '@proton/crypto';
5 import { decodeBase64, encodeBase64, encodeUtf8Base64 } from '@proton/crypto/lib/utils';
6 import type { TransferableFile } from '@proton/pass/utils/file/transferable-file';
7 import { prop } from '@proton/pass/utils/fp/lens';
8 import { PASS_APP_NAME } from '@proton/shared/lib/constants';
9 import { uint8ArrayToBase64String } from '@proton/shared/lib/helpers/encoding';
11 import type { ExportCSVItem } from './types';
12 import { type ExportData, ExportFormat, type ExportOptions } from './types';
14 const EXPORT_AS_JSON_TYPES = ['creditCard', 'identity'];
16 /** Exporting data from the extension uses the .zip format
17 * for future-proofing : we will support integrating additional
18 * files when exporting */
19 export const createPassExportZip = async (payload: ExportData): Promise<Uint8Array> => {
20 const zip = new JSZip();
21 zip.file(`${PASS_APP_NAME}/data.json`, JSON.stringify(payload));
22 return zip.generateAsync({ type: 'uint8array' });
25 /** FIXME: ideally we should also support exporting
26 * `extraFields` to notes when exporting to CSV */
27 export const createPassExportCSV = (payload: ExportData): string => {
28 const items = Object.values(payload.vaults)
29 .flatMap(prop('items'))
30 .map<ExportCSVItem>(({ data, aliasEmail, createTime, modifyTime, shareId }) => ({
32 name: data.metadata.name,
33 url: 'urls' in data.content ? data.content.urls.join(', ') : '',
37 return data.content.itemEmail;
39 return aliasEmail ?? '';
44 username: data.type === 'login' ? data.content.itemUsername : '',
45 password: 'password' in data.content ? data.content.password : '',
46 note: EXPORT_AS_JSON_TYPES.includes(data.type)
47 ? JSON.stringify({ ...data.content, note: data.metadata.note })
49 totp: 'totpUri' in data.content ? data.content.totpUri : '',
50 createTime: createTime.toString(),
51 modifyTime: modifyTime.toString(),
52 vault: payload.vaults[shareId].name,
55 return Papa.unparse(items);
59 * Encrypts an `Uint8Array` representation of an export zip to a base64.
60 * Once support for argon2 is released, then we should pass a config to use
61 * that instead - as long as the export feature is alpha, it’s okay to release
62 * without argon2, but let’s pass config: { s2kIterationCountByte: 255 } to
63 * encryptMessage with the highest security we have atm */
64 export const encryptPassExport = async (data: Uint8Array, passphrase: string): Promise<string> =>
67 await CryptoProxy.encryptMessage({
69 passwords: [passphrase],
70 config: { s2kIterationCountByte: 255 },
76 export const decryptPassExport = async (base64: string, passphrase: string): Promise<Uint8Array> =>
78 await CryptoProxy.decryptMessage({
79 armoredMessage: decodeBase64(base64),
80 passwords: [passphrase],
85 const getMimeType = (format: ExportFormat) => {
87 case ExportFormat.ZIP:
88 return 'application/zip';
89 case ExportFormat.PGP:
90 return 'application/pgp-encrypted';
91 case ExportFormat.CSV:
92 return 'text/csv;charset=utf-8;';
96 const createBase64Export = async (payload: ExportData, options: ExportOptions): Promise<string> => {
97 switch (options.format) {
98 case ExportFormat.ZIP:
99 return uint8ArrayToBase64String(await createPassExportZip(payload));
100 case ExportFormat.PGP:
101 return encryptPassExport(await createPassExportZip(payload), options.passphrase);
102 case ExportFormat.CSV:
103 return encodeUtf8Base64(createPassExportCSV(payload));
107 /** If data is encrypted, will export as PGP file instead of a ZIP.
108 * Returns a `TransferableFile` in case the data must be passed around
109 * different contexts (ie: from extension component to service worker) */
110 export const createPassExport = async (payload: ExportData, options: ExportOptions): Promise<TransferableFile> => {
111 const base64 = await createBase64Export(payload, options);
112 const type = getMimeType(options.format);
113 const timestamp = new Date().toISOString().split('T')[0];
114 const name = `${PASS_APP_NAME}_export_${timestamp}.${options.format}`;
116 return { base64, name, type };