diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 507789ae7a..f9e2e1c534 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -133,19 +133,11 @@ describe("NotificationBackground", () => { expect(cipherView.name).toEqual("example.com"); expect(cipherView.login).toEqual({ - autofillOnPageLoad: null, - fido2Credentials: null, + fido2Credentials: [], password: message.password, - passwordRevisionDate: null, - totp: null, uris: [ { - _canLaunch: null, - _domain: null, - _host: null, - _hostname: null, _uri: message.uri, - match: null, }, ], username: message.username, diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5bfb12f193..e27b50f13c 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1271,7 +1271,6 @@ export default class NotificationBackground { cipherView.folderId = folderId; cipherView.type = CipherType.Login; cipherView.login = loginView; - cipherView.organizationId = null; return cipherView; } diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index aa631c44c6..5f888e081c 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -45,6 +45,7 @@ import { CipherViewLike, CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { BadgeModule, ButtonModule, @@ -168,6 +169,7 @@ export class VaultV2Component private organizations$: Observable = this.accountService.activeAccount$.pipe( map((a) => a?.id), + filterOutNullish(), switchMap((id) => this.organizationService.organizations$(id)), ); @@ -319,7 +321,7 @@ export class VaultV2Component this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); const authRequests = await firstValueFrom( - this.authRequestService.getLatestPendingAuthRequest$(), + this.authRequestService.getLatestPendingAuthRequest$()!, ); if (authRequests != null) { this.messagingService.send("openLoginApproval", { diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 3aab02b3b4..b961de9e24 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -978,7 +978,7 @@ export class VaultComponent implements OnInit, OnDestroy { // Allow restore of an Unassigned Item try { - if (c.id == null) { + if (c.id == null || c.id === "") { throw new Error("Cipher must have an Id to be restored"); } const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); @@ -1211,7 +1211,7 @@ export class VaultComponent implements OnInit, OnDestroy { aType = "Password"; value = cipher.login.password; typeI18nKey = "password"; - } else if (field === "totp") { + } else if (field === "totp" && cipher.login.totp != null) { aType = "TOTP"; const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); value = totpResponse.code; @@ -1232,7 +1232,7 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - if (!cipher.viewPassword) { + if (!cipher.viewPassword || value == null) { return; } diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 1a4141c4d6..bf2a528e72 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -89,6 +89,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple private async isPasswordExposed(cv: CipherView): Promise { const { login } = cv; + if (login.password == null) { + return null; + } return await this.auditService.passwordLeaked(login.password).then((exposedCount) => { if (exposedCount > 0) { return { ...cv, exposedXTimes: exposedCount } as ReportResult; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts index 487ac28e96..6afb0ee681 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/risk-insights-data-mappers.ts @@ -40,7 +40,7 @@ export function getTrimmedCipherUris(cipher: CipherView): string[] { const uniqueDomains = new Set(); - uris.forEach((u: { uri: string }) => { + uris.forEach((u: { uri: string | undefined }) => { const domain = Utils.getDomain(u.uri) ?? u.uri; uniqueDomains.add(domain); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts index 3904c4c386..2ad9f1c7cf 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/password-health.service.ts @@ -29,7 +29,7 @@ export class PasswordHealthService { filter((cipher) => this.isValidCipher(cipher)), mergeMap((cipher) => this.auditService - .passwordLeaked(cipher.login.password) + .passwordLeaked(cipher.login.password!) .then((exposedCount) => ({ cipher, exposedCount })), ), // [FIXME] ExposedDetails is can still return a null @@ -74,11 +74,11 @@ export class PasswordHealthService { // Check the username const userInput = this.isUserNameNotEmpty(cipher) - ? this.extractUsernameParts(cipher.login.username) + ? this.extractUsernameParts(cipher.login.username!) : undefined; const { score } = this.passwordStrengthService.getPasswordStrength( - cipher.login.password, + cipher.login.password!, undefined, // No email available in this context userInput, ); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index dc078d810c..fcfc7a255d 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -436,13 +436,13 @@ export class RiskInsightsReportService { const weakPassword = this.passwordHealthService.findWeakPasswordDetails(cipher); // Looping over all ciphers needs to happen first to determine reused passwords over all ciphers. // Store in the set and evaluate later - if (passwordUseMap.has(cipher.login.password)) { + if (passwordUseMap.has(cipher.login.password!)) { passwordUseMap.set( - cipher.login.password, - (passwordUseMap.get(cipher.login.password) || 0) + 1, + cipher.login.password!, + (passwordUseMap.get(cipher.login.password!) || 0) + 1, ); } else { - passwordUseMap.set(cipher.login.password, 1); + passwordUseMap.set(cipher.login.password!, 1); } const exposedPassword = exposedDetails.find((x) => x?.cipherId === cipher.id); @@ -466,7 +466,7 @@ export class RiskInsightsReportService { // loop for reused passwords cipherHealthReports.forEach((detail) => { - detail.reusedPasswordCount = passwordUseMap.get(detail.login.password) ?? 0; + detail.reusedPasswordCount = passwordUseMap.get(detail.login.password!) ?? 0; }); return cipherHealthReports; } @@ -514,7 +514,7 @@ export class RiskInsightsReportService { private _buildPasswordUseMap(ciphers: CipherView[]): Map { const passwordUseMap = new Map(); ciphers.forEach((cipher) => { - const password = cipher.login.password; + const password = cipher.login.password!; passwordUseMap.set(password, (passwordUseMap.get(password) || 0) + 1); }); return passwordUseMap; @@ -686,7 +686,7 @@ export class RiskInsightsReportService { healthData: { weakPasswordDetail: this.passwordHealthService.findWeakPasswordDetails(cipher), exposedPasswordDetail: exposedPassword, - reusedPasswordCount: passwordUseMap.get(cipher.login.password) ?? 0, + reusedPasswordCount: passwordUseMap.get(cipher.login.password!) ?? 0, }, applications: getTrimmedCipherUris(cipher), } as CipherHealthReport; diff --git a/libs/common/src/platform/misc/safe-urls.ts b/libs/common/src/platform/misc/safe-urls.ts index d7223a344e..f958f92aa1 100644 --- a/libs/common/src/platform/misc/safe-urls.ts +++ b/libs/common/src/platform/misc/safe-urls.ts @@ -17,13 +17,13 @@ const CanLaunchWhitelist = [ ]; export class SafeUrls { - static canLaunch(uri: string): boolean { + static canLaunch(uri: string | null | undefined): boolean { if (Utils.isNullOrWhitespace(uri)) { return false; } for (let i = 0; i < CanLaunchWhitelist.length; i++) { - if (uri.indexOf(CanLaunchWhitelist[i]) === 0) { + if (uri!.indexOf(CanLaunchWhitelist[i]) === 0) { return true; } } diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index c771bee546..5f977da397 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -375,7 +375,7 @@ export class Utils { } } - static getDomain(uriString: string): string { + static getDomain(uriString: string | null | undefined): string { if (Utils.isNullOrWhitespace(uriString)) { return null; } @@ -457,7 +457,7 @@ export class Utils { return str == null || typeof str !== "string" || str.trim() === ""; } - static isNullOrEmpty(str: string | null): boolean { + static isNullOrEmpty(str: string | null | undefined): boolean { return str == null || typeof str !== "string" || str == ""; } @@ -479,7 +479,7 @@ export class Utils { return (Object.keys(obj).filter((k) => Number.isNaN(+k)) as K[]).map((k) => obj[k]); } - static getUrl(uriString: string): URL { + static getUrl(uriString: string | undefined | null): URL { if (this.isNullOrWhitespace(uriString)) { return null; } diff --git a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts index 31f6ce10e0..a58b2d470e 100644 --- a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts +++ b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts @@ -27,8 +27,8 @@ export async function getCredentialsForAutofill( cipherId: cipher.id, credentialId: credId, rpId: credential.rpId, - userHandle: credential.userHandle, - userName: credential.userName, - }; + userHandle: credential.userHandle!, + userName: credential.userName!, + } satisfies Fido2CredentialAutofillView; }); } diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 5fff6b32aa..4ace8ce0e7 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -47,6 +47,7 @@ export class Attachment extends Domain { ): Promise { const view = await this.decryptObj( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new AttachmentView(this), ["fileName"], orgId, diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index 5a134651e3..4da62c631d 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -63,7 +63,6 @@ describe("Card", () => { expect(view).toEqual({ _brand: "brand", _number: "number", - _subTitle: null, cardholderName: "cardHolder", code: "code", expMonth: "expMonth", diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index c4ee35b2b8..8ba81c7bbd 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -161,6 +161,8 @@ export class Cipher extends Domain implements Decryptable { await this.decryptObj( this, + // @ts-expect-error Ciphers have optional Ids which are getting swallowed by the ViewEncryptableKeys type + // The ViewEncryptableKeys type should be fixed to allow for optional Ids, but is out of scope for now. model, ["name", "notes"], this.organizationId, diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index a74afc2336..bdfac9a85a 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -56,6 +56,7 @@ export class Fido2Credential extends Domain { async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { const view = await this.decryptObj( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new Fido2CredentialView(), [ "credentialId", diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index f652a2820d..130d1cc56d 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -39,6 +39,7 @@ export class Field extends Domain { decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise { return this.decryptObj( this, + // @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now. new FieldView(this), ["name", "value"], orgId, diff --git a/libs/common/src/vault/models/domain/identity.spec.ts b/libs/common/src/vault/models/domain/identity.spec.ts index c122c90371..9fbcb92e4a 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -112,7 +112,6 @@ describe("Identity", () => { expect(view).toEqual({ _firstName: "mockFirstName", _lastName: "mockLastName", - _subTitle: null, address1: "mockAddress1", address2: "mockAddress2", address3: "mockAddress3", diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index cbab41f147..e67ba77141 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -56,10 +56,6 @@ describe("LoginUri", () => { const view = await loginUri.decrypt(null); expect(view).toEqual({ - _canLaunch: null, - _domain: null, - _host: null, - _hostname: null, _uri: "uri", match: 3, }); diff --git a/libs/common/src/vault/models/domain/login.spec.ts b/libs/common/src/vault/models/domain/login.spec.ts index dc3cc71fda..99ceb2b0a3 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -2,7 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended"; import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; -import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; +import { UriMatchStrategy } from "../../../models/domain/domain-service"; import { LoginData } from "../../models/data/login.data"; import { Login } from "../../models/domain/login"; import { LoginUri } from "../../models/domain/login-uri"; @@ -82,12 +82,7 @@ describe("Login DTO", () => { totp: "encrypted totp", uris: [ { - match: null as UriMatchStrategySetting, _uri: "decrypted uri", - _domain: null as string, - _hostname: null as string, - _host: null as string, - _canLaunch: null as boolean, }, ], autofillOnPageLoad: true, diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index 1c796c8f27..ef4a9ed8b2 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; @@ -10,12 +8,12 @@ import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-cr import { Attachment } from "../domain/attachment"; export class AttachmentView implements View { - id: string = null; - url: string = null; - size: string = null; - sizeName: string = null; - fileName: string = null; - key: SymmetricCryptoKey = null; + id?: string; + url?: string; + size?: string; + sizeName?: string; + fileName?: string; + key?: SymmetricCryptoKey; /** * The SDK returns an encrypted key for the attachment. */ @@ -35,7 +33,7 @@ export class AttachmentView implements View { get fileSize(): number { try { if (this.size != null) { - return parseInt(this.size, null); + return parseInt(this.size); } } catch { // Invalid file size. @@ -71,7 +69,7 @@ export class AttachmentView implements View { fileName: this.fileName, key: this.encryptedKey?.toSdk(), // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete - decryptedKey: this.key ? this.key.toBase64() : null, + decryptedKey: this.key ? this.key.toBase64() : undefined, }; } @@ -84,13 +82,13 @@ export class AttachmentView implements View { } const view = new AttachmentView(); - view.id = obj.id ?? null; - view.url = obj.url ?? null; - view.size = obj.size ?? null; - view.sizeName = obj.sizeName ?? null; - view.fileName = obj.fileName ?? null; + view.id = obj.id; + view.url = obj.url; + view.size = obj.size; + view.sizeName = obj.sizeName; + view.fileName = obj.fileName; // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete - view.key = obj.decryptedKey ? SymmetricCryptoKey.fromString(obj.decryptedKey) : null; + view.key = obj.decryptedKey ? SymmetricCryptoKey.fromString(obj.decryptedKey) : undefined; view.encryptedKey = obj.key ? new EncString(obj.key) : undefined; return view; diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index ed02fa6836..9b78ad384c 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { CardView as SdkCardView } from "@bitwarden/sdk-internal"; @@ -12,45 +10,45 @@ import { ItemView } from "./item.view"; export class CardView extends ItemView implements SdkCardView { @linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 }) - cardholderName: string = null; + cardholderName: string | undefined; @linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" }) - expMonth: string = null; + expMonth: string | undefined; @linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" }) - expYear: string = null; + expYear: string | undefined; @linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" }) - code: string = null; + code: string | undefined; - private _brand: string = null; - private _number: string = null; - private _subTitle: string = null; + private _brand?: string; + private _number?: string; + private _subTitle?: string; - get maskedCode(): string { - return this.code != null ? "•".repeat(this.code.length) : null; + get maskedCode(): string | undefined { + return this.code != null ? "•".repeat(this.code.length) : undefined; } - get maskedNumber(): string { - return this.number != null ? "•".repeat(this.number.length) : null; + get maskedNumber(): string | undefined { + return this.number != null ? "•".repeat(this.number.length) : undefined; } @linkedFieldOption(LinkedId.Brand, { sortPosition: 2 }) - get brand(): string { + get brand(): string | undefined { return this._brand; } - set brand(value: string) { + set brand(value: string | undefined) { this._brand = value; - this._subTitle = null; + this._subTitle = undefined; } @linkedFieldOption(LinkedId.Number, { sortPosition: 1 }) - get number(): string { + get number(): string | undefined { return this._number; } - set number(value: string) { + set number(value: string | undefined) { this._number = value; - this._subTitle = null; + this._subTitle = undefined; } - get subTitle(): string { + get subTitle(): string | undefined { if (this._subTitle == null) { this._subTitle = this.brand; if (this.number != null && this.number.length >= 4) { @@ -69,11 +67,11 @@ export class CardView extends ItemView implements SdkCardView { return this._subTitle; } - get expiration(): string { - const normalizedYear = normalizeExpiryYearFormat(this.expYear); + get expiration(): string | undefined { + const normalizedYear = this.expYear ? normalizeExpiryYearFormat(this.expYear) : undefined; if (!this.expMonth && !normalizedYear) { - return null; + return undefined; } let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__"; @@ -82,14 +80,14 @@ export class CardView extends ItemView implements SdkCardView { return exp; } - static fromJSON(obj: Partial>): CardView { + static fromJSON(obj: Partial> | undefined): CardView { return Object.assign(new CardView(), obj); } // ref https://stackoverflow.com/a/5911300 - static getCardBrandByPatterns(cardNum: string): string { + static getCardBrandByPatterns(cardNum: string | undefined | null): string | undefined { if (cardNum == null || typeof cardNum !== "string" || cardNum.trim() === "") { - return null; + return undefined; } // Visa @@ -146,25 +144,21 @@ export class CardView extends ItemView implements SdkCardView { return "Visa"; } - return null; + return undefined; } /** * Converts an SDK CardView to a CardView. */ - static fromSdkCardView(obj: SdkCardView): CardView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkCardView(obj: SdkCardView): CardView { const cardView = new CardView(); - cardView.cardholderName = obj.cardholderName ?? null; - cardView.brand = obj.brand ?? null; - cardView.number = obj.number ?? null; - cardView.expMonth = obj.expMonth ?? null; - cardView.expYear = obj.expYear ?? null; - cardView.code = obj.code ?? null; + cardView.cardholderName = obj.cardholderName; + cardView.brand = obj.brand; + cardView.number = obj.number; + cardView.expMonth = obj.expMonth; + cardView.expYear = obj.expYear; + cardView.code = obj.code; return cardView; } diff --git a/libs/common/src/vault/models/view/cipher.view.spec.ts b/libs/common/src/vault/models/view/cipher.view.spec.ts index e9614db685..2965a9b1c7 100644 --- a/libs/common/src/vault/models/view/cipher.view.spec.ts +++ b/libs/common/src/vault/models/view/cipher.view.spec.ts @@ -180,15 +180,12 @@ describe("CipherView", () => { folderId: "folderId", collectionIds: ["collectionId"], name: "name", - notes: null, type: CipherType.Login, favorite: true, edit: true, reprompt: CipherRepromptType.None, organizationUseTotp: false, viewPassword: true, - localData: undefined, - permissions: undefined, attachments: [ { id: "attachmentId", @@ -224,7 +221,6 @@ describe("CipherView", () => { passwordHistory: [], creationDate: new Date("2022-01-01T12:00:00.000Z"), revisionDate: new Date("2022-01-02T12:00:00.000Z"), - deletedDate: null, }); }); }); @@ -283,18 +279,12 @@ describe("CipherView", () => { restore: true, delete: true, }, - deletedDate: undefined, creationDate: "2022-01-02T12:00:00.000Z", revisionDate: "2022-01-02T12:00:00.000Z", attachments: [], passwordHistory: [], - login: undefined, - identity: undefined, - card: undefined, - secureNote: undefined, - sshKey: undefined, fields: [], - } as SdkCipherView); + }); }); }); }); diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index b9f717b3a7..3381f0a47a 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,7 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { asUuid, uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; +import { ItemView } from "@bitwarden/common/vault/models/view/item.view"; import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal"; import { View } from "../../../models/view/view"; @@ -26,18 +25,18 @@ import { SshKeyView } from "./ssh-key.view"; export class CipherView implements View, InitializerMetadata { readonly initializerKey = InitializerKey.CipherView; - id: string = null; - organizationId: string | undefined = null; - folderId: string = null; - name: string = null; - notes: string = null; - type: CipherType = null; + id: string = ""; + organizationId?: string; + folderId?: string; + name: string = ""; + notes?: string; + type: CipherType = CipherType.Login; favorite = false; organizationUseTotp = false; - permissions: CipherPermissionsApi = new CipherPermissionsApi(); + permissions?: CipherPermissionsApi = new CipherPermissionsApi(); edit = false; viewPassword = true; - localData: LocalData; + localData?: LocalData; login = new LoginView(); identity = new IdentityView(); card = new CardView(); @@ -46,11 +45,11 @@ export class CipherView implements View, InitializerMetadata { attachments: AttachmentView[] = []; fields: FieldView[] = []; passwordHistory: PasswordHistoryView[] = []; - collectionIds: string[] = null; - revisionDate: Date = null; - creationDate: Date = null; - deletedDate: Date | null = null; - archivedDate: Date | null = null; + collectionIds: string[] = []; + revisionDate: Date; + creationDate: Date; + deletedDate?: Date; + archivedDate?: Date; reprompt: CipherRepromptType = CipherRepromptType.None; // We need a copy of the encrypted key so we can pass it to // the SdkCipherView during encryption @@ -63,6 +62,7 @@ export class CipherView implements View, InitializerMetadata { constructor(c?: Cipher) { if (!c) { + this.creationDate = this.revisionDate = new Date(); return; } @@ -86,7 +86,7 @@ export class CipherView implements View, InitializerMetadata { this.key = c.key; } - private get item() { + private get item(): ItemView | undefined { switch (this.type) { case CipherType.Login: return this.login; @@ -102,10 +102,10 @@ export class CipherView implements View, InitializerMetadata { break; } - return null; + return undefined; } - get subTitle(): string { + get subTitle(): string | undefined { return this.item?.subTitle; } @@ -114,7 +114,7 @@ export class CipherView implements View, InitializerMetadata { } get hasAttachments(): boolean { - return this.attachments && this.attachments.length > 0; + return !!this.attachments && this.attachments.length > 0; } get hasOldAttachments(): boolean { @@ -132,11 +132,11 @@ export class CipherView implements View, InitializerMetadata { return this.fields && this.fields.length > 0; } - get passwordRevisionDisplayDate(): Date { + get passwordRevisionDisplayDate(): Date | undefined { if (this.type !== CipherType.Login || this.login == null) { - return null; + return undefined; } else if (this.login.password == null || this.login.password === "") { - return null; + return undefined; } return this.login.passwordRevisionDate; } @@ -170,23 +170,17 @@ export class CipherView implements View, InitializerMetadata { * Determines if the cipher can be launched in a new browser tab. */ get canLaunch(): boolean { - return this.type === CipherType.Login && this.login.canLaunch; + return this.type === CipherType.Login && this.login!.canLaunch; } linkedFieldValue(id: LinkedIdType) { const linkedFieldOption = this.linkedFieldOptions?.get(id); - if (linkedFieldOption == null) { - return null; + const item = this.item; + if (linkedFieldOption == null || item == null) { + return undefined; } - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const item = this.item; - return this.item[linkedFieldOption.propertyKey as keyof typeof item]; - } - - linkedFieldI18nKey(id: LinkedIdType): string { - return this.linkedFieldOptions.get(id)?.i18nKey; + return item[linkedFieldOption.propertyKey as keyof typeof item]; } // This is used as a marker to indicate that the cipher view object still has its prototype @@ -194,23 +188,31 @@ export class CipherView implements View, InitializerMetadata { return this; } - static fromJSON(obj: Partial>): CipherView { + static fromJSON(obj: Partial>): CipherView | null { if (obj == null) { return null; } const view = new CipherView(); - const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); - const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); - const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); - const archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); - const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a)); - const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f)); - const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)); - const permissions = CipherPermissionsApi.fromJSON(obj.permissions); - let key: EncString | undefined; + view.type = obj.type ?? CipherType.Login; + view.id = obj.id ?? ""; + view.name = obj.name ?? ""; + if (obj.creationDate) { + view.creationDate = new Date(obj.creationDate); + } + if (obj.revisionDate) { + view.revisionDate = new Date(obj.revisionDate); + } + view.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate); + view.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate); + view.attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a)) ?? []; + view.fields = obj.fields?.map((f: any) => FieldView.fromJSON(f)) ?? []; + view.passwordHistory = + obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)) ?? []; + view.permissions = obj.permissions ? CipherPermissionsApi.fromJSON(obj.permissions) : undefined; if (obj.key != null) { + let key: EncString | undefined; if (typeof obj.key === "string") { // If the key is a string, we need to parse it as EncString key = EncString.fromJSON(obj.key); @@ -218,20 +220,9 @@ export class CipherView implements View, InitializerMetadata { // If the key is already an EncString instance, we can use it directly key = obj.key; } + view.key = key; } - Object.assign(view, obj, { - creationDate: creationDate, - revisionDate: revisionDate, - deletedDate: deletedDate, - archivedDate: archivedDate, - attachments: attachments, - fields: fields, - passwordHistory: passwordHistory, - permissions: permissions, - key: key, - }); - switch (obj.type) { case CipherType.Card: view.card = CardView.fromJSON(obj.card); @@ -264,46 +255,54 @@ export class CipherView implements View, InitializerMetadata { } const cipherView = new CipherView(); - cipherView.id = uuidAsString(obj.id) ?? null; - cipherView.organizationId = uuidAsString(obj.organizationId) ?? null; - cipherView.folderId = uuidAsString(obj.folderId) ?? null; + cipherView.id = uuidAsString(obj.id); + cipherView.organizationId = uuidAsString(obj.organizationId); + cipherView.folderId = uuidAsString(obj.folderId); cipherView.name = obj.name; - cipherView.notes = obj.notes ?? null; + cipherView.notes = obj.notes; cipherView.type = obj.type; cipherView.favorite = obj.favorite; cipherView.organizationUseTotp = obj.organizationUseTotp; - cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions); + cipherView.permissions = obj.permissions + ? CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions) + : undefined; cipherView.edit = obj.edit; cipherView.viewPassword = obj.viewPassword; cipherView.localData = fromSdkLocalData(obj.localData); cipherView.attachments = - obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? []; - cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? []; + obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)!) ?? []; + cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)!) ?? []; cipherView.passwordHistory = - obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? []; + obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)!) ?? []; cipherView.collectionIds = obj.collectionIds?.map((i) => uuidAsString(i)) ?? []; - cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); - cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); - cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); - cipherView.archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); + cipherView.revisionDate = new Date(obj.revisionDate); + cipherView.creationDate = new Date(obj.creationDate); + cipherView.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate); + cipherView.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate); cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; - cipherView.key = EncString.fromJSON(obj.key); + cipherView.key = obj.key ? EncString.fromJSON(obj.key) : undefined; switch (obj.type) { case CipherType.Card: - cipherView.card = CardView.fromSdkCardView(obj.card); + cipherView.card = obj.card ? CardView.fromSdkCardView(obj.card) : new CardView(); break; case CipherType.Identity: - cipherView.identity = IdentityView.fromSdkIdentityView(obj.identity); + cipherView.identity = obj.identity + ? IdentityView.fromSdkIdentityView(obj.identity) + : new IdentityView(); break; case CipherType.Login: - cipherView.login = LoginView.fromSdkLoginView(obj.login); + cipherView.login = obj.login ? LoginView.fromSdkLoginView(obj.login) : new LoginView(); break; case CipherType.SecureNote: - cipherView.secureNote = SecureNoteView.fromSdkSecureNoteView(obj.secureNote); + cipherView.secureNote = obj.secureNote + ? SecureNoteView.fromSdkSecureNoteView(obj.secureNote) + : new SecureNoteView(); break; case CipherType.SshKey: - cipherView.sshKey = SshKeyView.fromSdkSshKeyView(obj.sshKey); + cipherView.sshKey = obj.sshKey + ? SshKeyView.fromSdkSshKeyView(obj.sshKey) + : new SshKeyView(); break; default: break; @@ -354,19 +353,19 @@ export class CipherView implements View, InitializerMetadata { switch (this.type) { case CipherType.Card: - sdkCipherView.card = this.card.toSdkCardView(); + sdkCipherView.card = this.card?.toSdkCardView(); break; case CipherType.Identity: - sdkCipherView.identity = this.identity.toSdkIdentityView(); + sdkCipherView.identity = this.identity?.toSdkIdentityView(); break; case CipherType.Login: - sdkCipherView.login = this.login.toSdkLoginView(); + sdkCipherView.login = this.login?.toSdkLoginView(); break; case CipherType.SecureNote: - sdkCipherView.secureNote = this.secureNote.toSdkSecureNoteView(); + sdkCipherView.secureNote = this.secureNote?.toSdkSecureNoteView(); break; case CipherType.SshKey: - sdkCipherView.sshKey = this.sshKey.toSdkSshKeyView(); + sdkCipherView.sshKey = this.sshKey?.toSdkSshKeyView(); break; default: break; diff --git a/libs/common/src/vault/models/view/fido2-credential.view.ts b/libs/common/src/vault/models/view/fido2-credential.view.ts index 410757ebe3..19e7f5d7e3 100644 --- a/libs/common/src/vault/models/view/fido2-credential.view.ts +++ b/libs/common/src/vault/models/view/fido2-credential.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { @@ -10,21 +8,55 @@ import { import { ItemView } from "./item.view"; export class Fido2CredentialView extends ItemView { - credentialId: string; - keyType: "public-key"; - keyAlgorithm: "ECDSA"; - keyCurve: "P-256"; - keyValue: string; - rpId: string; - userHandle: string; - userName: string; - counter: number; - rpName: string; - userDisplayName: string; - discoverable: boolean; - creationDate: Date = null; + credentialId!: string; + keyType!: "public-key"; + keyAlgorithm!: "ECDSA"; + keyCurve!: "P-256"; + keyValue!: string; + rpId!: string; + userHandle?: string; + userName?: string; + counter!: number; + rpName?: string; + userDisplayName?: string; + discoverable: boolean = false; + creationDate!: Date; - get subTitle(): string { + constructor(f?: { + credentialId: string; + keyType: "public-key"; + keyAlgorithm: "ECDSA"; + keyCurve: "P-256"; + keyValue: string; + rpId: string; + userHandle?: string; + userName?: string; + counter: number; + rpName?: string; + userDisplayName?: string; + discoverable?: boolean; + creationDate: Date; + }) { + super(); + if (f == null) { + return; + } + this.credentialId = f.credentialId; + this.keyType = f.keyType; + this.keyAlgorithm = f.keyAlgorithm; + this.keyCurve = f.keyCurve; + this.keyValue = f.keyValue; + this.rpId = f.rpId; + this.userHandle = f.userHandle; + this.userName = f.userName; + this.counter = f.counter; + this.rpName = f.rpName; + this.userDisplayName = f.userDisplayName; + this.discoverable = f.discoverable ?? false; + this.creationDate = f.creationDate; + } + + get subTitle(): string | undefined { return this.userDisplayName; } @@ -43,21 +75,21 @@ export class Fido2CredentialView extends ItemView { return undefined; } - const view = new Fido2CredentialView(); - view.credentialId = obj.credentialId; - view.keyType = obj.keyType as "public-key"; - view.keyAlgorithm = obj.keyAlgorithm as "ECDSA"; - view.keyCurve = obj.keyCurve as "P-256"; - view.rpId = obj.rpId; - view.userHandle = obj.userHandle; - view.userName = obj.userName; - view.counter = parseInt(obj.counter); - view.rpName = obj.rpName; - view.userDisplayName = obj.userDisplayName; - view.discoverable = obj.discoverable?.toLowerCase() === "true" ? true : false; - view.creationDate = obj.creationDate ? new Date(obj.creationDate) : null; - - return view; + return new Fido2CredentialView({ + credentialId: obj.credentialId, + keyType: obj.keyType as "public-key", + keyAlgorithm: obj.keyAlgorithm as "ECDSA", + keyCurve: obj.keyCurve as "P-256", + keyValue: obj.keyValue, + rpId: obj.rpId, + userHandle: obj.userHandle, + userName: obj.userName, + counter: parseInt(obj.counter), + rpName: obj.rpName, + userDisplayName: obj.userDisplayName, + discoverable: obj.discoverable?.toLowerCase() === "true", + creationDate: new Date(obj.creationDate), + }); } toSdkFido2CredentialFullView(): Fido2CredentialFullView { diff --git a/libs/common/src/vault/models/view/field.view.ts b/libs/common/src/vault/models/view/field.view.ts index 8c9a923aed..9f34420a86 100644 --- a/libs/common/src/vault/models/view/field.view.ts +++ b/libs/common/src/vault/models/view/field.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { FieldView as SdkFieldView, FieldType as SdkFieldType } from "@bitwarden/sdk-internal"; @@ -9,13 +7,13 @@ import { FieldType, LinkedIdType } from "../../enums"; import { Field } from "../domain/field"; export class FieldView implements View { - name: string = null; - value: string = null; - type: FieldType = null; + name?: string; + value?: string; + type: FieldType = FieldType.Text; newField = false; // Marks if the field is new and hasn't been saved showValue = false; showCount = false; - linkedId: LinkedIdType = null; + linkedId?: LinkedIdType; constructor(f?: Field) { if (!f) { @@ -26,8 +24,8 @@ export class FieldView implements View { this.linkedId = f.linkedId; } - get maskedValue(): string { - return this.value != null ? "••••••••" : null; + get maskedValue(): string | undefined { + return this.value != null ? "••••••••" : undefined; } static fromJSON(obj: Partial>): FieldView { diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 2b863dc5e5..5fb0d1acba 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal"; @@ -12,65 +10,65 @@ import { ItemView } from "./item.view"; export class IdentityView extends ItemView implements SdkIdentityView { @linkedFieldOption(LinkedId.Title, { sortPosition: 0 }) - title: string = null; + title: string | undefined; @linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 }) - middleName: string = null; + middleName: string | undefined; @linkedFieldOption(LinkedId.Address1, { sortPosition: 12 }) - address1: string = null; + address1: string | undefined; @linkedFieldOption(LinkedId.Address2, { sortPosition: 13 }) - address2: string = null; + address2: string | undefined; @linkedFieldOption(LinkedId.Address3, { sortPosition: 14 }) - address3: string = null; + address3: string | undefined; @linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" }) - city: string = null; + city: string | undefined; @linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" }) - state: string = null; + state: string | undefined; @linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" }) - postalCode: string = null; + postalCode: string | undefined; @linkedFieldOption(LinkedId.Country, { sortPosition: 18 }) - country: string = null; + country: string | undefined; @linkedFieldOption(LinkedId.Company, { sortPosition: 6 }) - company: string = null; + company: string | undefined; @linkedFieldOption(LinkedId.Email, { sortPosition: 10 }) - email: string = null; + email: string | undefined; @linkedFieldOption(LinkedId.Phone, { sortPosition: 11 }) - phone: string = null; + phone: string | undefined; @linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 }) - ssn: string = null; + ssn: string | undefined; @linkedFieldOption(LinkedId.Username, { sortPosition: 5 }) - username: string = null; + username: string | undefined; @linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 }) - passportNumber: string = null; + passportNumber: string | undefined; @linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 }) - licenseNumber: string = null; + licenseNumber: string | undefined; - private _firstName: string = null; - private _lastName: string = null; - private _subTitle: string = null; + private _firstName: string | undefined; + private _lastName: string | undefined; + private _subTitle: string | undefined; constructor() { super(); } @linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 }) - get firstName(): string { + get firstName(): string | undefined { return this._firstName; } - set firstName(value: string) { + set firstName(value: string | undefined) { this._firstName = value; - this._subTitle = null; + this._subTitle = undefined; } @linkedFieldOption(LinkedId.LastName, { sortPosition: 4 }) - get lastName(): string { + get lastName(): string | undefined { return this._lastName; } - set lastName(value: string) { + set lastName(value: string | undefined) { this._lastName = value; - this._subTitle = null; + this._subTitle = undefined; } - get subTitle(): string { + get subTitle(): string | undefined { if (this._subTitle == null && (this.firstName != null || this.lastName != null)) { this._subTitle = ""; if (this.firstName != null) { @@ -88,7 +86,7 @@ export class IdentityView extends ItemView implements SdkIdentityView { } @linkedFieldOption(LinkedId.FullName, { sortPosition: 3 }) - get fullName(): string { + get fullName(): string | undefined { if ( this.title != null || this.firstName != null || @@ -111,11 +109,11 @@ export class IdentityView extends ItemView implements SdkIdentityView { return name.trim(); } - return null; + return undefined; } - get fullAddress(): string { - let address = this.address1; + get fullAddress(): string | undefined { + let address = this.address1 ?? ""; if (!Utils.isNullOrWhitespace(this.address2)) { if (!Utils.isNullOrWhitespace(address)) { address += ", "; @@ -131,9 +129,9 @@ export class IdentityView extends ItemView implements SdkIdentityView { return address; } - get fullAddressPart2(): string { + get fullAddressPart2(): string | undefined { if (this.city == null && this.state == null && this.postalCode == null) { - return null; + return undefined; } const city = this.city || "-"; const state = this.state; @@ -146,7 +144,7 @@ export class IdentityView extends ItemView implements SdkIdentityView { return addressPart2; } - get fullAddressForCopy(): string { + get fullAddressForCopy(): string | undefined { let address = this.fullAddress; if (this.city != null || this.state != null || this.postalCode != null) { address += "\n" + this.fullAddressPart2; @@ -157,38 +155,34 @@ export class IdentityView extends ItemView implements SdkIdentityView { return address; } - static fromJSON(obj: Partial>): IdentityView { + static fromJSON(obj: Partial> | undefined): IdentityView { return Object.assign(new IdentityView(), obj); } /** * Converts the SDK IdentityView to an IdentityView. */ - static fromSdkIdentityView(obj: SdkIdentityView): IdentityView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkIdentityView(obj: SdkIdentityView): IdentityView { const identityView = new IdentityView(); - identityView.title = obj.title ?? null; - identityView.firstName = obj.firstName ?? null; - identityView.middleName = obj.middleName ?? null; - identityView.lastName = obj.lastName ?? null; - identityView.address1 = obj.address1 ?? null; - identityView.address2 = obj.address2 ?? null; - identityView.address3 = obj.address3 ?? null; - identityView.city = obj.city ?? null; - identityView.state = obj.state ?? null; - identityView.postalCode = obj.postalCode ?? null; - identityView.country = obj.country ?? null; - identityView.company = obj.company ?? null; - identityView.email = obj.email ?? null; - identityView.phone = obj.phone ?? null; - identityView.ssn = obj.ssn ?? null; - identityView.username = obj.username ?? null; - identityView.passportNumber = obj.passportNumber ?? null; - identityView.licenseNumber = obj.licenseNumber ?? null; + identityView.title = obj.title; + identityView.firstName = obj.firstName; + identityView.middleName = obj.middleName; + identityView.lastName = obj.lastName; + identityView.address1 = obj.address1; + identityView.address2 = obj.address2; + identityView.address3 = obj.address3; + identityView.city = obj.city; + identityView.state = obj.state; + identityView.postalCode = obj.postalCode; + identityView.country = obj.country; + identityView.company = obj.company; + identityView.email = obj.email; + identityView.phone = obj.phone; + identityView.ssn = obj.ssn; + identityView.username = obj.username; + identityView.passportNumber = obj.passportNumber; + identityView.licenseNumber = obj.licenseNumber; return identityView; } diff --git a/libs/common/src/vault/models/view/item.view.ts b/libs/common/src/vault/models/view/item.view.ts index 3954276ca0..d25901f804 100644 --- a/libs/common/src/vault/models/view/item.view.ts +++ b/libs/common/src/vault/models/view/item.view.ts @@ -1,9 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { View } from "../../../models/view/view"; import { LinkedMetadata } from "../../linked-field-option.decorator"; export abstract class ItemView implements View { - linkedFieldOptions: Map; - abstract get subTitle(): string; + linkedFieldOptions?: Map; + abstract get subTitle(): string | undefined; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index 49ac9c6278..bf8dcc83b3 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal"; @@ -11,13 +9,13 @@ import { Utils } from "../../../platform/misc/utils"; import { LoginUri } from "../domain/login-uri"; export class LoginUriView implements View { - match: UriMatchStrategySetting = null; + match?: UriMatchStrategySetting; - private _uri: string = null; - private _domain: string = null; - private _hostname: string = null; - private _host: string = null; - private _canLaunch: boolean = null; + private _uri?: string; + private _domain?: string; + private _hostname?: string; + private _host?: string; + private _canLaunch?: boolean; constructor(u?: LoginUri) { if (!u) { @@ -27,59 +25,59 @@ export class LoginUriView implements View { this.match = u.match; } - get uri(): string { + get uri(): string | undefined { return this._uri; } - set uri(value: string) { + set uri(value: string | undefined) { this._uri = value; - this._domain = null; - this._canLaunch = null; + this._domain = undefined; + this._canLaunch = undefined; } - get domain(): string { + get domain(): string | undefined { if (this._domain == null && this.uri != null) { this._domain = Utils.getDomain(this.uri); if (this._domain === "") { - this._domain = null; + this._domain = undefined; } } return this._domain; } - get hostname(): string { + get hostname(): string | undefined { if (this.match === UriMatchStrategy.RegularExpression) { - return null; + return undefined; } if (this._hostname == null && this.uri != null) { this._hostname = Utils.getHostname(this.uri); if (this._hostname === "") { - this._hostname = null; + this._hostname = undefined; } } return this._hostname; } - get host(): string { + get host(): string | undefined { if (this.match === UriMatchStrategy.RegularExpression) { - return null; + return undefined; } if (this._host == null && this.uri != null) { this._host = Utils.getHost(this.uri); if (this._host === "") { - this._host = null; + this._host = undefined; } } return this._host; } - get hostnameOrUri(): string { + get hostnameOrUri(): string | undefined { return this.hostname != null ? this.hostname : this.uri; } - get hostOrUri(): string { + get hostOrUri(): string | undefined { return this.host != null ? this.host : this.uri; } @@ -104,7 +102,10 @@ export class LoginUriView implements View { return this._canLaunch; } - get launchUri(): string { + get launchUri(): string | undefined { + if (this.uri == null) { + return undefined; + } return this.uri.indexOf("://") < 0 && !Utils.isNullOrWhitespace(Utils.getDomain(this.uri)) ? "http://" + this.uri : this.uri; @@ -141,7 +142,7 @@ export class LoginUriView implements View { matchesUri( targetUri: string, equivalentDomains: Set, - defaultUriMatch: UriMatchStrategySetting = null, + defaultUriMatch?: UriMatchStrategySetting, /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ overrideNeverMatchStrategy?: true, ): boolean { @@ -198,7 +199,7 @@ export class LoginUriView implements View { if (Utils.DomainMatchBlacklist.has(this.domain)) { const domainUrlHost = Utils.getHost(targetUri); - return !Utils.DomainMatchBlacklist.get(this.domain).has(domainUrlHost); + return !Utils.DomainMatchBlacklist.get(this.domain)!.has(domainUrlHost); } return true; diff --git a/libs/common/src/vault/models/view/login.view.spec.ts b/libs/common/src/vault/models/view/login.view.spec.ts index 57e82faf7f..ec011bed43 100644 --- a/libs/common/src/vault/models/view/login.view.spec.ts +++ b/libs/common/src/vault/models/view/login.view.spec.ts @@ -29,11 +29,6 @@ describe("LoginView", () => { }); describe("fromSdkLoginView", () => { - it("should return undefined when the input is null", () => { - const result = LoginView.fromSdkLoginView(null as unknown as SdkLoginView); - expect(result).toBeUndefined(); - }); - it("should return a LoginView from an SdkLoginView", () => { jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk); diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 44c6ee8f2e..6f9167cd77 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; @@ -15,15 +13,15 @@ import { LoginUriView } from "./login-uri.view"; export class LoginView extends ItemView { @linkedFieldOption(LinkedId.Username, { sortPosition: 0 }) - username: string = null; + username: string | undefined; @linkedFieldOption(LinkedId.Password, { sortPosition: 1 }) - password: string = null; + password: string | undefined; - passwordRevisionDate?: Date = null; - totp: string = null; + passwordRevisionDate?: Date; + totp: string | undefined; uris: LoginUriView[] = []; - autofillOnPageLoad: boolean = null; - fido2Credentials: Fido2CredentialView[] = null; + autofillOnPageLoad: boolean | undefined; + fido2Credentials: Fido2CredentialView[] = []; constructor(l?: Login) { super(); @@ -35,15 +33,15 @@ export class LoginView extends ItemView { this.autofillOnPageLoad = l.autofillOnPageLoad; } - get uri(): string { - return this.hasUris ? this.uris[0].uri : null; + get uri(): string | undefined { + return this.hasUris ? this.uris[0].uri : undefined; } - get maskedPassword(): string { - return this.password != null ? "••••••••" : null; + get maskedPassword(): string | undefined { + return this.password != null ? "••••••••" : undefined; } - get subTitle(): string { + get subTitle(): string | undefined { // if there's a passkey available, use that as a fallback if (Utils.isNullOrEmpty(this.username) && this.fido2Credentials?.length > 0) { return this.fido2Credentials[0].userName; @@ -60,14 +58,14 @@ export class LoginView extends ItemView { return !Utils.isNullOrWhitespace(this.totp); } - get launchUri(): string { + get launchUri(): string | undefined { if (this.hasUris) { const uri = this.uris.find((u) => u.canLaunch); if (uri != null) { return uri.launchUri; } } - return null; + return undefined; } get hasUris(): boolean { @@ -81,7 +79,7 @@ export class LoginView extends ItemView { matchesUri( targetUri: string, equivalentDomains: Set, - defaultUriMatch: UriMatchStrategySetting = null, + defaultUriMatch?: UriMatchStrategySetting, /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ overrideNeverMatchStrategy?: true, ): boolean { @@ -94,17 +92,20 @@ export class LoginView extends ItemView { ); } - static fromJSON(obj: Partial>): LoginView { - const passwordRevisionDate = - obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - const uris = obj.uris.map((uri) => LoginUriView.fromJSON(uri)); - const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key)); + static fromJSON(obj: Partial> | undefined): LoginView { + if (obj == undefined) { + return new LoginView(); + } - return Object.assign(new LoginView(), obj, { - passwordRevisionDate, - uris, - fido2Credentials, - }); + const loginView = Object.assign(new LoginView(), obj) as LoginView; + + loginView.passwordRevisionDate = + obj.passwordRevisionDate == null ? undefined : new Date(obj.passwordRevisionDate); + loginView.uris = obj.uris?.map((uri) => LoginUriView.fromJSON(uri)) ?? []; + loginView.fido2Credentials = + obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key)) ?? []; + + return loginView; } /** @@ -115,25 +116,21 @@ export class LoginView extends ItemView { * the FIDO2 credentials in encrypted form. We can decrypt them later using a separate * call to client.vault().ciphers().decrypt_fido2_credentials(). */ - static fromSdkLoginView(obj: SdkLoginView): LoginView | undefined { - if (obj == null) { - return undefined; - } - + static fromSdkLoginView(obj: SdkLoginView): LoginView { const loginView = new LoginView(); - loginView.username = obj.username ?? null; - loginView.password = obj.password ?? null; + loginView.username = obj.username; + loginView.password = obj.password; loginView.passwordRevisionDate = - obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); - loginView.totp = obj.totp ?? null; - loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null; + obj.passwordRevisionDate == null ? undefined : new Date(obj.passwordRevisionDate); + loginView.totp = obj.totp; + loginView.autofillOnPageLoad = obj.autofillOnPageLoad; loginView.uris = obj.uris ?.filter((uri) => uri.uri != null && uri.uri !== "") - .map((uri) => LoginUriView.fromSdkLoginUriView(uri)) || []; + .map((uri) => LoginUriView.fromSdkLoginUriView(uri)!) || []; // FIDO2 credentials are not decrypted here, they remain encrypted - loginView.fido2Credentials = null; + loginView.fido2Credentials = []; return loginView; } diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index 5e40196186..85c0d3fd61 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal"; @@ -10,7 +8,7 @@ import { SecureNote } from "../domain/secure-note"; import { ItemView } from "./item.view"; export class SecureNoteView extends ItemView implements SdkSecureNoteView { - type: SecureNoteType = null; + type: SecureNoteType = SecureNoteType.Generic; constructor(n?: SecureNote) { super(); @@ -21,24 +19,20 @@ export class SecureNoteView extends ItemView implements SdkSecureNoteView { this.type = n.type; } - get subTitle(): string { - return null; + get subTitle(): string | undefined { + return undefined; } - static fromJSON(obj: Partial>): SecureNoteView { + static fromJSON(obj: Partial> | undefined): SecureNoteView { return Object.assign(new SecureNoteView(), obj); } /** * Converts the SDK SecureNoteView to a SecureNoteView. */ - static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView | undefined { - if (!obj) { - return undefined; - } - + static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView { const secureNoteView = new SecureNoteView(); - secureNoteView.type = obj.type ?? null; + secureNoteView.type = obj.type; return secureNoteView; } diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index 0547eeb7f8..525608ce27 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -1,24 +1,13 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Jsonify } from "type-fest"; import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal"; -import { SshKey } from "../domain/ssh-key"; - import { ItemView } from "./item.view"; export class SshKeyView extends ItemView { - privateKey: string = null; - publicKey: string = null; - keyFingerprint: string = null; - - constructor(n?: SshKey) { - super(); - if (!n) { - return; - } - } + privateKey!: string; + publicKey!: string; + keyFingerprint!: string; get maskedPrivateKey(): string { if (!this.privateKey || this.privateKey.length === 0) { @@ -43,23 +32,19 @@ export class SshKeyView extends ItemView { return this.keyFingerprint; } - static fromJSON(obj: Partial>): SshKeyView { + static fromJSON(obj: Partial> | undefined): SshKeyView { return Object.assign(new SshKeyView(), obj); } /** * Converts the SDK SshKeyView to a SshKeyView. */ - static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView | undefined { - if (!obj) { - return undefined; - } - + static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView { const sshKeyView = new SshKeyView(); - sshKeyView.privateKey = obj.privateKey ?? null; - sshKeyView.publicKey = obj.publicKey ?? null; - sshKeyView.keyFingerprint = obj.fingerprint ?? null; + sshKeyView.privateKey = obj.privateKey; + sshKeyView.publicKey = obj.publicKey; + sshKeyView.keyFingerprint = obj.fingerprint; return sshKeyView; } diff --git a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts index 70122ebd27..56b94fcf3c 100644 --- a/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts +++ b/libs/common/src/vault/utils/cipher-view-like-utils.spec.ts @@ -298,6 +298,10 @@ describe("CipherViewLikeUtils", () => { (cipherView.attachments as any) = null; expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false); + + cipherView.attachments = []; + + expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false); }); }); diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index 19a8a4828e..f8acb5e064 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -193,7 +193,6 @@ export abstract class BaseImporter { if (this.isNullOrWhitespace(loginUri.uri)) { return null; } - loginUri.match = null; return [loginUri]; } @@ -205,7 +204,6 @@ export abstract class BaseImporter { if (this.isNullOrWhitespace(loginUri.uri)) { return; } - loginUri.match = null; returnArr.push(loginUri); }); return returnArr.length === 0 ? null : returnArr; @@ -236,7 +234,7 @@ export abstract class BaseImporter { return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname; } - protected isNullOrWhitespace(str: string): boolean { + protected isNullOrWhitespace(str: string | undefined | null): boolean { return Utils.isNullOrWhitespace(str); } diff --git a/libs/importer/src/importers/chrome-csv-importer.spec.ts b/libs/importer/src/importers/chrome-csv-importer.spec.ts index a7a2909470..df60a6f264 100644 --- a/libs/importer/src/importers/chrome-csv-importer.spec.ts +++ b/libs/importer/src/importers/chrome-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse app name", csv: androidData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "com.xyz.example.app.android", login: Object.assign(new LoginView(), { username: "username@example.com", @@ -24,7 +21,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -32,9 +28,6 @@ const CipherData = [ title: "should parse password", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "www.example.com", login: Object.assign(new LoginView(), { username: "username@example.com", @@ -45,7 +38,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -54,6 +46,7 @@ const CipherData = [ describe("Chrome CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new ChromeCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts b/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts index b8d84a9378..2dedcec6b2 100644 --- a/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts +++ b/libs/importer/src/importers/dashlane/dashlane-csv-importer.spec.ts @@ -59,12 +59,12 @@ describe("Dashlane CSV Importer", () => { const cipher = result.ciphers.shift(); expect(cipher.type).toBe(CipherType.Card); expect(cipher.name).toBe("John's savings account"); - expect(cipher.card.brand).toBeNull(); + expect(cipher.card.brand).toBeUndefined(); expect(cipher.card.cardholderName).toBe("John Doe"); expect(cipher.card.number).toBe("accountNumber"); - expect(cipher.card.code).toBeNull(); - expect(cipher.card.expMonth).toBeNull(); - expect(cipher.card.expYear).toBeNull(); + expect(cipher.card.code).toBeUndefined(); + expect(cipher.card.expMonth).toBeUndefined(); + expect(cipher.card.expYear).toBeUndefined(); expect(cipher.fields.length).toBe(4); @@ -112,7 +112,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher.name).toBe("John Doe card"); expect(cipher.identity.fullName).toBe("John Doe"); expect(cipher.identity.firstName).toBe("John"); - expect(cipher.identity.middleName).toBeNull(); + expect(cipher.identity.middleName).toBeUndefined(); expect(cipher.identity.lastName).toBe("Doe"); expect(cipher.identity.licenseNumber).toBe("123123123"); @@ -133,7 +133,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher2.name).toBe("John Doe passport"); expect(cipher2.identity.fullName).toBe("John Doe"); expect(cipher2.identity.firstName).toBe("John"); - expect(cipher2.identity.middleName).toBeNull(); + expect(cipher2.identity.middleName).toBeUndefined(); expect(cipher2.identity.lastName).toBe("Doe"); expect(cipher2.identity.passportNumber).toBe("123123123"); @@ -154,7 +154,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher3.name).toBe("John Doe license"); expect(cipher3.identity.fullName).toBe("John Doe"); expect(cipher3.identity.firstName).toBe("John"); - expect(cipher3.identity.middleName).toBeNull(); + expect(cipher3.identity.middleName).toBeUndefined(); expect(cipher3.identity.lastName).toBe("Doe"); expect(cipher3.identity.licenseNumber).toBe("1234556"); expect(cipher3.identity.state).toBe("DC"); @@ -173,7 +173,7 @@ describe("Dashlane CSV Importer", () => { expect(cipher4.name).toBe("John Doe social_security"); expect(cipher4.identity.fullName).toBe("John Doe"); expect(cipher4.identity.firstName).toBe("John"); - expect(cipher4.identity.middleName).toBeNull(); + expect(cipher4.identity.middleName).toBeUndefined(); expect(cipher4.identity.lastName).toBe("Doe"); expect(cipher4.identity.ssn).toBe("123123123"); diff --git a/libs/importer/src/importers/firefox-csv-importer.spec.ts b/libs/importer/src/importers/firefox-csv-importer.spec.ts index 78bca0599b..59d2aa9e7a 100644 --- a/libs/importer/src/importers/firefox-csv-importer.spec.ts +++ b/libs/importer/src/importers/firefox-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse password", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com", login: Object.assign(new LoginView(), { username: "foo", @@ -24,7 +21,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -32,9 +28,6 @@ const CipherData = [ title: 'should skip "chrome://FirefoxAccounts"', csv: firefoxAccountsData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com", login: Object.assign(new LoginView(), { username: "foo", @@ -45,7 +38,6 @@ const CipherData = [ }), ], }), - notes: null, type: 1, }), }, @@ -54,6 +46,7 @@ const CipherData = [ describe("Firefox CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new FirefoxCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts index b326bc5d35..dcaacffc05 100644 --- a/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-csv-importer.spec.ts @@ -51,10 +51,10 @@ describe("Keeper CSV Importer", () => { expect(result != null).toBe(true); const cipher = result.ciphers.shift(); - expect(cipher.login.totp).toBeNull(); + expect(cipher.login.totp).toBeUndefined(); const cipher2 = result.ciphers.shift(); - expect(cipher2.login.totp).toBeNull(); + expect(cipher2.login.totp).toBeUndefined(); const cipher3 = result.ciphers.shift(); expect(cipher3.login.totp).toEqual( diff --git a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts index 1141897a04..a9d42369b1 100644 --- a/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts +++ b/libs/importer/src/importers/keeper/keeper-json-importer.spec.ts @@ -51,7 +51,7 @@ describe("Keeper Json Importer", () => { expect(result != null).toBe(true); const cipher = result.ciphers.shift(); - expect(cipher.login.totp).toBeNull(); + expect(cipher.login.totp).toBeUndefined(); // 2nd Cipher const cipher2 = result.ciphers.shift(); diff --git a/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts b/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts index cabd246fa7..6515e3959b 100644 --- a/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts +++ b/libs/importer/src/importers/lastpass/lastpass-csv-importer.spec.ts @@ -37,9 +37,6 @@ Expiration Date:June,2020 Notes:some text ",Credit-card,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "Credit-card", notes: "some text\n", type: 3, @@ -71,11 +68,7 @@ Start Date:, Expiration Date:, Notes:",empty,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "empty", - notes: null, type: 3, card: { expMonth: undefined, @@ -101,11 +94,7 @@ Start Date:, Expiration Date:January, Notes:",noyear,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "noyear", - notes: null, type: 3, card: { cardholderName: "John Doe", @@ -139,11 +128,7 @@ Start Date:, Expiration Date:,2020 Notes:",nomonth,,0`, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "nomonth", - notes: null, type: 3, card: { cardholderName: "John Doe", @@ -171,6 +156,7 @@ Notes:",nomonth,,0`, describe("Lastpass CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new LastPassCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/myki-csv-importer.spec.ts b/libs/importer/src/importers/myki-csv-importer.spec.ts index 6f804523ef..a77e85d134 100644 --- a/libs/importer/src/importers/myki-csv-importer.spec.ts +++ b/libs/importer/src/importers/myki-csv-importer.spec.ts @@ -468,8 +468,8 @@ describe("Myki CSV Importer", () => { const cipher = result.ciphers.shift(); expect(cipher.name).toEqual("2FA nickname"); - expect(cipher.login.username).toBeNull(); - expect(cipher.login.password).toBeNull(); + expect(cipher.login.username).toBeUndefined(); + expect(cipher.login.password).toBeUndefined(); expect(cipher.login.totp).toBe("someTOTPSeed"); expect(cipher.notes).toEqual("Additional information field content."); diff --git a/libs/importer/src/importers/nordpass-csv-importer.spec.ts b/libs/importer/src/importers/nordpass-csv-importer.spec.ts index e633310e6e..f04272de01 100644 --- a/libs/importer/src/importers/nordpass-csv-importer.spec.ts +++ b/libs/importer/src/importers/nordpass-csv-importer.spec.ts @@ -17,8 +17,8 @@ const namesTestData = [ fullName: "MyFirstName", expected: Object.assign(new IdentityView(), { firstName: "MyFirstName", - middleName: null, - lastName: null, + middleName: undefined, + lastName: undefined, }), }, { @@ -26,7 +26,7 @@ const namesTestData = [ fullName: "MyFirstName MyLastName", expected: Object.assign(new IdentityView(), { firstName: "MyFirstName", - middleName: null, + middleName: undefined, lastName: "MyLastName", }), }, diff --git a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts index 1ca12a9ce6..4ec20ba2a8 100644 --- a/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts +++ b/libs/importer/src/importers/onepassword/onepassword-1pux-importer.spec.ts @@ -393,7 +393,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Michael"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Scarn"); expect(identity.address1).toEqual("2120 Mifflin Rd."); expect(identity.state).toEqual("Pennsylvania"); @@ -423,7 +423,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Cash"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Bandit"); expect(identity.state).toEqual("Washington"); expect(identity.country).toEqual("United States of America"); @@ -447,7 +447,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("George"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Engels"); expect(identity.company).toEqual("National Public Library"); expect(identity.phone).toEqual("9995555555"); @@ -472,7 +472,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("David"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Global"); expect(identity.passportNumber).toEqual("76436847"); @@ -499,7 +499,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Chef"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Coldroom"); expect(identity.company).toEqual("Super Cool Store Co."); @@ -523,7 +523,7 @@ describe("1Password 1Pux Importer", () => { const identity = cipher.identity; expect(identity.firstName).toEqual("Jack"); - expect(identity.middleName).toBeNull(); + expect(identity.middleName).toBeUndefined(); expect(identity.lastName).toEqual("Judd"); expect(identity.ssn).toEqual("131-216-1900"); }); @@ -682,12 +682,12 @@ describe("1Password 1Pux Importer", () => { expect(folders[3].name).toBe("Education"); expect(folders[4].name).toBe("Starter Kit"); - // Check that ciphers have a folder assigned to them - expect(result.ciphers.filter((c) => c.folderId === folders[0].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[1].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[2].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[3].id).length).toBeGreaterThan(0); - expect(result.ciphers.filter((c) => c.folderId === folders[4].id).length).toBeGreaterThan(0); + // Check that folder/cipher relationships + expect(result.folderRelationships.filter(([_, f]) => f == 0).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 1).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 2).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 3).length).toBeGreaterThan(0); + expect(result.folderRelationships.filter(([_, f]) => f == 4).length).toBeGreaterThan(0); }); it("should create collections if part of an organization", async () => { diff --git a/libs/importer/src/importers/safari-csv-importer.spec.ts b/libs/importer/src/importers/safari-csv-importer.spec.ts index 4ca8df23f3..c55117226f 100644 --- a/libs/importer/src/importers/safari-csv-importer.spec.ts +++ b/libs/importer/src/importers/safari-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse URLs in new CSV format", csv: simplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com (example_user)", login: Object.assign(new LoginView(), { username: "example_user", @@ -33,9 +30,6 @@ const CipherData = [ title: "should parse URLs in old CSV format", csv: oldSimplePasswordData, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "example.com (example_user)", login: Object.assign(new LoginView(), { username: "example_user", @@ -45,6 +39,7 @@ const CipherData = [ uri: "https://example.com", }), ], + totp: null, }), type: 1, }), @@ -54,6 +49,7 @@ const CipherData = [ describe("Safari CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new SafariCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/importer/src/importers/zohovault-csv-importer.spec.ts b/libs/importer/src/importers/zohovault-csv-importer.spec.ts index d3904fb521..c82e3e5dcf 100644 --- a/libs/importer/src/importers/zohovault-csv-importer.spec.ts +++ b/libs/importer/src/importers/zohovault-csv-importer.spec.ts @@ -11,9 +11,6 @@ const CipherData = [ title: "should parse Zoho Vault CSV format", csv: samplezohovaultcsvdata, expected: Object.assign(new CipherView(), { - id: null, - organizationId: null, - folderId: null, name: "XYZ Test", login: Object.assign(new LoginView(), { username: "email@domain.de", @@ -41,6 +38,7 @@ describe("Zoho Vault CSV Importer", () => { CipherData.forEach((data) => { it(data.title, async () => { + jest.useFakeTimers().setSystemTime(data.expected.creationDate); const importer = new ZohoVaultCsvImporter(); const result = await importer.parse(data.csv); expect(result != null).toBe(true); diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts index 6d0266dd0f..a9a327b90c 100644 --- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts @@ -86,7 +86,10 @@ describe("AdditionalOptionsSectionComponent", () => { expect(cipherFormProvider.patchCipher).toHaveBeenCalled(); const patchFn = cipherFormProvider.patchCipher.mock.lastCall[0]; - const updated = patchFn(new CipherView()); + const newCipher = new CipherView(); + newCipher.creationDate = newCipher.revisionDate = expectedCipher.creationDate; + + const updated = patchFn(newCipher); expect(updated).toEqual(expectedCipher); }); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index 1552ef9f30..60002ca592 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -66,7 +66,7 @@ export class DeleteAttachmentComponent { await this.cipherService.deleteAttachmentWithServer( this.cipherId, - this.attachment.id, + this.attachment.id!, activeUserId, this.admin, ); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts index 9233f1fa40..4b0cd0f5f9 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts @@ -67,6 +67,7 @@ describe("CardDetailsSectionComponent", () => { cardView.brand = "Visa"; cardView.expMonth = ""; cardView.code = ""; + cardView.expYear = ""; expect(patchCipherSpy).toHaveBeenCalled(); const patchFn = patchCipherSpy.mock.lastCall[0]; @@ -85,6 +86,7 @@ describe("CardDetailsSectionComponent", () => { cardView.number = ""; cardView.expMonth = ""; cardView.code = ""; + cardView.brand = ""; cardView.expYear = "2022"; expect(patchCipherSpy).toHaveBeenCalled(); @@ -122,8 +124,6 @@ describe("CardDetailsSectionComponent", () => { number, code, brand: cardView.brand, - expMonth: null, - expYear: null, }); }); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index a71f57481f..7b8149b6d7 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -52,12 +52,12 @@ export class CardDetailsSectionComponent implements OnInit { * leaving as just null gets inferred as `unknown` */ cardDetailsForm = this.formBuilder.group({ - cardholderName: null as string | null, - number: null as string | null, - brand: null as string | null, - expMonth: null as string | null, - expYear: null as string | number | null, - code: null as string | null, + cardholderName: "", + number: "", + brand: "", + expMonth: "", + expYear: "" as string | number, + code: "", }); /** Available Card Brands */ @@ -110,16 +110,14 @@ export class CardDetailsSectionComponent implements OnInit { .pipe(takeUntilDestroyed()) .subscribe(({ cardholderName, number, brand, expMonth, expYear, code }) => { this.cipherFormContainer.patchCipher((cipher) => { - const expirationYear = normalizeExpiryYearFormat(expYear); + const expirationYear = normalizeExpiryYearFormat(expYear) ?? ""; - Object.assign(cipher.card, { - cardholderName, - number, - brand, - expMonth, - expYear: expirationYear, - code, - }); + cipher.card.cardholderName = cardholderName; + cipher.card.number = number; + cipher.card.brand = brand; + cipher.card.expMonth = expMonth; + cipher.card.expYear = expirationYear; + cipher.card.code = code; return cipher; }); @@ -167,6 +165,7 @@ export class CardDetailsSectionComponent implements OnInit { expMonth: this.initialValues?.expMonth || "", expYear: this.initialValues?.expYear || "", code: this.initialValues?.code || "", + brand: CardView.getCardBrandByPatterns(this.initialValues?.number) || "", }); } @@ -195,18 +194,4 @@ export class CardDetailsSectionComponent implements OnInit { ); } } - - /** Set form initial form values from the current cipher */ - private setInitialValues(cipherView: CipherView) { - const { cardholderName, number, brand, expMonth, expYear, code } = cipherView.card; - - this.cardDetailsForm.setValue({ - cardholderName: cardholderName, - number: number, - brand: brand, - expMonth: expMonth, - expYear: expYear, - code: code, - }); - } } diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts index 12e83b052b..013ccd6c87 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts @@ -386,7 +386,7 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit { fieldView.type = field.type; fieldView.name = field.name; fieldView.value = value; - fieldView.linkedId = field.linkedId; + fieldView.linkedId = field.linkedId ?? undefined; return fieldView; }); diff --git a/libs/vault/src/cipher-form/components/identity/identity.component.ts b/libs/vault/src/cipher-form/components/identity/identity.component.ts index 119ce1caf6..4c90024e05 100644 --- a/libs/vault/src/cipher-form/components/identity/identity.component.ts +++ b/libs/vault/src/cipher-form/components/identity/identity.component.ts @@ -172,7 +172,7 @@ export class IdentitySectionComponent implements OnInit { populateFormData(cipherView: CipherView) { const { identity } = cipherView; - this.identityForm.setValue({ + this.identityForm.patchValue({ title: identity.title, firstName: identity.firstName, middleName: identity.middleName, diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts index ced6c80972..8877b4cbce 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.ts @@ -312,7 +312,7 @@ export class ItemDetailsSectionComponent implements OnInit { private async initFromExistingCipher(prefillCipher: CipherView) { const { name, folderId, collectionIds } = prefillCipher; - this.itemDetailsForm.setValue({ + this.itemDetailsForm.patchValue({ name: name ? name : (this.initialValues?.name ?? ""), organizationId: prefillCipher.organizationId, // We do not allow changing ownership of an existing cipher. folderId: folderId ? folderId : (this.initialValues?.folderId ?? null), diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index d195ff8b00..59c583f980 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -36,7 +36,7 @@ export class DefaultCipherFormService implements CipherFormService { let savedCipher: Cipher; // Creating a new cipher - if (cipher.id == null) { + if (cipher.id == null || cipher.id === "") { const encrypted = await this.cipherService.encrypt(cipher, activeUserId); savedCipher = await this.cipherService.createWithServer(encrypted, config.admin); return await this.cipherService.decrypt(savedCipher, activeUserId); diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts index 2fc35574ba..7c2afd5029 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.ts @@ -44,7 +44,7 @@ import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textare export class CustomFieldV2Component implements OnInit, OnChanges { @Input({ required: true }) cipher!: CipherView; fieldType = FieldType; - fieldOptions: Map | null = null; + fieldOptions: Map | undefined; /** Indexes of hidden fields that are revealed */ revealedHiddenFields: number[] = []; @@ -124,7 +124,7 @@ export class CustomFieldV2Component implements OnInit, OnChanges { case CipherType.Identity: return IdentityView.prototype.linkedFieldOptions; default: - return null; + return undefined; } } }