From 162db0b6006c3f7c0f434d8729662e292c0e5fbf Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 4 Oct 2022 06:50:43 +1000 Subject: [PATCH] [EC-582] Add domain object serialization (#3623) --- .../spec/models/domain/attachment.spec.ts | 23 ++++++- libs/common/spec/models/domain/card.spec.ts | 32 ++++++++- libs/common/spec/models/domain/cipher.spec.ts | 66 ++++++++++++++++++- .../spec/models/domain/encString.spec.ts | 4 ++ libs/common/spec/models/domain/field.spec.ts | 24 ++++++- libs/common/spec/models/domain/folder.spec.ts | 3 +- .../spec/models/domain/identity.spec.ts | 56 +++++++++++++++- libs/common/spec/models/domain/login.spec.ts | 32 ++++++++- .../spec/models/domain/loginUri.spec.ts | 24 ++++++- .../spec/models/domain/password.spec.ts | 25 ++++++- .../spec/models/domain/secureNote.spec.ts | 6 ++ .../spec/models/view/attachmentView.spec.ts | 5 +- .../spec/models/view/cipherView.spec.ts | 4 +- .../common/spec/models/view/loginView.spec.ts | 5 +- libs/common/spec/utils.ts | 5 ++ libs/common/src/models/domain/attachment.ts | 16 +++++ libs/common/src/models/domain/card.ts | 23 +++++++ libs/common/src/models/domain/cipher.ts | 46 +++++++++++++ libs/common/src/models/domain/encString.ts | 4 ++ libs/common/src/models/domain/field.ts | 16 +++++ libs/common/src/models/domain/identity.ts | 48 ++++++++++++++ libs/common/src/models/domain/login.ts | 23 +++++++ libs/common/src/models/domain/loginUri.ts | 13 ++++ libs/common/src/models/domain/password.ts | 16 +++++ libs/common/src/models/domain/secureNote.ts | 10 +++ 25 files changed, 513 insertions(+), 16 deletions(-) diff --git a/libs/common/spec/models/domain/attachment.spec.ts b/libs/common/spec/models/domain/attachment.spec.ts index 835628d47d..322061987a 100644 --- a/libs/common/spec/models/domain/attachment.spec.ts +++ b/libs/common/spec/models/domain/attachment.spec.ts @@ -8,7 +8,7 @@ import { EncString } from "@bitwarden/common/models/domain/encString"; import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; import { ContainerService } from "@bitwarden/common/services/container.service"; -import { makeStaticByteArray, mockEnc } from "../../utils"; +import { makeStaticByteArray, mockEnc, mockFromJson } from "../../utils"; describe("Attachment", () => { let data: AttachmentData; @@ -131,4 +131,25 @@ describe("Attachment", () => { }); }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const actual = Attachment.fromJSON({ + key: "myKey", + fileName: "myFileName", + }); + + expect(actual).toEqual({ + key: "myKey_fromJSON", + fileName: "myFileName_fromJSON", + }); + expect(actual).toBeInstanceOf(Attachment); + }); + + it("returns null if object is null", () => { + expect(Attachment.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/card.spec.ts b/libs/common/spec/models/domain/card.spec.ts index 01357e005e..3b7e32b91a 100644 --- a/libs/common/spec/models/domain/card.spec.ts +++ b/libs/common/spec/models/domain/card.spec.ts @@ -1,7 +1,8 @@ import { CardData } from "@bitwarden/common/models/data/cardData"; import { Card } from "@bitwarden/common/models/domain/card"; +import { EncString } from "@bitwarden/common/models/domain/encString"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Card", () => { let data: CardData; @@ -70,4 +71,33 @@ describe("Card", () => { expYear: "expYear", }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const actual = Card.fromJSON({ + cardholderName: "mockCardHolder", + brand: "mockBrand", + number: "mockNumber", + expMonth: "mockExpMonth", + expYear: "mockExpYear", + code: "mockCode", + }); + + expect(actual).toEqual({ + cardholderName: "mockCardHolder_fromJSON", + brand: "mockBrand_fromJSON", + number: "mockNumber_fromJSON", + expMonth: "mockExpMonth_fromJSON", + expYear: "mockExpYear_fromJSON", + code: "mockCode_fromJSON", + }); + expect(actual).toBeInstanceOf(Card); + }); + + it("returns null if object is null", () => { + expect(Card.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/cipher.spec.ts b/libs/common/spec/models/domain/cipher.spec.ts index 0158945d8c..47d0d4f547 100644 --- a/libs/common/spec/models/domain/cipher.spec.ts +++ b/libs/common/spec/models/domain/cipher.spec.ts @@ -1,4 +1,5 @@ import { Substitute, Arg } from "@fluffy-spoon/substitute"; +import { Jsonify } from "type-fest"; import { CipherRepromptType } from "@bitwarden/common/enums/cipherRepromptType"; import { CipherType } from "@bitwarden/common/enums/cipherType"; @@ -6,16 +7,20 @@ import { FieldType } from "@bitwarden/common/enums/fieldType"; import { SecureNoteType } from "@bitwarden/common/enums/secureNoteType"; import { UriMatchType } from "@bitwarden/common/enums/uriMatchType"; import { CipherData } from "@bitwarden/common/models/data/cipherData"; +import { Attachment } from "@bitwarden/common/models/domain/attachment"; import { Card } from "@bitwarden/common/models/domain/card"; import { Cipher } from "@bitwarden/common/models/domain/cipher"; +import { EncString } from "@bitwarden/common/models/domain/encString"; +import { Field } from "@bitwarden/common/models/domain/field"; import { Identity } from "@bitwarden/common/models/domain/identity"; import { Login } from "@bitwarden/common/models/domain/login"; +import { Password } from "@bitwarden/common/models/domain/password"; import { SecureNote } from "@bitwarden/common/models/domain/secureNote"; import { CardView } from "@bitwarden/common/models/view/cardView"; import { IdentityView } from "@bitwarden/common/models/view/identityView"; import { LoginView } from "@bitwarden/common/models/view/loginView"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Cipher DTO", () => { it("Convert from empty CipherData", () => { @@ -587,4 +592,63 @@ describe("Cipher DTO", () => { }); }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(Attachment, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(Field, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(Password, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const revisionDate = new Date("2022-08-04T01:06:40.441Z"); + const deletedDate = new Date("2022-09-04T01:06:40.441Z"); + const actual = Cipher.fromJSON({ + name: "myName", + notes: "myNotes", + revisionDate: revisionDate.toISOString(), + attachments: ["attachment1", "attachment2"] as any, + fields: ["field1", "field2"] as any, + passwordHistory: ["ph1", "ph2"] as any, + deletedDate: deletedDate.toISOString(), + } as Jsonify); + + expect(actual).toMatchObject({ + name: "myName_fromJSON", + notes: "myNotes_fromJSON", + revisionDate: revisionDate, + attachments: ["attachment1_fromJSON", "attachment2_fromJSON"], + fields: ["field1_fromJSON", "field2_fromJSON"], + passwordHistory: ["ph1_fromJSON", "ph2_fromJSON"], + deletedDate: deletedDate, + }); + expect(actual).toBeInstanceOf(Cipher); + }); + + test.each([ + // Test description, CipherType, expected output + ["LoginView", CipherType.Login, { login: "myLogin_fromJSON" }], + ["CardView", CipherType.Card, { card: "myCard_fromJSON" }], + ["IdentityView", CipherType.Identity, { identity: "myIdentity_fromJSON" }], + ["Secure Note", CipherType.SecureNote, { secureNote: "mySecureNote_fromJSON" }], + ])("initializes %s", (description: string, cipherType: CipherType, expected: any) => { + jest.spyOn(Login, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(Identity, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(Card, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(SecureNote, "fromJSON").mockImplementation(mockFromJson); + + const actual = Cipher.fromJSON({ + login: "myLogin", + card: "myCard", + identity: "myIdentity", + secureNote: "mySecureNote", + type: cipherType, + } as any); + + expect(actual).toMatchObject(expected); + }); + + it("returns null if object is null", () => { + expect(Cipher.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/encString.spec.ts b/libs/common/spec/models/domain/encString.spec.ts index 413d091d68..a35a02ecc2 100644 --- a/libs/common/spec/models/domain/encString.spec.ts +++ b/libs/common/spec/models/domain/encString.spec.ts @@ -226,5 +226,9 @@ describe("EncString", () => { expect(encString.toJSON()).toBe(encString.encryptedString); }); + + it("returns null if object is null", () => { + expect(EncString.fromJSON(null)).toBeNull(); + }); }); }); diff --git a/libs/common/spec/models/domain/field.spec.ts b/libs/common/spec/models/domain/field.spec.ts index 2902f7af7e..de1f184dad 100644 --- a/libs/common/spec/models/domain/field.spec.ts +++ b/libs/common/spec/models/domain/field.spec.ts @@ -1,8 +1,9 @@ import { FieldType } from "@bitwarden/common/enums/fieldType"; import { FieldData } from "@bitwarden/common/models/data/fieldData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { Field } from "@bitwarden/common/models/domain/field"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Field", () => { let data: FieldData; @@ -61,4 +62,25 @@ describe("Field", () => { showValue: false, }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const actual = Field.fromJSON({ + name: "myName", + value: "myValue", + }); + + expect(actual).toEqual({ + name: "myName_fromJSON", + value: "myValue_fromJSON", + }); + expect(actual).toBeInstanceOf(Field); + }); + + it("returns null if object is null", () => { + expect(Field.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/folder.spec.ts b/libs/common/spec/models/domain/folder.spec.ts index 7ae36f9b39..83e935d82a 100644 --- a/libs/common/spec/models/domain/folder.spec.ts +++ b/libs/common/spec/models/domain/folder.spec.ts @@ -2,7 +2,7 @@ import { FolderData } from "@bitwarden/common/models/data/folderData"; import { EncString } from "@bitwarden/common/models/domain/encString"; import { Folder } from "@bitwarden/common/models/domain/folder"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Folder", () => { let data: FolderData; @@ -42,7 +42,6 @@ describe("Folder", () => { describe("fromJSON", () => { jest.mock("@bitwarden/common/models/domain/encString"); - const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); it("initializes nested objects", () => { diff --git a/libs/common/spec/models/domain/identity.spec.ts b/libs/common/spec/models/domain/identity.spec.ts index 19f94582be..78eff0fc44 100644 --- a/libs/common/spec/models/domain/identity.spec.ts +++ b/libs/common/spec/models/domain/identity.spec.ts @@ -1,7 +1,8 @@ import { IdentityData } from "@bitwarden/common/models/data/identityData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { Identity } from "@bitwarden/common/models/domain/identity"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Identity", () => { let data: IdentityData; @@ -131,4 +132,57 @@ describe("Identity", () => { username: "mockUsername", }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const actual = Identity.fromJSON({ + firstName: "mockFirstName", + lastName: "mockLastName", + address1: "mockAddress1", + address2: "mockAddress2", + address3: "mockAddress3", + city: "mockCity", + company: "mockCompany", + country: "mockCountry", + email: "mockEmail", + licenseNumber: "mockLicenseNumber", + middleName: "mockMiddleName", + passportNumber: "mockPassportNumber", + phone: "mockPhone", + postalCode: "mockPostalCode", + ssn: "mockSsn", + state: "mockState", + title: "mockTitle", + username: "mockUsername", + }); + + expect(actual).toEqual({ + firstName: "mockFirstName_fromJSON", + lastName: "mockLastName_fromJSON", + address1: "mockAddress1_fromJSON", + address2: "mockAddress2_fromJSON", + address3: "mockAddress3_fromJSON", + city: "mockCity_fromJSON", + company: "mockCompany_fromJSON", + country: "mockCountry_fromJSON", + email: "mockEmail_fromJSON", + licenseNumber: "mockLicenseNumber_fromJSON", + middleName: "mockMiddleName_fromJSON", + passportNumber: "mockPassportNumber_fromJSON", + phone: "mockPhone_fromJSON", + postalCode: "mockPostalCode_fromJSON", + ssn: "mockSsn_fromJSON", + state: "mockState_fromJSON", + title: "mockTitle_fromJSON", + username: "mockUsername_fromJSON", + }); + expect(actual).toBeInstanceOf(Identity); + }); + + it("returns null if object is null", () => { + expect(Identity.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/login.spec.ts b/libs/common/spec/models/domain/login.spec.ts index b2235e7583..9234051f41 100644 --- a/libs/common/spec/models/domain/login.spec.ts +++ b/libs/common/spec/models/domain/login.spec.ts @@ -2,11 +2,12 @@ import { Substitute, Arg } from "@fluffy-spoon/substitute"; import { UriMatchType } from "@bitwarden/common/enums/uriMatchType"; import { LoginData } from "@bitwarden/common/models/data/loginData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { Login } from "@bitwarden/common/models/domain/login"; import { LoginUri } from "@bitwarden/common/models/domain/loginUri"; import { LoginUriView } from "@bitwarden/common/models/view/loginUriView"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Login DTO", () => { it("Convert from empty LoginData", () => { @@ -98,4 +99,33 @@ describe("Login DTO", () => { expect(loginData).toEqual(data); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + jest.spyOn(LoginUri, "fromJSON").mockImplementation(mockFromJson); + const passwordRevisionDate = new Date("2022-01-31T12:00:00.000Z"); + + const actual = Login.fromJSON({ + uris: ["loginUri1", "loginUri2"] as any, + username: "myUsername", + password: "myPassword", + passwordRevisionDate: passwordRevisionDate.toISOString(), + totp: "myTotp", + }); + + expect(actual).toEqual({ + uris: ["loginUri1_fromJSON", "loginUri2_fromJSON"] as any, + username: "myUsername_fromJSON", + password: "myPassword_fromJSON", + passwordRevisionDate: passwordRevisionDate, + totp: "myTotp_fromJSON", + }); + expect(actual).toBeInstanceOf(Login); + }); + + it("returns null if object is null", () => { + expect(Login.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/loginUri.spec.ts b/libs/common/spec/models/domain/loginUri.spec.ts index 059910917d..50a6859f98 100644 --- a/libs/common/spec/models/domain/loginUri.spec.ts +++ b/libs/common/spec/models/domain/loginUri.spec.ts @@ -1,8 +1,11 @@ +import { Jsonify } from "type-fest"; + import { UriMatchType } from "@bitwarden/common/enums/uriMatchType"; import { LoginUriData } from "@bitwarden/common/models/data/loginUriData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { LoginUri } from "@bitwarden/common/models/domain/loginUri"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("LoginUri", () => { let data: LoginUriData; @@ -54,4 +57,23 @@ describe("LoginUri", () => { match: 3, }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + + const actual = LoginUri.fromJSON({ + uri: "myUri", + } as Jsonify); + + expect(actual).toEqual({ + uri: "myUri_fromJSON", + }); + expect(actual).toBeInstanceOf(LoginUri); + }); + + it("returns null if object is null", () => { + expect(LoginUri.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/password.spec.ts b/libs/common/spec/models/domain/password.spec.ts index b24f15059e..bbe29c9a87 100644 --- a/libs/common/spec/models/domain/password.spec.ts +++ b/libs/common/spec/models/domain/password.spec.ts @@ -1,7 +1,8 @@ import { PasswordHistoryData } from "@bitwarden/common/models/data/passwordHistoryData"; +import { EncString } from "@bitwarden/common/models/domain/encString"; import { Password } from "@bitwarden/common/models/domain/password"; -import { mockEnc } from "../../utils"; +import { mockEnc, mockFromJson } from "../../utils"; describe("Password", () => { let data: PasswordHistoryData; @@ -48,4 +49,26 @@ describe("Password", () => { lastUsedDate: new Date("2022-01-31T12:00:00.000Z"), }); }); + + describe("fromJSON", () => { + it("initializes nested objects", () => { + jest.spyOn(EncString, "fromJSON").mockImplementation(mockFromJson); + const lastUsedDate = new Date("2022-01-31T12:00:00.000Z"); + + const actual = Password.fromJSON({ + password: "myPassword", + lastUsedDate: lastUsedDate.toISOString(), + }); + + expect(actual).toEqual({ + password: "myPassword_fromJSON", + lastUsedDate: lastUsedDate, + }); + expect(actual).toBeInstanceOf(Password); + }); + + it("returns null if object is null", () => { + expect(Password.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/domain/secureNote.spec.ts b/libs/common/spec/models/domain/secureNote.spec.ts index 592c991d80..3117961102 100644 --- a/libs/common/spec/models/domain/secureNote.spec.ts +++ b/libs/common/spec/models/domain/secureNote.spec.ts @@ -43,4 +43,10 @@ describe("SecureNote", () => { type: 0, }); }); + + describe("fromJSON", () => { + it("returns null if object is null", () => { + expect(SecureNote.fromJSON(null)).toBeNull(); + }); + }); }); diff --git a/libs/common/spec/models/view/attachmentView.spec.ts b/libs/common/spec/models/view/attachmentView.spec.ts index d94456daa9..5784b4b4ff 100644 --- a/libs/common/spec/models/view/attachmentView.spec.ts +++ b/libs/common/spec/models/view/attachmentView.spec.ts @@ -1,12 +1,13 @@ import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey"; import { AttachmentView } from "@bitwarden/common/models/view/attachmentView"; +import { mockFromJson } from "../../utils"; + jest.mock("@bitwarden/common/models/domain/symmetricCryptoKey"); describe("AttachmentView", () => { it("fromJSON initializes nested objects", () => { - const mockFromJson = (stub: string) => stub + "_fromJSON"; - jest.spyOn(SymmetricCryptoKey, "fromJSON").mockImplementation(mockFromJson as any); + jest.spyOn(SymmetricCryptoKey, "fromJSON").mockImplementation(mockFromJson); const actual = AttachmentView.fromJSON({ key: "encKeyB64" as any, diff --git a/libs/common/spec/models/view/cipherView.spec.ts b/libs/common/spec/models/view/cipherView.spec.ts index f69bb089dc..1362babc3b 100644 --- a/libs/common/spec/models/view/cipherView.spec.ts +++ b/libs/common/spec/models/view/cipherView.spec.ts @@ -8,6 +8,8 @@ import { LoginView } from "@bitwarden/common/models/view/loginView"; import { PasswordHistoryView } from "@bitwarden/common/models/view/passwordHistoryView"; import { SecureNoteView } from "@bitwarden/common/models/view/secureNoteView"; +import { mockFromJson } from "../../utils"; + jest.mock("@bitwarden/common/models/view/loginView"); jest.mock("@bitwarden/common/models/view/attachmentView"); jest.mock("@bitwarden/common/models/view/fieldView"); @@ -22,8 +24,6 @@ describe("CipherView", () => { }); describe("fromJSON", () => { - const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; - it("initializes nested objects", () => { jest.spyOn(AttachmentView, "fromJSON").mockImplementation(mockFromJson); jest.spyOn(FieldView, "fromJSON").mockImplementation(mockFromJson); diff --git a/libs/common/spec/models/view/loginView.spec.ts b/libs/common/spec/models/view/loginView.spec.ts index e9b7627957..f50636b897 100644 --- a/libs/common/spec/models/view/loginView.spec.ts +++ b/libs/common/spec/models/view/loginView.spec.ts @@ -1,6 +1,8 @@ import { LoginUriView } from "@bitwarden/common/models/view/loginUriView"; import { LoginView } from "@bitwarden/common/models/view/loginView"; +import { mockFromJson } from "../../utils"; + jest.mock("@bitwarden/common/models/view/loginUriView"); describe("LoginView", () => { @@ -9,8 +11,7 @@ describe("LoginView", () => { }); it("fromJSON initializes nested objects", () => { - const mockFromJson = (stub: string) => stub + "_fromJSON"; - jest.spyOn(LoginUriView, "fromJSON").mockImplementation(mockFromJson as any); + jest.spyOn(LoginUriView, "fromJSON").mockImplementation(mockFromJson); const passwordRevisionDate = new Date(); diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index 4f9dc4076f..d3729d14e5 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -35,3 +35,8 @@ export function makeStaticByteArray(length: number, start = 0) { } return arr; } + +/** + * Use to mock a return value of a static fromJSON method. + */ +export const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; diff --git a/libs/common/src/models/domain/attachment.ts b/libs/common/src/models/domain/attachment.ts index 6696a0f753..4ac8fed62e 100644 --- a/libs/common/src/models/domain/attachment.ts +++ b/libs/common/src/models/domain/attachment.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { Utils } from "../../misc/utils"; import { AttachmentData } from "../data/attachmentData"; import { AttachmentView } from "../view/attachmentView"; @@ -90,4 +92,18 @@ export class Attachment extends Domain { ); return a; } + + static fromJSON(obj: Partial>): Attachment { + if (obj == null) { + return null; + } + + const key = EncString.fromJSON(obj.key); + const fileName = EncString.fromJSON(obj.fileName); + + return Object.assign(new Attachment(), obj, { + key, + fileName, + }); + } } diff --git a/libs/common/src/models/domain/card.ts b/libs/common/src/models/domain/card.ts index 59f73f9028..4c09532e03 100644 --- a/libs/common/src/models/domain/card.ts +++ b/libs/common/src/models/domain/card.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { CardData } from "../data/cardData"; import { CardView } from "../view/cardView"; @@ -62,4 +64,25 @@ export class Card extends Domain { }); return c; } + + static fromJSON(obj: Partial>): Card { + if (obj == null) { + return null; + } + + const cardholderName = EncString.fromJSON(obj.cardholderName); + const brand = EncString.fromJSON(obj.brand); + const number = EncString.fromJSON(obj.number); + const expMonth = EncString.fromJSON(obj.expMonth); + const expYear = EncString.fromJSON(obj.expYear); + const code = EncString.fromJSON(obj.code); + return Object.assign(new Card(), obj, { + cardholderName, + brand, + number, + expMonth, + expYear, + code, + }); + } } diff --git a/libs/common/src/models/domain/cipher.ts b/libs/common/src/models/domain/cipher.ts index 132bcaf3bd..ca792926c0 100644 --- a/libs/common/src/models/domain/cipher.ts +++ b/libs/common/src/models/domain/cipher.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { CipherRepromptType } from "../../enums/cipherRepromptType"; import { CipherType } from "../../enums/cipherType"; import { CipherData } from "../data/cipherData"; @@ -234,4 +236,48 @@ export class Cipher extends Domain { } return c; } + + static fromJSON(obj: Jsonify) { + if (obj == null) { + return null; + } + + const domain = new Cipher(); + const name = EncString.fromJSON(obj.name); + const notes = EncString.fromJSON(obj.notes); + const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); + const attachments = obj.attachments?.map((a: any) => Attachment.fromJSON(a)); + const fields = obj.fields?.map((f: any) => Field.fromJSON(f)); + const passwordHistory = obj.passwordHistory?.map((ph: any) => Password.fromJSON(ph)); + + Object.assign(domain, obj, { + name, + notes, + revisionDate, + deletedDate, + attachments, + fields, + passwordHistory, + }); + + switch (obj.type) { + case CipherType.Card: + domain.card = Card.fromJSON(obj.card); + break; + case CipherType.Identity: + domain.identity = Identity.fromJSON(obj.identity); + break; + case CipherType.Login: + domain.login = Login.fromJSON(obj.login); + break; + case CipherType.SecureNote: + domain.secureNote = SecureNote.fromJSON(obj.secureNote); + break; + default: + break; + } + + return domain; + } } diff --git a/libs/common/src/models/domain/encString.ts b/libs/common/src/models/domain/encString.ts index c828f4aa52..25a5bbbf8f 100644 --- a/libs/common/src/models/domain/encString.ts +++ b/libs/common/src/models/domain/encString.ts @@ -44,6 +44,10 @@ export class EncString implements IEncrypted { } static fromJSON(obj: Jsonify): EncString { + if (obj == null) { + return null; + } + return new EncString(obj); } diff --git a/libs/common/src/models/domain/field.ts b/libs/common/src/models/domain/field.ts index 71dc615afd..ea242ce24f 100644 --- a/libs/common/src/models/domain/field.ts +++ b/libs/common/src/models/domain/field.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { FieldType } from "../../enums/fieldType"; import { LinkedIdType } from "../../enums/linkedIdType"; import { FieldData } from "../data/fieldData"; @@ -59,4 +61,18 @@ export class Field extends Domain { ); return f; } + + static fromJSON(obj: Partial>): Field { + if (obj == null) { + return null; + } + + const name = EncString.fromJSON(obj.name); + const value = EncString.fromJSON(obj.value); + + return Object.assign(new Field(), obj, { + name, + value, + }); + } } diff --git a/libs/common/src/models/domain/identity.ts b/libs/common/src/models/domain/identity.ts index 4af228499b..075f4c8c4f 100644 --- a/libs/common/src/models/domain/identity.ts +++ b/libs/common/src/models/domain/identity.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { IdentityData } from "../data/identityData"; import { IdentityView } from "../view/identityView"; @@ -110,4 +112,50 @@ export class Identity extends Domain { }); return i; } + + static fromJSON(obj: Jsonify): Identity { + if (obj == null) { + return null; + } + + const title = EncString.fromJSON(obj.title); + const firstName = EncString.fromJSON(obj.firstName); + const middleName = EncString.fromJSON(obj.middleName); + const lastName = EncString.fromJSON(obj.lastName); + const address1 = EncString.fromJSON(obj.address1); + const address2 = EncString.fromJSON(obj.address2); + const address3 = EncString.fromJSON(obj.address3); + const city = EncString.fromJSON(obj.city); + const state = EncString.fromJSON(obj.state); + const postalCode = EncString.fromJSON(obj.postalCode); + const country = EncString.fromJSON(obj.country); + const company = EncString.fromJSON(obj.company); + const email = EncString.fromJSON(obj.email); + const phone = EncString.fromJSON(obj.phone); + const ssn = EncString.fromJSON(obj.ssn); + const username = EncString.fromJSON(obj.username); + const passportNumber = EncString.fromJSON(obj.passportNumber); + const licenseNumber = EncString.fromJSON(obj.licenseNumber); + + return Object.assign(new Identity(), obj, { + title, + firstName, + middleName, + lastName, + address1, + address2, + address3, + city, + state, + postalCode, + country, + company, + email, + phone, + ssn, + username, + passportNumber, + licenseNumber, + }); + } } diff --git a/libs/common/src/models/domain/login.ts b/libs/common/src/models/domain/login.ts index 76ba402000..19b99e956a 100644 --- a/libs/common/src/models/domain/login.ts +++ b/libs/common/src/models/domain/login.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { LoginData } from "../data/loginData"; import { LoginView } from "../view/loginView"; @@ -85,4 +87,25 @@ export class Login extends Domain { return l; } + + static fromJSON(obj: Partial>): Login { + if (obj == null) { + return null; + } + + const username = EncString.fromJSON(obj.username); + const password = EncString.fromJSON(obj.password); + const totp = EncString.fromJSON(obj.totp); + const passwordRevisionDate = + obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); + const uris = obj.uris?.map((uri: any) => LoginUri.fromJSON(uri)); + + return Object.assign(new Login(), obj, { + username, + password, + totp, + passwordRevisionDate: passwordRevisionDate, + uris: uris, + }); + } } diff --git a/libs/common/src/models/domain/loginUri.ts b/libs/common/src/models/domain/loginUri.ts index 9bd78c655f..419268cd51 100644 --- a/libs/common/src/models/domain/loginUri.ts +++ b/libs/common/src/models/domain/loginUri.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { UriMatchType } from "../../enums/uriMatchType"; import { LoginUriData } from "../data/loginUriData"; import { LoginUriView } from "../view/loginUriView"; @@ -51,4 +53,15 @@ export class LoginUri extends Domain { ); return u; } + + static fromJSON(obj: Jsonify): LoginUri { + if (obj == null) { + return null; + } + + const uri = EncString.fromJSON(obj.uri); + return Object.assign(new LoginUri(), obj, { + uri, + }); + } } diff --git a/libs/common/src/models/domain/password.ts b/libs/common/src/models/domain/password.ts index ae47b18db8..81ae30551f 100644 --- a/libs/common/src/models/domain/password.ts +++ b/libs/common/src/models/domain/password.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { PasswordHistoryData } from "../data/passwordHistoryData"; import { PasswordHistoryView } from "../view/passwordHistoryView"; @@ -40,4 +42,18 @@ export class Password extends Domain { }); return ph; } + + static fromJSON(obj: Partial>): Password { + if (obj == null) { + return null; + } + + const password = EncString.fromJSON(obj.password); + const lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate); + + return Object.assign(new Password(), obj, { + password, + lastUsedDate, + }); + } } diff --git a/libs/common/src/models/domain/secureNote.ts b/libs/common/src/models/domain/secureNote.ts index 8bde3164e7..475ad5300c 100644 --- a/libs/common/src/models/domain/secureNote.ts +++ b/libs/common/src/models/domain/secureNote.ts @@ -1,3 +1,5 @@ +import { Jsonify } from "type-fest"; + import { SecureNoteType } from "../../enums/secureNoteType"; import { SecureNoteData } from "../data/secureNoteData"; import { SecureNoteView } from "../view/secureNoteView"; @@ -26,4 +28,12 @@ export class SecureNote extends Domain { n.type = this.type; return n; } + + static fromJSON(obj: Jsonify): SecureNote { + if (obj == null) { + return null; + } + + return Object.assign(new SecureNote(), obj); + } }