diff --git a/libs/common/spec/importers/enpass/enpass-json-importer.spec.ts b/libs/common/spec/importers/enpass/enpass-json-importer.spec.ts new file mode 100644 index 0000000000..c4ed631206 --- /dev/null +++ b/libs/common/spec/importers/enpass/enpass-json-importer.spec.ts @@ -0,0 +1,133 @@ +import { CipherType } from "@bitwarden/common/enums/cipherType"; +import { EnpassJsonImporter as Importer } from "@bitwarden/common/importers/enpass/enpass-json-importer"; +import { FieldView } from "@bitwarden/common/models/view/field.view"; + +import { creditCard } from "./test-data/json/credit-card"; +import { folders } from "./test-data/json/folders"; +import { login } from "./test-data/json/login"; +import { loginAndroidUrl } from "./test-data/json/login-android-url"; +import { note } from "./test-data/json/note"; + +function validateCustomField(fields: FieldView[], fieldName: string, expectedValue: any) { + expect(fields).toBeDefined(); + const customField = fields.find((f) => f.name === fieldName); + expect(customField).toBeDefined(); + + expect(customField.value).toEqual(expectedValue); +} + +describe("Enpass JSON Importer", () => { + it("should create folders/ nested folder and assignment", async () => { + const importer = new Importer(); + const testDataString = JSON.stringify(folders); + const result = await importer.parse(testDataString); + expect(result != null).toBe(true); + + expect(result.folders.length).toEqual(2); + const folder1 = result.folders.shift(); + expect(folder1.name).toEqual("Social"); + + // Created 2 folders and Twitter is child of Social + const folder2 = result.folders.shift(); + expect(folder2.name).toEqual("Social/Twitter"); + + // Expect that the single cipher item is assigned to sub-folder "Social/Twitter" + const folderRelationship = result.folderRelationships.shift(); + expect(folderRelationship).toEqual([0, 1]); + }); + + it("should parse login items", async () => { + const importer = new Importer(); + const testDataString = JSON.stringify(login); + const result = await importer.parse(testDataString); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toEqual("Amazon"); + expect(cipher.subTitle).toEqual("emily@enpass.io"); + expect(cipher.favorite).toBe(true); + expect(cipher.notes).toEqual("some notes on the login item"); + + expect(cipher.login.username).toEqual("emily@enpass.io"); + expect(cipher.login.password).toEqual("$&W:v@}4\\iRpUXVbjPdPKDGbD"); + expect(cipher.login.totp).toEqual("TOTP_SEED_VALUE"); + expect(cipher.login.uris.length).toEqual(1); + const uriView = cipher.login.uris.shift(); + expect(uriView.uri).toEqual("https://www.amazon.com"); + + // remaining fields as custom fields + expect(cipher.fields.length).toEqual(3); + validateCustomField(cipher.fields, "Phone number", "12345678"); + validateCustomField(cipher.fields, "Security question", "SECURITY_QUESTION"); + validateCustomField(cipher.fields, "Security answer", "SECURITY_ANSWER"); + }); + + it("should parse login items with Android Autofill information", async () => { + const importer = new Importer(); + const testDataString = JSON.stringify(loginAndroidUrl); + const result = await importer.parse(testDataString); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toEqual(CipherType.Login); + expect(cipher.name).toEqual("Amazon"); + + expect(cipher.login.uris.length).toEqual(5); + expect(cipher.login.uris[0].uri).toEqual("https://www.amazon.com"); + expect(cipher.login.uris[1].uri).toEqual("androidapp://com.amazon.0"); + expect(cipher.login.uris[2].uri).toEqual("androidapp://com.amazon.1"); + expect(cipher.login.uris[3].uri).toEqual("androidapp://com.amazon.2"); + expect(cipher.login.uris[4].uri).toEqual("androidapp://com.amazon.3"); + }); + + it("should parse credit card items", async () => { + const importer = new Importer(); + const testDataString = JSON.stringify(creditCard); + const result = await importer.parse(testDataString); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toEqual(CipherType.Card); + expect(cipher.name).toEqual("Emily Sample Credit Card"); + expect(cipher.subTitle).toEqual("Amex, *10005"); + expect(cipher.favorite).toBe(true); + expect(cipher.notes).toEqual("some notes on the credit card"); + + expect(cipher.card.cardholderName).toEqual("Emily Sample"); + expect(cipher.card.number).toEqual("3782 822463 10005"); + expect(cipher.card.brand).toEqual("Amex"); + expect(cipher.card.code).toEqual("1234"); + expect(cipher.card.expMonth).toEqual("3"); + expect(cipher.card.expYear).toEqual("23"); + + // remaining fields as custom fields + expect(cipher.fields.length).toEqual(9); + validateCustomField(cipher.fields, "PIN", "9874"); + validateCustomField(cipher.fields, "Username", "Emily_ENP"); + validateCustomField( + cipher.fields, + "Login password", + "nnn tug shoot selfish bon liars convent dusty minnow uncheck" + ); + validateCustomField(cipher.fields, "Website", "http://global.americanexpress.com/"); + validateCustomField(cipher.fields, "Issuing bank", "American Express"); + validateCustomField(cipher.fields, "Credit limit", "100000"); + validateCustomField(cipher.fields, "Withdrawal limit", "50000"); + validateCustomField(cipher.fields, "Interest rate", "1.5"); + validateCustomField(cipher.fields, "If lost, call", "12345678"); + }); + + it("should parse notes", async () => { + const importer = new Importer(); + const testDataString = JSON.stringify(note); + const result = await importer.parse(testDataString); + expect(result != null).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toEqual(CipherType.SecureNote); + expect(cipher.name).toEqual("some secure note title"); + expect(cipher.favorite).toBe(false); + expect(cipher.notes).toEqual("some secure note content"); + }); +}); diff --git a/libs/common/spec/importers/enpass/test-data/json/credit-card.ts b/libs/common/spec/importers/enpass/test-data/json/credit-card.ts new file mode 100644 index 0000000000..5b9b07fe91 --- /dev/null +++ b/libs/common/spec/importers/enpass/test-data/json/credit-card.ts @@ -0,0 +1,274 @@ +import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type"; + +export const creditCard: EnpassJsonFile = { + folders: [], + items: [ + { + archived: 0, + auto_submit: 1, + category: "creditcard", + createdAt: 1666449561, + favorite: 1, + fields: [ + { + deleted: 0, + history: [ + { + updated_at: 1534490234, + value: "Wendy Apple Seed", + }, + { + updated_at: 1535521811, + value: "Emma", + }, + { + updated_at: 1535522090, + value: "Emily", + }, + ], + label: "Cardholder", + order: 1, + sensitive: 0, + type: "ccName", + uid: 0, + updated_at: 1666449561, + value: "Emily Sample", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Type", + order: 2, + sensitive: 0, + type: "ccType", + uid: 17, + updated_at: 1666449561, + value: "American Express", + value_updated_at: 1666449561, + }, + { + deleted: 0, + history: [ + { + updated_at: 1534490234, + value: "1234 1234 5678 0000", + }, + ], + label: "Number", + order: 3, + sensitive: 0, + type: "ccNumber", + uid: 1, + updated_at: 1666449561, + value: "3782 822463 10005", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "CVC", + order: 4, + sensitive: 1, + type: "ccCvc", + uid: 2, + updated_at: 1666449561, + value: "1234", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "PIN", + order: 5, + sensitive: 1, + type: "ccPin", + uid: 3, + updated_at: 1666449561, + value: "9874", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Expiry date", + order: 6, + sensitive: 0, + type: "ccExpiry", + uid: 4, + updated_at: 1666449561, + value: "03/23", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "INTERNET BANKING", + order: 7, + sensitive: 0, + type: "section", + uid: 103, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + history: [ + { + updated_at: 1534490234, + value: "WendySeed", + }, + { + updated_at: 1535521811, + value: "Emma1", + }, + { + updated_at: 1535522182, + value: "Emily1", + }, + ], + label: "Username", + order: 8, + sensitive: 0, + type: "username", + uid: 15, + updated_at: 1666449561, + value: "Emily_ENP", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Login password", + order: 9, + sensitive: 1, + type: "password", + uid: 16, + updated_at: 1666449561, + value: "nnn tug shoot selfish bon liars convent dusty minnow uncheck", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Transaction password", + order: 10, + sensitive: 1, + type: "ccTxnpassword", + uid: 9, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Website", + order: 11, + sensitive: 0, + type: "url", + uid: 14, + updated_at: 1666449561, + value: "http://global.americanexpress.com/", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "ADDITIONAL DETAILS", + order: 12, + sensitive: 0, + type: "section", + uid: 104, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Issuing bank", + order: 13, + sensitive: 0, + type: "ccBankname", + uid: 6, + updated_at: 1666449561, + value: "American Express", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Issued on", + order: 14, + sensitive: 0, + type: "date", + uid: 7, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Valid from", + order: 15, + sensitive: 0, + type: "ccValidfrom", + uid: 18, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Credit limit", + order: 16, + sensitive: 0, + type: "numeric", + uid: 10, + updated_at: 1666449561, + value: "100000", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Withdrawal limit", + order: 17, + sensitive: 0, + type: "numeric", + uid: 11, + updated_at: 1666449561, + value: "50000", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Interest rate", + order: 18, + sensitive: 0, + type: "numeric", + uid: 12, + updated_at: 1666449561, + value: "1.5", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "If lost, call", + order: 19, + sensitive: 0, + type: "phone", + uid: 8, + updated_at: 1666449561, + value: "12345678", + value_updated_at: 1666449561, + }, + ], + icon: { + fav: "global.americanexpress.com", + image: { + file: "cc/american_express", + }, + type: 2, + uuid: "", + }, + note: "some notes on the credit card", + subtitle: "***** 0000", + template_type: "creditcard.default", + title: "Emily Sample Credit Card", + trashed: 0, + updated_at: 1666554351, + uuid: "dbbc741b-81d6-491a-9660-92995fd8958c", + }, + ], +}; diff --git a/libs/common/spec/importers/enpass/test-data/json/folders.ts b/libs/common/spec/importers/enpass/test-data/json/folders.ts new file mode 100644 index 0000000000..d12a9f6973 --- /dev/null +++ b/libs/common/spec/importers/enpass/test-data/json/folders.ts @@ -0,0 +1,45 @@ +import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type"; + +export const folders: EnpassJsonFile = { + folders: [ + { + icon: "1008", + parent_uuid: "", + title: "Social", + updated_at: 1666449561, + uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc", + }, + { + icon: "1008", + parent_uuid: "7b2ed0da-8cd9-445f-9a1a-490ca2b9ffbc", + title: "Twitter", + updated_at: 1666450857, + uuid: "7fe8a8bc-b848-4f9f-9870-c2936317e74d", + }, + ], + items: [ + { + archived: 0, + auto_submit: 1, + category: "note", + createdAt: 1666554621, + favorite: 0, + folders: ["7fe8a8bc-b848-4f9f-9870-c2936317e74d"], + icon: { + fav: "", + image: { + file: "misc/secure_note", + }, + type: 1, + uuid: "", + }, + note: "some secure note content", + subtitle: "", + template_type: "note.default", + title: "some secure note title", + trashed: 0, + updated_at: 1666554621, + uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6", + }, + ], +}; diff --git a/libs/common/spec/importers/enpass/test-data/json/login-android-url.ts b/libs/common/spec/importers/enpass/test-data/json/login-android-url.ts new file mode 100644 index 0000000000..10acec7846 --- /dev/null +++ b/libs/common/spec/importers/enpass/test-data/json/login-android-url.ts @@ -0,0 +1,79 @@ +import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type"; + +import { login } from "./login"; + +export const loginAndroidUrl: EnpassJsonFile = { + folders: [], + items: [ + { + archived: 0, + auto_submit: 1, + category: "login", + createdAt: 1666449561, + favorite: 1, + fields: [ + ...login.items[0].fields, + { + deleted: 0, + label: "Autofill Info", + order: 9, + sensitive: 0, + type: ".Android#", + uid: 7696, + updated_at: 1666551057, + value: "com.amazon.0", + value_updated_at: 1666551057, + }, + { + deleted: 0, + label: "Autofill Info 1", + order: 9, + sensitive: 0, + type: ".Android#", + uid: 7696, + updated_at: 1666551057, + value: + "android://pMUhLBalOhcc3yK-84sMiGc2U856FVVUhm8PZveoRfNFT3ocT1KWZlciAkF2ED--B5i_fMuNlC6JfPxcHk1AQg==@com.amazon.1", + value_updated_at: 1666551057, + }, + { + deleted: 0, + label: "Autofill Info2 ", + order: 9, + sensitive: 0, + type: ".Android#", + uid: 7696, + updated_at: 1666551057, + value: "android://com.amazon.2", + value_updated_at: 1666551057, + }, + { + deleted: 0, + label: "Autofill Info 3", + order: 9, + sensitive: 0, + type: ".Android#", + uid: 7696, + updated_at: 1666551057, + value: "androidapp://com.amazon.3", + value_updated_at: 1666551057, + }, + ], + icon: { + fav: "www.amazon.com", + image: { + file: "web/amazon.com", + }, + type: 1, + uuid: "", + }, + note: "some notes on the login item", + subtitle: "emily@enpass.io", + template_type: "login.default", + title: "Amazon", + trashed: 0, + updated_at: 1666449561, + uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992", + }, + ], +}; diff --git a/libs/common/spec/importers/enpass/test-data/json/login.ts b/libs/common/spec/importers/enpass/test-data/json/login.ts new file mode 100644 index 0000000000..62625b7cd0 --- /dev/null +++ b/libs/common/spec/importers/enpass/test-data/json/login.ts @@ -0,0 +1,130 @@ +import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type"; + +export const login: EnpassJsonFile = { + folders: [], + items: [ + { + archived: 0, + auto_submit: 1, + category: "login", + createdAt: 1666449561, + favorite: 1, + fields: [ + { + deleted: 0, + label: "Username", + order: 1, + sensitive: 0, + type: "username", + uid: 10, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "E-mail", + order: 2, + sensitive: 0, + type: "email", + uid: 12, + updated_at: 1666449561, + value: "emily@enpass.io", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Password", + order: 3, + sensitive: 1, + type: "password", + uid: 11, + updated_at: 1666449561, + value: "$&W:v@}4\\iRpUXVbjPdPKDGbD", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Website", + order: 4, + sensitive: 0, + type: "url", + uid: 13, + updated_at: 1666449561, + value: "https://www.amazon.com", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "ADDITIONAL DETAILS", + order: 5, + sensitive: 0, + type: "section", + uid: 101, + updated_at: 1666449561, + value: "", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Phone number", + order: 6, + sensitive: 0, + type: "phone", + uid: 14, + updated_at: 1666449561, + value: "12345678", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "TOTP", + order: 7, + sensitive: 0, + type: "totp", + uid: 102, + updated_at: 1666449561, + value: "TOTP_SEED_VALUE", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Security question", + order: 8, + sensitive: 0, + type: "text", + uid: 15, + updated_at: 1666449561, + value: "SECURITY_QUESTION", + value_updated_at: 1666449561, + }, + { + deleted: 0, + label: "Security answer", + order: 9, + sensitive: 1, + type: "text", + uid: 16, + updated_at: 1666449561, + value: "SECURITY_ANSWER", + value_updated_at: 1666449561, + }, + ], + icon: { + fav: "www.amazon.com", + image: { + file: "web/amazon.com", + }, + type: 1, + uuid: "", + }, + note: "some notes on the login item", + subtitle: "emily@enpass.io", + template_type: "login.default", + title: "Amazon", + trashed: 0, + updated_at: 1666449561, + uuid: "f717cb7c-6cce-4b24-b023-ec8a429cc992", + }, + ], +}; diff --git a/libs/common/spec/importers/enpass/test-data/json/note.ts b/libs/common/spec/importers/enpass/test-data/json/note.ts new file mode 100644 index 0000000000..2ca1e50927 --- /dev/null +++ b/libs/common/spec/importers/enpass/test-data/json/note.ts @@ -0,0 +1,29 @@ +import { EnpassJsonFile } from "@bitwarden/common/importers/enpass/types/enpass-json-type"; + +export const note: EnpassJsonFile = { + folders: [], + items: [ + { + archived: 0, + auto_submit: 1, + category: "note", + createdAt: 1666554621, + favorite: 0, + icon: { + fav: "", + image: { + file: "misc/secure_note", + }, + type: 1, + uuid: "", + }, + note: "some secure note content", + subtitle: "", + template_type: "note.default", + title: "some secure note title", + trashed: 0, + updated_at: 1666554621, + uuid: "8b5ea2f6-f62b-4fec-a235-4a40946026b6", + }, + ], +}; diff --git a/libs/common/src/importers/enpass-csv-importer.ts b/libs/common/src/importers/enpass/enpass-csv-importer.ts similarity index 91% rename from libs/common/src/importers/enpass-csv-importer.ts rename to libs/common/src/importers/enpass/enpass-csv-importer.ts index d913c439ee..a73430ba8a 100644 --- a/libs/common/src/importers/enpass-csv-importer.ts +++ b/libs/common/src/importers/enpass/enpass-csv-importer.ts @@ -1,11 +1,10 @@ -import { CipherType } from "../enums/cipherType"; -import { SecureNoteType } from "../enums/secureNoteType"; -import { ImportResult } from "../models/domain/import-result"; -import { CardView } from "../models/view/card.view"; -import { SecureNoteView } from "../models/view/secure-note.view"; - -import { BaseImporter } from "./base-importer"; -import { Importer } from "./importer"; +import { CipherType } from "../../enums/cipherType"; +import { SecureNoteType } from "../../enums/secureNoteType"; +import { ImportResult } from "../../models/domain/import-result"; +import { CardView } from "../../models/view/card.view"; +import { SecureNoteView } from "../../models/view/secure-note.view"; +import { BaseImporter } from "../base-importer"; +import { Importer } from "../importer"; export class EnpassCsvImporter extends BaseImporter implements Importer { parse(data: string): Promise { diff --git a/libs/common/src/importers/enpass-json-importer.ts b/libs/common/src/importers/enpass/enpass-json-importer.ts similarity index 72% rename from libs/common/src/importers/enpass-json-importer.ts rename to libs/common/src/importers/enpass/enpass-json-importer.ts index 88778c8ade..8dbf164ce4 100644 --- a/libs/common/src/importers/enpass-json-importer.ts +++ b/libs/common/src/importers/enpass/enpass-json-importer.ts @@ -1,17 +1,21 @@ -import { CipherType } from "../enums/cipherType"; -import { FieldType } from "../enums/fieldType"; -import { ImportResult } from "../models/domain/import-result"; -import { CardView } from "../models/view/card.view"; -import { CipherView } from "../models/view/cipher.view"; -import { FolderView } from "../models/view/folder.view"; +import { CipherType } from "../../enums/cipherType"; +import { FieldType } from "../../enums/fieldType"; +import { ImportResult } from "../../models/domain/import-result"; +import { CardView } from "../../models/view/card.view"; +import { CipherView } from "../../models/view/cipher.view"; +import { FolderView } from "../../models/view/folder.view"; +import { BaseImporter } from "../base-importer"; +import { Importer } from "../importer"; -import { BaseImporter } from "./base-importer"; -import { Importer } from "./importer"; +import { EnpassJsonFile, EnpassFolder, EnpassField } from "./types/enpass-json-type"; + +type EnpassFolderTreeItem = EnpassFolder & { children: EnpassFolderTreeItem[] }; +const androidUrlRegex = new RegExp("androidapp://.*==@", "g"); export class EnpassJsonImporter extends BaseImporter implements Importer { parse(data: string): Promise { const result = new ImportResult(); - const results = JSON.parse(data); + const results: EnpassJsonFile = JSON.parse(data); if (results == null || results.items == null || results.items.length === 0) { result.success = false; return Promise.resolve(result); @@ -28,7 +32,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { result.folders.push(f); }); - results.items.forEach((item: any) => { + results.items.forEach((item) => { if (item.folders != null && item.folders.length > 0 && foldersIndexMap.has(item.folders[0])) { result.folderRelationships.push([ result.ciphers.length, @@ -50,7 +54,7 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { this.processCard(cipher, item.fields); } else if ( item.template_type.indexOf("identity.") < 0 && - item.fields.some((f: any) => f.type === "password" && !this.isNullOrWhitespace(f.value)) + item.fields.some((f) => f.type === "password" && !this.isNullOrWhitespace(f.value)) ) { this.processLogin(cipher, item.fields); } else { @@ -68,9 +72,9 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { return Promise.resolve(result); } - private processLogin(cipher: CipherView, fields: any[]) { + private processLogin(cipher: CipherView, fields: EnpassField[]) { const urls: string[] = []; - fields.forEach((field: any) => { + fields.forEach((field) => { if (this.isNullOrWhitespace(field.value) || field.type === "section") { return; } @@ -86,6 +90,13 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { cipher.login.totp = field.value; } else if (field.type === "url") { urls.push(field.value); + } else if (field.type === ".Android#") { + let cleanedValue = field.value.startsWith("androidapp://") + ? field.value + : "androidapp://" + field.value; + cleanedValue = cleanedValue.replace("android://", ""); + cleanedValue = cleanedValue.replace(androidUrlRegex, "androidapp://"); + urls.push(cleanedValue); } else { this.processKvp( cipher, @@ -98,10 +109,10 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { cipher.login.uris = this.makeUriArray(urls); } - private processCard(cipher: CipherView, fields: any[]) { + private processCard(cipher: CipherView, fields: EnpassField[]) { cipher.card = new CardView(); cipher.type = CipherType.Card; - fields.forEach((field: any) => { + fields.forEach((field) => { if ( this.isNullOrWhitespace(field.value) || field.type === "section" || @@ -137,8 +148,8 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { }); } - private processNote(cipher: CipherView, fields: any[]) { - fields.forEach((field: any) => { + private processNote(cipher: CipherView, fields: EnpassField[]) { + fields.forEach((field) => { if (this.isNullOrWhitespace(field.value) || field.type === "section") { return; } @@ -151,17 +162,17 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { }); } - private buildFolderTree(folders: any[]): any[] { + private buildFolderTree(folders: EnpassFolder[]): EnpassFolderTreeItem[] { if (folders == null) { return []; } - const folderTree: any[] = []; - const map = new Map([]); - folders.forEach((obj: any) => { + const folderTree: EnpassFolderTreeItem[] = []; + const map = new Map([]); + folders.forEach((obj: EnpassFolderTreeItem) => { map.set(obj.uuid, obj); obj.children = []; }); - folders.forEach((obj: any) => { + folders.forEach((obj: EnpassFolderTreeItem) => { if (obj.parent_uuid != null && obj.parent_uuid !== "" && map.has(obj.parent_uuid)) { map.get(obj.parent_uuid).children.push(obj); } else { @@ -171,11 +182,15 @@ export class EnpassJsonImporter extends BaseImporter implements Importer { return folderTree; } - private flattenFolderTree(titlePrefix: string, tree: any[], map: Map) { + private flattenFolderTree( + titlePrefix: string, + tree: EnpassFolderTreeItem[], + map: Map + ) { if (tree == null) { return; } - tree.forEach((f: any) => { + tree.forEach((f) => { if (f.title != null && f.title.trim() !== "") { let title = f.title.trim(); if (titlePrefix != null && titlePrefix.trim() !== "") { diff --git a/libs/common/src/importers/enpass/types/enpass-item-templates.ts b/libs/common/src/importers/enpass/types/enpass-item-templates.ts new file mode 100644 index 0000000000..dc95ea8244 --- /dev/null +++ b/libs/common/src/importers/enpass/types/enpass-item-templates.ts @@ -0,0 +1,79 @@ +type Login = "login.default"; + +type CreditCard = "creditcard.default"; + +type Identity = "identity.default"; + +type Note = "note.default"; + +type Password = "password.default"; + +type Finance = + | "finance.stock" + | "finance.bankaccount" + | "finance.loan" + | "finance.mutualfund" + | "finance.insurance" + | "finance.other"; + +type License = "license.driving" | "license.hunting" | "license.software" | "license.other"; + +type Travel = + | "travel.passport" + | "travel.flightdetails" + | "travel.hotelreservation" + | "travel.visa" + | "travel.freqflyer" + | "travel.other"; + +type Computer = + | "computer.database" + | "computer.emailaccount" + | "computer.ftp" + | "computer.messaging" + | "computer.internetprovider" + | "computer.server" + | "computer.wifi" + | "computer.hosting" + | "computer.other"; + +type Misc = + | "misc.Aadhar" + | "misc.address" + | "misc.library" + | "misc.rewardprogram" + | "misc.lens" + | "misc.service" + | "misc.vehicleinfo" + | "misc.itic" + | "misc.itz" + | "misc.propertyinfo" + | "misc.clothsize" + | "misc.contact" + | "misc.membership" + | "misc.cellphone" + | "misc.emergencyno" + | "misc.pan" + | "misc.identity" + | "misc.regcode" + | "misc.prescription" + | "misc.serial" + | "misc.socialsecurityno" + | "misc.isic" + | "misc.calling" + | "misc.voicemail" + | "misc.voter" + | "misc.combilock" + | "misc.other"; + +export type EnpassItemTemplate = + | Login + | CreditCard + | Identity + | Note + | Password + | Finance + | License + | Travel + | Computer + | Misc; diff --git a/libs/common/src/importers/enpass/types/enpass-json-type.ts b/libs/common/src/importers/enpass/types/enpass-json-type.ts new file mode 100644 index 0000000000..663c1707be --- /dev/null +++ b/libs/common/src/importers/enpass/types/enpass-json-type.ts @@ -0,0 +1,85 @@ +import { EnpassItemTemplate } from "./enpass-item-templates"; + +export type EnpassJsonFile = { + folders: EnpassFolder[]; + items: EnpassItem[]; +}; + +export type EnpassFolder = { + icon: string; + parent_uuid: string; + title: string; + updated_at: number; + uuid: string; +}; + +export type EnpassItem = { + archived: number; + auto_submit: number; + category: string; + createdAt: number; + favorite: number; + fields?: EnpassField[]; + icon: Icon; + note: string; + subtitle: string; + template_type: EnpassItemTemplate; + title: string; + trashed: number; + updated_at: number; + uuid: string; + folders?: string[]; +}; + +export type EnpassFieldType = + | "text" + | "password" + | "pin" + | "numeric" + | "date" + | "email" + | "url" + | "phone" + | "username" + | "totp" + | "multiline" + | "ccName" + | "ccNumber" + | "ccCvc" + | "ccPin" + | "ccExpiry" + | "ccBankname" + | "ccTxnpassword" + | "ccType" + | "ccValidfrom" + | "section" + | ".Android#"; + +export type EnpassField = { + deleted: number; + history?: History[]; + label: string; + order: number; + sensitive: number; + type: EnpassFieldType; + uid: number; + updated_at: number; + value: string; + value_updated_at: number; +}; + +export type History = { + updated_at: number; + value: string; +}; + +export type Icon = { + fav: string; + image: Image; + type: number; + uuid: string; +}; + +export type Image = { + file: string; +}; diff --git a/libs/common/src/services/import.service.ts b/libs/common/src/services/import.service.ts index c04b3626fd..eec8edf6c5 100644 --- a/libs/common/src/services/import.service.ts +++ b/libs/common/src/services/import.service.ts @@ -28,8 +28,8 @@ import { CodebookCsvImporter } from "../importers/codebook-csv-importer"; import { DashlaneCsvImporter } from "../importers/dashlane/dashlane-csv-importer"; import { DashlaneJsonImporter } from "../importers/dashlane/dashlane-json-importer"; import { EncryptrCsvImporter } from "../importers/encryptr-csv-importer"; -import { EnpassCsvImporter } from "../importers/enpass-csv-importer"; -import { EnpassJsonImporter } from "../importers/enpass-json-importer"; +import { EnpassCsvImporter } from "../importers/enpass/enpass-csv-importer"; +import { EnpassJsonImporter } from "../importers/enpass/enpass-json-importer"; import { FirefoxCsvImporter } from "../importers/firefox-csv-importer"; import { FSecureFskImporter } from "../importers/fsecure/fsecure-fsk-importer"; import { GnomeJsonImporter } from "../importers/gnome-json-importer";