From 1e78820b8e9a80c36ddf9b56b59807047ed0021d Mon Sep 17 00:00:00 2001 From: jaasen-livefront Date: Tue, 4 Nov 2025 15:53:15 -0800 Subject: [PATCH] migrate all exports to strict types --- libs/common/src/models/export/card.export.ts | 24 +++---- .../common/src/models/export/cipher.export.ts | 31 ++++---- .../src/models/export/collection.export.ts | 6 +- .../models/export/fido2-credential.export.ts | 32 ++++----- libs/common/src/models/export/field.export.ts | 4 +- .../common/src/models/export/folder.export.ts | 2 +- .../src/models/export/identity.export.ts | 72 +++++++++---------- .../src/models/export/login-uri.export.ts | 2 +- libs/common/src/models/export/login.export.ts | 10 +-- .../models/export/password-history.export.ts | 2 +- .../src/models/export/ssh-key.export.ts | 6 +- libs/common/src/models/export/utils.ts | 8 +-- .../individual-vault-export.service.spec.ts | 32 ++++++--- .../individual-vault-export.service.ts | 4 +- 14 files changed, 123 insertions(+), 112 deletions(-) diff --git a/libs/common/src/models/export/card.export.ts b/libs/common/src/models/export/card.export.ts index fbf16fd4be4..13a6ab54911 100644 --- a/libs/common/src/models/export/card.export.ts +++ b/libs/common/src/models/export/card.export.ts @@ -36,23 +36,23 @@ export class CardExport { return domain; } - cardholderName: string = ""; - brand: string = ""; - number: string = ""; - expMonth: string = ""; - expYear: string = ""; - code: string = ""; + cardholderName?: string; + brand?: string; + number?: string; + expMonth?: string; + expYear?: string; + code?: string; constructor(o?: CardView | CardDomain) { if (o == null) { return; } - this.cardholderName = safeGetString(o.cardholderName ?? "") ?? ""; - this.brand = safeGetString(o.brand ?? "") ?? ""; - this.number = safeGetString(o.number ?? "") ?? ""; - this.expMonth = safeGetString(o.expMonth ?? "") ?? ""; - this.expYear = safeGetString(o.expYear ?? "") ?? ""; - this.code = safeGetString(o.code ?? "") ?? ""; + this.cardholderName = safeGetString(o.cardholderName); + this.brand = safeGetString(o.brand); + this.number = safeGetString(o.number); + this.expMonth = safeGetString(o.expMonth); + this.expYear = safeGetString(o.expYear); + this.code = safeGetString(o.code); } } diff --git a/libs/common/src/models/export/cipher.export.ts b/libs/common/src/models/export/cipher.export.ts index 6aa8125fcec..d68fa0bd9d4 100644 --- a/libs/common/src/models/export/cipher.export.ts +++ b/libs/common/src/models/export/cipher.export.ts @@ -31,11 +31,6 @@ export class CipherExport { req.notes = "Some notes about this item."; req.favorite = false; req.fields = []; - req.login = null; - req.secureNote = null; - req.card = null; - req.identity = null; - req.sshKey = null; req.reprompt = CipherRepromptType.None; req.passwordHistory = []; req.creationDate = new Date(); @@ -170,35 +165,35 @@ export class CipherExport { } type: CipherType = CipherType.Login; - folderId: string = ""; - organizationId: string = ""; + folderId?: string; + organizationId?: string; collectionIds: string[] = []; name: string = ""; - notes: string = ""; + notes?: string; favorite: boolean = false; fields: FieldExport[] = []; - login: LoginExport | null = null; - secureNote: SecureNoteExport | null = null; - card: CardExport | null = null; - identity: IdentityExport | null = null; - sshKey: SshKeyExport | null = null; + login?: LoginExport; + secureNote?: SecureNoteExport; + card?: CardExport; + identity?: IdentityExport; + sshKey?: SshKeyExport; reprompt: CipherRepromptType = CipherRepromptType.None; passwordHistory: PasswordHistoryExport[] = []; revisionDate: Date; creationDate: Date; deletedDate: Date; archivedDate: Date; - key: string = ""; + key?: string; // Use build method instead of ctor so that we can control order of JSON stringify for pretty print build(o: CipherView | CipherDomain) { - this.organizationId = o.organizationId ?? ""; - this.folderId = o.folderId ?? ""; + this.organizationId = o.organizationId; + this.folderId = o.folderId; this.type = o.type; this.reprompt = o.reprompt; - this.name = safeGetString(o.name) ?? ""; - this.notes = safeGetString(o.notes ?? "") ?? ""; + this.name = safeGetString(o.name); + this.notes = safeGetString(o.notes); if ("key" in o) { this.key = o.key?.encryptedString ?? ""; } diff --git a/libs/common/src/models/export/collection.export.ts b/libs/common/src/models/export/collection.export.ts index cf06ffd3be7..981dafdfd1a 100644 --- a/libs/common/src/models/export/collection.export.ts +++ b/libs/common/src/models/export/collection.export.ts @@ -43,12 +43,12 @@ export class CollectionExport { organizationId: OrganizationId; name: string; - externalId: string; + externalId?: string; // Use build method instead of ctor so that we can control order of JSON stringify for pretty print build(o: CollectionView | CollectionDomain) { this.organizationId = o.organizationId; - this.name = safeGetString(o.name) ?? ""; - this.externalId = o.externalId ?? ""; + this.name = safeGetString(o.name); + this.externalId = o.externalId; } } diff --git a/libs/common/src/models/export/fido2-credential.export.ts b/libs/common/src/models/export/fido2-credential.export.ts index 5dd71a0efc4..ce8d6cc9ed0 100644 --- a/libs/common/src/models/export/fido2-credential.export.ts +++ b/libs/common/src/models/export/fido2-credential.export.ts @@ -82,11 +82,11 @@ export class Fido2CredentialExport { keyCurve: string = ""; keyValue: string = ""; rpId: string = ""; - userHandle: string = ""; - userName: string = ""; + userHandle?: string; + userName?: string; counter: string = ""; - rpName: string = ""; - userDisplayName: string = ""; + rpName?: string; + userDisplayName?: string; discoverable: string = ""; creationDate: Date = new Date(); @@ -100,18 +100,18 @@ export class Fido2CredentialExport { return; } - this.credentialId = safeGetString(o.credentialId) ?? ""; - this.keyType = safeGetString(o.keyType) ?? ""; - this.keyAlgorithm = safeGetString(o.keyAlgorithm) ?? ""; - this.keyCurve = safeGetString(o.keyCurve) ?? ""; - this.keyValue = safeGetString(o.keyValue) ?? ""; - this.rpId = safeGetString(o.rpId) ?? ""; - this.userHandle = safeGetString(o.userHandle ?? "") ?? ""; - this.userName = safeGetString(o.userName ?? "") ?? ""; - this.counter = safeGetString(String(o.counter)) ?? ""; - this.rpName = safeGetString(o.rpName ?? "") ?? ""; - this.userDisplayName = safeGetString(o.userDisplayName ?? "") ?? ""; - this.discoverable = safeGetString(String(o.discoverable)) ?? ""; + this.credentialId = safeGetString(o.credentialId); + this.keyType = safeGetString(o.keyType); + this.keyAlgorithm = safeGetString(o.keyAlgorithm); + this.keyCurve = safeGetString(o.keyCurve); + this.keyValue = safeGetString(o.keyValue); + this.rpId = safeGetString(o.rpId); + this.userHandle = safeGetString(o.userHandle); + this.userName = safeGetString(o.userName); + this.counter = safeGetString(String(o.counter)); + this.rpName = safeGetString(o.rpName); + this.userDisplayName = safeGetString(o.userDisplayName); + this.discoverable = safeGetString(String(o.discoverable)); this.creationDate = o.creationDate; } } diff --git a/libs/common/src/models/export/field.export.ts b/libs/common/src/models/export/field.export.ts index 4d2e61b662e..2aca5277f34 100644 --- a/libs/common/src/models/export/field.export.ts +++ b/libs/common/src/models/export/field.export.ts @@ -40,8 +40,8 @@ export class FieldExport { return; } - this.name = safeGetString(o.name ?? "") ?? ""; - this.value = safeGetString(o.value ?? "") ?? ""; + this.name = safeGetString(o.name); + this.value = safeGetString(o.value); this.type = o.type; this.linkedId = o.linkedId; } diff --git a/libs/common/src/models/export/folder.export.ts b/libs/common/src/models/export/folder.export.ts index 127a5b8baba..550cfe2d89c 100644 --- a/libs/common/src/models/export/folder.export.ts +++ b/libs/common/src/models/export/folder.export.ts @@ -25,6 +25,6 @@ export class FolderExport { // Use build method instead of ctor so that we can control order of JSON stringify for pretty print build(o: FolderView | FolderDomain) { - this.name = safeGetString(o.name) ?? ""; + this.name = safeGetString(o.name); } } diff --git a/libs/common/src/models/export/identity.export.ts b/libs/common/src/models/export/identity.export.ts index bcf3de38b44..c61c859e87d 100644 --- a/libs/common/src/models/export/identity.export.ts +++ b/libs/common/src/models/export/identity.export.ts @@ -72,47 +72,47 @@ export class IdentityExport { return domain; } - title: string = ""; - firstName: string = ""; - middleName: string = ""; - lastName: string = ""; - address1: string = ""; - address2: string = ""; - address3: string = ""; - city: string = ""; - state: string = ""; - postalCode: string = ""; - country: string = ""; - company: string = ""; - email: string = ""; - phone: string = ""; - ssn: string = ""; - username: string = ""; - passportNumber: string = ""; - licenseNumber: string = ""; + title?: string; + firstName?: string; + middleName?: string; + lastName?: string; + address1?: string; + address2?: string; + address3?: string; + city?: string; + state?: string; + postalCode?: string; + country?: string; + company?: string; + email?: string; + phone?: string; + ssn?: string; + username?: string; + passportNumber?: string; + licenseNumber?: string; constructor(o?: IdentityView | IdentityDomain) { if (o == null) { return; } - this.title = safeGetString(o.title ?? "") ?? ""; - this.firstName = safeGetString(o.firstName ?? "") ?? ""; - this.middleName = safeGetString(o.middleName ?? "") ?? ""; - this.lastName = safeGetString(o.lastName ?? "") ?? ""; - this.address1 = safeGetString(o.address1 ?? "") ?? ""; - this.address2 = safeGetString(o.address2 ?? "") ?? ""; - this.address3 = safeGetString(o.address3 ?? "") ?? ""; - this.city = safeGetString(o.city ?? "") ?? ""; - this.state = safeGetString(o.state ?? "") ?? ""; - this.postalCode = safeGetString(o.postalCode ?? "") ?? ""; - this.country = safeGetString(o.country ?? "") ?? ""; - this.company = safeGetString(o.company ?? "") ?? ""; - this.email = safeGetString(o.email ?? "") ?? ""; - this.phone = safeGetString(o.phone ?? "") ?? ""; - this.ssn = safeGetString(o.ssn ?? "") ?? ""; - this.username = safeGetString(o.username ?? "") ?? ""; - this.passportNumber = safeGetString(o.passportNumber ?? "") ?? ""; - this.licenseNumber = safeGetString(o.licenseNumber ?? "") ?? ""; + this.title = safeGetString(o.title); + this.firstName = safeGetString(o.firstName); + this.middleName = safeGetString(o.middleName); + this.lastName = safeGetString(o.lastName); + this.address1 = safeGetString(o.address1); + this.address2 = safeGetString(o.address2); + this.address3 = safeGetString(o.address3); + this.city = safeGetString(o.city); + this.state = safeGetString(o.state); + this.postalCode = safeGetString(o.postalCode); + this.country = safeGetString(o.country); + this.company = safeGetString(o.company); + this.email = safeGetString(o.email); + this.phone = safeGetString(o.phone); + this.ssn = safeGetString(o.ssn); + this.username = safeGetString(o.username); + this.passportNumber = safeGetString(o.passportNumber); + this.licenseNumber = safeGetString(o.licenseNumber); } } diff --git a/libs/common/src/models/export/login-uri.export.ts b/libs/common/src/models/export/login-uri.export.ts index d33682ab740..0fedbb7b0ee 100644 --- a/libs/common/src/models/export/login-uri.export.ts +++ b/libs/common/src/models/export/login-uri.export.ts @@ -34,7 +34,7 @@ export class LoginUriExport { return; } - this.uri = safeGetString(o.uri ?? "") ?? ""; + this.uri = safeGetString(o.uri); if ("uriChecksum" in o) { this.uriChecksum = o.uriChecksum?.encryptedString; } diff --git a/libs/common/src/models/export/login.export.ts b/libs/common/src/models/export/login.export.ts index cee28389716..7c9bbd3ab30 100644 --- a/libs/common/src/models/export/login.export.ts +++ b/libs/common/src/models/export/login.export.ts @@ -34,8 +34,8 @@ export class LoginExport { if (req.uris != null) { domain.uris = req.uris.map((u) => LoginUriExport.toDomain(u)); } - domain.username = new EncString(req.username ?? ""); - domain.password = new EncString(req.password ?? ""); + domain.username = new EncString(req.username); + domain.password = new EncString(req.password); domain.totp = new EncString(req.totp ?? ""); // Fido2credentials are currently not supported for exports. @@ -61,8 +61,8 @@ export class LoginExport { this.fido2Credentials = o.fido2Credentials.map((key) => new Fido2CredentialExport(key)); } - this.username = safeGetString(o.username ?? "") ?? ""; - this.password = safeGetString(o.password ?? "") ?? ""; - this.totp = safeGetString(o.totp ?? "") ?? ""; + this.username = safeGetString(o.username); + this.password = safeGetString(o.password); + this.totp = safeGetString(o.totp); } } diff --git a/libs/common/src/models/export/password-history.export.ts b/libs/common/src/models/export/password-history.export.ts index f72aba5f32a..c28b753af47 100644 --- a/libs/common/src/models/export/password-history.export.ts +++ b/libs/common/src/models/export/password-history.export.ts @@ -32,7 +32,7 @@ export class PasswordHistoryExport { return; } - this.password = safeGetString(o.password ?? "") ?? ""; + this.password = safeGetString(o.password); this.lastUsedDate = o.lastUsedDate ?? new Date(); } } diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts index 53cf88b3931..9594c8c047b 100644 --- a/libs/common/src/models/export/ssh-key.export.ts +++ b/libs/common/src/models/export/ssh-key.export.ts @@ -36,8 +36,8 @@ export class SshKeyExport { return; } - this.privateKey = safeGetString(o.privateKey ?? "") ?? ""; - this.publicKey = safeGetString(o.publicKey ?? "") ?? ""; - this.keyFingerprint = safeGetString(o.keyFingerprint ?? "") ?? ""; + this.privateKey = safeGetString(o.privateKey); + this.publicKey = safeGetString(o.publicKey); + this.keyFingerprint = safeGetString(o.keyFingerprint); } } diff --git a/libs/common/src/models/export/utils.ts b/libs/common/src/models/export/utils.ts index 1b7a9e1f171..acd0a72849c 100644 --- a/libs/common/src/models/export/utils.ts +++ b/libs/common/src/models/export/utils.ts @@ -1,12 +1,12 @@ import { EncString } from "../../key-management/crypto/models/enc-string"; -export function safeGetString(value: string | EncString) { - if (value == null) { - return null; +export function safeGetString(value?: string | EncString) { + if (!value) { + return ""; } if (typeof value == "string") { return value; } - return value?.encryptedString; + return value?.encryptedString ?? ""; } diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 0d7be81aee2..7e9bab609e4 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -78,8 +78,8 @@ function generateCipherView(deleted: boolean) { }, LoginView, ), - collectionIds: null, - deletedDate: deleted ? new Date() : null, + collectionIds: [], + deletedDate: deleted ? new Date() : undefined, }, CipherView, ); @@ -98,8 +98,8 @@ function generateCipherDomain(deleted: boolean) { }, Login, ), - collectionIds: null, - deletedDate: deleted ? new Date() : null, + collectionIds: [], + deletedDate: deleted ? new Date() : undefined, }, Cipher, ); @@ -126,15 +126,19 @@ function generateFolder() { } function expectEqualCiphers(ciphers: CipherView[] | Cipher[], jsonResult: string) { - const actual = JSON.stringify(JSON.parse(jsonResult).items); - const items: CipherWithIdExport[] = []; + const parsed = JSON.parse(jsonResult); + const actualItems = sanitizeDates(parsed.items); + + const expected: CipherWithIdExport[] = []; ciphers.forEach((c: CipherView | Cipher) => { const item = new CipherWithIdExport(); item.build(c); - items.push(item); + expected.push(item); }); - expect(actual).toEqual(JSON.stringify(items)); + const expectedSanitized = sanitizeDates(expected); + + expect(JSON.stringify(actualItems)).toEqual(JSON.stringify(expectedSanitized)); } function expectEqualFolderViews(folderViews: FolderView[] | Folder[], jsonResult: string) { @@ -162,6 +166,18 @@ function expectEqualFolders(folders: Folder[], jsonResult: string) { expect(actual).toMatchObject(expected); } +function sanitizeDates(obj: T): T { + const dateKeyRegex = /Date$/i; + return JSON.parse( + JSON.stringify(obj, (key, value) => { + if (key && dateKeyRegex.test(key)) { + return undefined; // omit this property + } + return value; + }), + ); +} + describe("VaultExportService", () => { let exportService: IndividualVaultExportService; let cryptoFunctionService: MockProxy; diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index e7a97801e09..fd24dc74725 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -254,7 +254,7 @@ export class IndividualVaultExportService } const cipher = new CipherWithIdExport(); cipher.build(c); - cipher.collectionIds = null; + cipher.collectionIds = []; jsonDoc.items.push(cipher); }); @@ -316,7 +316,7 @@ export class IndividualVaultExportService } const cipher = new CipherWithIdExport(); cipher.build(c); - cipher.collectionIds = null; + cipher.collectionIds = []; jsonDoc.items.push(cipher); });