1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-25682] Migrate CipherView and subviews to be TS strict compliant (#16463)

* [PM-25682] Remove ts-strict-ignore from Vault view models and update types to be strict

* [PM-25682] Ignore ViewEncryptableKeys error for old decrypt methods

* [PM-25682] Add null/undefined as possible types for isNull* and other helpers that include null checks internally

* [PM-25682] Use patchValue instead of setValue which does not support undefined values

* [PM-25682] Add type assertions and other misc. null checks where necessary

* [PM-25682] Fix importers specs

* [PM-25682] Cleanup card view/details

* [PM-25682] Fix cipher view hasAttachment helper

* [PM-25682] Cleanup unecessary null assignments in notification.background.spec.ts

* [PM-25682] Ensure linkedId is undefined instead of null

* [PM-25682] Cleanup misc typing errors

* [PM-25682] Make the CipherId required

* [PM-25682] Undo CipherId assertions

* [PM-25682] Undo brand initial value change

* [PM-25682] Update SshKeyView

* [PM-25682] Add constructor to Fido2CredentialView

* [PM-25682] Prettier

* [PM-25682] Fix strict type warnings after merge with main

* [PM-25682] Cleanup cipher view spec

* [PM-25682] Cleanup new type warnings after merge

* [PM-25682] Undo removed eslint-disable-next-line comment

* [PM-25682] Fix flaky test

* [PM-25682] Use satisfies instead of as for Fido2CredentialAutofillView
This commit is contained in:
Shane Melton
2025-10-07 08:40:57 -07:00
committed by GitHub
parent 2127f71f5d
commit 9f0a565241
54 changed files with 424 additions and 503 deletions

View File

@@ -133,19 +133,11 @@ describe("NotificationBackground", () => {
expect(cipherView.name).toEqual("example.com"); expect(cipherView.name).toEqual("example.com");
expect(cipherView.login).toEqual({ expect(cipherView.login).toEqual({
autofillOnPageLoad: null, fido2Credentials: [],
fido2Credentials: null,
password: message.password, password: message.password,
passwordRevisionDate: null,
totp: null,
uris: [ uris: [
{ {
_canLaunch: null,
_domain: null,
_host: null,
_hostname: null,
_uri: message.uri, _uri: message.uri,
match: null,
}, },
], ],
username: message.username, username: message.username,

View File

@@ -1271,7 +1271,6 @@ export default class NotificationBackground {
cipherView.folderId = folderId; cipherView.folderId = folderId;
cipherView.type = CipherType.Login; cipherView.type = CipherType.Login;
cipherView.login = loginView; cipherView.login = loginView;
cipherView.organizationId = null;
return cipherView; return cipherView;
} }

View File

@@ -45,6 +45,7 @@ import {
CipherViewLike, CipherViewLike,
CipherViewLikeUtils, CipherViewLikeUtils,
} from "@bitwarden/common/vault/utils/cipher-view-like-utils"; } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities";
import { import {
BadgeModule, BadgeModule,
ButtonModule, ButtonModule,
@@ -168,6 +169,7 @@ export class VaultV2Component<C extends CipherViewLike>
private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe( private organizations$: Observable<Organization[]> = this.accountService.activeAccount$.pipe(
map((a) => a?.id), map((a) => a?.id),
filterOutNullish(),
switchMap((id) => this.organizationService.organizations$(id)), switchMap((id) => this.organizationService.organizations$(id)),
); );
@@ -319,7 +321,7 @@ export class VaultV2Component<C extends CipherViewLike>
this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault")); this.searchBarService.setPlaceholderText(this.i18nService.t("searchVault"));
const authRequests = await firstValueFrom( const authRequests = await firstValueFrom(
this.authRequestService.getLatestPendingAuthRequest$(), this.authRequestService.getLatestPendingAuthRequest$()!,
); );
if (authRequests != null) { if (authRequests != null) {
this.messagingService.send("openLoginApproval", { this.messagingService.send("openLoginApproval", {

View File

@@ -978,7 +978,7 @@ export class VaultComponent implements OnInit, OnDestroy {
// Allow restore of an Unassigned Item // Allow restore of an Unassigned Item
try { try {
if (c.id == null) { if (c.id == null || c.id === "") {
throw new Error("Cipher must have an Id to be restored"); throw new Error("Cipher must have an Id to be restored");
} }
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
@@ -1211,7 +1211,7 @@ export class VaultComponent implements OnInit, OnDestroy {
aType = "Password"; aType = "Password";
value = cipher.login.password; value = cipher.login.password;
typeI18nKey = "password"; typeI18nKey = "password";
} else if (field === "totp") { } else if (field === "totp" && cipher.login.totp != null) {
aType = "TOTP"; aType = "TOTP";
const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp)); const totpResponse = await firstValueFrom(this.totpService.getCode$(cipher.login.totp));
value = totpResponse.code; value = totpResponse.code;
@@ -1232,7 +1232,7 @@ export class VaultComponent implements OnInit, OnDestroy {
return; return;
} }
if (!cipher.viewPassword) { if (!cipher.viewPassword || value == null) {
return; return;
} }

View File

@@ -89,6 +89,9 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
private async isPasswordExposed(cv: CipherView): Promise<ReportResult | null> { private async isPasswordExposed(cv: CipherView): Promise<ReportResult | null> {
const { login } = cv; const { login } = cv;
if (login.password == null) {
return null;
}
return await this.auditService.passwordLeaked(login.password).then((exposedCount) => { return await this.auditService.passwordLeaked(login.password).then((exposedCount) => {
if (exposedCount > 0) { if (exposedCount > 0) {
return { ...cv, exposedXTimes: exposedCount } as ReportResult; return { ...cv, exposedXTimes: exposedCount } as ReportResult;

View File

@@ -40,7 +40,7 @@ export function getTrimmedCipherUris(cipher: CipherView): string[] {
const uniqueDomains = new Set<string>(); const uniqueDomains = new Set<string>();
uris.forEach((u: { uri: string }) => { uris.forEach((u: { uri: string | undefined }) => {
const domain = Utils.getDomain(u.uri) ?? u.uri; const domain = Utils.getDomain(u.uri) ?? u.uri;
uniqueDomains.add(domain); uniqueDomains.add(domain);
}); });

View File

@@ -29,7 +29,7 @@ export class PasswordHealthService {
filter((cipher) => this.isValidCipher(cipher)), filter((cipher) => this.isValidCipher(cipher)),
mergeMap((cipher) => mergeMap((cipher) =>
this.auditService this.auditService
.passwordLeaked(cipher.login.password) .passwordLeaked(cipher.login.password!)
.then((exposedCount) => ({ cipher, exposedCount })), .then((exposedCount) => ({ cipher, exposedCount })),
), ),
// [FIXME] ExposedDetails is can still return a null // [FIXME] ExposedDetails is can still return a null
@@ -74,11 +74,11 @@ export class PasswordHealthService {
// Check the username // Check the username
const userInput = this.isUserNameNotEmpty(cipher) const userInput = this.isUserNameNotEmpty(cipher)
? this.extractUsernameParts(cipher.login.username) ? this.extractUsernameParts(cipher.login.username!)
: undefined; : undefined;
const { score } = this.passwordStrengthService.getPasswordStrength( const { score } = this.passwordStrengthService.getPasswordStrength(
cipher.login.password, cipher.login.password!,
undefined, // No email available in this context undefined, // No email available in this context
userInput, userInput,
); );

View File

@@ -436,13 +436,13 @@ export class RiskInsightsReportService {
const weakPassword = this.passwordHealthService.findWeakPasswordDetails(cipher); const weakPassword = this.passwordHealthService.findWeakPasswordDetails(cipher);
// Looping over all ciphers needs to happen first to determine reused passwords over all ciphers. // Looping over all ciphers needs to happen first to determine reused passwords over all ciphers.
// Store in the set and evaluate later // Store in the set and evaluate later
if (passwordUseMap.has(cipher.login.password)) { if (passwordUseMap.has(cipher.login.password!)) {
passwordUseMap.set( passwordUseMap.set(
cipher.login.password, cipher.login.password!,
(passwordUseMap.get(cipher.login.password) || 0) + 1, (passwordUseMap.get(cipher.login.password!) || 0) + 1,
); );
} else { } else {
passwordUseMap.set(cipher.login.password, 1); passwordUseMap.set(cipher.login.password!, 1);
} }
const exposedPassword = exposedDetails.find((x) => x?.cipherId === cipher.id); const exposedPassword = exposedDetails.find((x) => x?.cipherId === cipher.id);
@@ -466,7 +466,7 @@ export class RiskInsightsReportService {
// loop for reused passwords // loop for reused passwords
cipherHealthReports.forEach((detail) => { cipherHealthReports.forEach((detail) => {
detail.reusedPasswordCount = passwordUseMap.get(detail.login.password) ?? 0; detail.reusedPasswordCount = passwordUseMap.get(detail.login.password!) ?? 0;
}); });
return cipherHealthReports; return cipherHealthReports;
} }
@@ -514,7 +514,7 @@ export class RiskInsightsReportService {
private _buildPasswordUseMap(ciphers: CipherView[]): Map<string, number> { private _buildPasswordUseMap(ciphers: CipherView[]): Map<string, number> {
const passwordUseMap = new Map<string, number>(); const passwordUseMap = new Map<string, number>();
ciphers.forEach((cipher) => { ciphers.forEach((cipher) => {
const password = cipher.login.password; const password = cipher.login.password!;
passwordUseMap.set(password, (passwordUseMap.get(password) || 0) + 1); passwordUseMap.set(password, (passwordUseMap.get(password) || 0) + 1);
}); });
return passwordUseMap; return passwordUseMap;
@@ -686,7 +686,7 @@ export class RiskInsightsReportService {
healthData: { healthData: {
weakPasswordDetail: this.passwordHealthService.findWeakPasswordDetails(cipher), weakPasswordDetail: this.passwordHealthService.findWeakPasswordDetails(cipher),
exposedPasswordDetail: exposedPassword, exposedPasswordDetail: exposedPassword,
reusedPasswordCount: passwordUseMap.get(cipher.login.password) ?? 0, reusedPasswordCount: passwordUseMap.get(cipher.login.password!) ?? 0,
}, },
applications: getTrimmedCipherUris(cipher), applications: getTrimmedCipherUris(cipher),
} as CipherHealthReport; } as CipherHealthReport;

View File

@@ -17,13 +17,13 @@ const CanLaunchWhitelist = [
]; ];
export class SafeUrls { export class SafeUrls {
static canLaunch(uri: string): boolean { static canLaunch(uri: string | null | undefined): boolean {
if (Utils.isNullOrWhitespace(uri)) { if (Utils.isNullOrWhitespace(uri)) {
return false; return false;
} }
for (let i = 0; i < CanLaunchWhitelist.length; i++) { for (let i = 0; i < CanLaunchWhitelist.length; i++) {
if (uri.indexOf(CanLaunchWhitelist[i]) === 0) { if (uri!.indexOf(CanLaunchWhitelist[i]) === 0) {
return true; return true;
} }
} }

View File

@@ -375,7 +375,7 @@ export class Utils {
} }
} }
static getDomain(uriString: string): string { static getDomain(uriString: string | null | undefined): string {
if (Utils.isNullOrWhitespace(uriString)) { if (Utils.isNullOrWhitespace(uriString)) {
return null; return null;
} }
@@ -457,7 +457,7 @@ export class Utils {
return str == null || typeof str !== "string" || str.trim() === ""; 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 == ""; 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]); 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)) { if (this.isNullOrWhitespace(uriString)) {
return null; return null;
} }

View File

@@ -27,8 +27,8 @@ export async function getCredentialsForAutofill(
cipherId: cipher.id, cipherId: cipher.id,
credentialId: credId, credentialId: credId,
rpId: credential.rpId, rpId: credential.rpId,
userHandle: credential.userHandle, userHandle: credential.userHandle!,
userName: credential.userName, userName: credential.userName!,
}; } satisfies Fido2CredentialAutofillView;
}); });
} }

View File

@@ -47,6 +47,7 @@ export class Attachment extends Domain {
): Promise<AttachmentView> { ): Promise<AttachmentView> {
const view = await this.decryptObj<Attachment, AttachmentView>( const view = await this.decryptObj<Attachment, AttachmentView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new AttachmentView(this), new AttachmentView(this),
["fileName"], ["fileName"],
orgId, orgId,

View File

@@ -63,7 +63,6 @@ describe("Card", () => {
expect(view).toEqual({ expect(view).toEqual({
_brand: "brand", _brand: "brand",
_number: "number", _number: "number",
_subTitle: null,
cardholderName: "cardHolder", cardholderName: "cardHolder",
code: "code", code: "code",
expMonth: "expMonth", expMonth: "expMonth",

View File

@@ -161,6 +161,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
await this.decryptObj<Cipher, CipherView>( await this.decryptObj<Cipher, CipherView>(
this, 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, model,
["name", "notes"], ["name", "notes"],
this.organizationId, this.organizationId,

View File

@@ -56,6 +56,7 @@ export class Fido2Credential extends Domain {
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<Fido2CredentialView> { async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<Fido2CredentialView> {
const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>( const view = await this.decryptObj<Fido2Credential, Fido2CredentialView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new Fido2CredentialView(), new Fido2CredentialView(),
[ [
"credentialId", "credentialId",

View File

@@ -39,6 +39,7 @@ export class Field extends Domain {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> { decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> {
return this.decryptObj<Field, FieldView>( return this.decryptObj<Field, FieldView>(
this, this,
// @ts-expect-error ViewEncryptableKeys type should be fixed to allow for optional values, but is out of scope for now.
new FieldView(this), new FieldView(this),
["name", "value"], ["name", "value"],
orgId, orgId,

View File

@@ -112,7 +112,6 @@ describe("Identity", () => {
expect(view).toEqual({ expect(view).toEqual({
_firstName: "mockFirstName", _firstName: "mockFirstName",
_lastName: "mockLastName", _lastName: "mockLastName",
_subTitle: null,
address1: "mockAddress1", address1: "mockAddress1",
address2: "mockAddress2", address2: "mockAddress2",
address3: "mockAddress3", address3: "mockAddress3",

View File

@@ -56,10 +56,6 @@ describe("LoginUri", () => {
const view = await loginUri.decrypt(null); const view = await loginUri.decrypt(null);
expect(view).toEqual({ expect(view).toEqual({
_canLaunch: null,
_domain: null,
_host: null,
_hostname: null,
_uri: "uri", _uri: "uri",
match: 3, match: 3,
}); });

View File

@@ -2,7 +2,7 @@ import { MockProxy, mock } from "jest-mock-extended";
import { mockEnc, mockFromJson } from "../../../../spec"; import { mockEnc, mockFromJson } from "../../../../spec";
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; 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 { LoginData } from "../../models/data/login.data";
import { Login } from "../../models/domain/login"; import { Login } from "../../models/domain/login";
import { LoginUri } from "../../models/domain/login-uri"; import { LoginUri } from "../../models/domain/login-uri";
@@ -82,12 +82,7 @@ describe("Login DTO", () => {
totp: "encrypted totp", totp: "encrypted totp",
uris: [ uris: [
{ {
match: null as UriMatchStrategySetting,
_uri: "decrypted uri", _uri: "decrypted uri",
_domain: null as string,
_hostname: null as string,
_host: null as string,
_canLaunch: null as boolean,
}, },
], ],
autofillOnPageLoad: true, autofillOnPageLoad: true,

View File

@@ -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 { Jsonify } from "type-fest";
import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; 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"; import { Attachment } from "../domain/attachment";
export class AttachmentView implements View { export class AttachmentView implements View {
id: string = null; id?: string;
url: string = null; url?: string;
size: string = null; size?: string;
sizeName: string = null; sizeName?: string;
fileName: string = null; fileName?: string;
key: SymmetricCryptoKey = null; key?: SymmetricCryptoKey;
/** /**
* The SDK returns an encrypted key for the attachment. * The SDK returns an encrypted key for the attachment.
*/ */
@@ -35,7 +33,7 @@ export class AttachmentView implements View {
get fileSize(): number { get fileSize(): number {
try { try {
if (this.size != null) { if (this.size != null) {
return parseInt(this.size, null); return parseInt(this.size);
} }
} catch { } catch {
// Invalid file size. // Invalid file size.
@@ -71,7 +69,7 @@ export class AttachmentView implements View {
fileName: this.fileName, fileName: this.fileName,
key: this.encryptedKey?.toSdk(), key: this.encryptedKey?.toSdk(),
// TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete // 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(); const view = new AttachmentView();
view.id = obj.id ?? null; view.id = obj.id;
view.url = obj.url ?? null; view.url = obj.url;
view.size = obj.size ?? null; view.size = obj.size;
view.sizeName = obj.sizeName ?? null; view.sizeName = obj.sizeName;
view.fileName = obj.fileName ?? null; view.fileName = obj.fileName;
// TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete // 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; view.encryptedKey = obj.key ? new EncString(obj.key) : undefined;
return view; return view;

View File

@@ -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 { Jsonify } from "type-fest";
import { CardView as SdkCardView } from "@bitwarden/sdk-internal"; import { CardView as SdkCardView } from "@bitwarden/sdk-internal";
@@ -12,45 +10,45 @@ import { ItemView } from "./item.view";
export class CardView extends ItemView implements SdkCardView { export class CardView extends ItemView implements SdkCardView {
@linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 }) @linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 })
cardholderName: string = null; cardholderName: string | undefined;
@linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" }) @linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" })
expMonth: string = null; expMonth: string | undefined;
@linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" }) @linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" })
expYear: string = null; expYear: string | undefined;
@linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" }) @linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" })
code: string = null; code: string | undefined;
private _brand: string = null; private _brand?: string;
private _number: string = null; private _number?: string;
private _subTitle: string = null; private _subTitle?: string;
get maskedCode(): string { get maskedCode(): string | undefined {
return this.code != null ? "•".repeat(this.code.length) : null; return this.code != null ? "•".repeat(this.code.length) : undefined;
} }
get maskedNumber(): string { get maskedNumber(): string | undefined {
return this.number != null ? "•".repeat(this.number.length) : null; return this.number != null ? "•".repeat(this.number.length) : undefined;
} }
@linkedFieldOption(LinkedId.Brand, { sortPosition: 2 }) @linkedFieldOption(LinkedId.Brand, { sortPosition: 2 })
get brand(): string { get brand(): string | undefined {
return this._brand; return this._brand;
} }
set brand(value: string) { set brand(value: string | undefined) {
this._brand = value; this._brand = value;
this._subTitle = null; this._subTitle = undefined;
} }
@linkedFieldOption(LinkedId.Number, { sortPosition: 1 }) @linkedFieldOption(LinkedId.Number, { sortPosition: 1 })
get number(): string { get number(): string | undefined {
return this._number; return this._number;
} }
set number(value: string) { set number(value: string | undefined) {
this._number = value; this._number = value;
this._subTitle = null; this._subTitle = undefined;
} }
get subTitle(): string { get subTitle(): string | undefined {
if (this._subTitle == null) { if (this._subTitle == null) {
this._subTitle = this.brand; this._subTitle = this.brand;
if (this.number != null && this.number.length >= 4) { if (this.number != null && this.number.length >= 4) {
@@ -69,11 +67,11 @@ export class CardView extends ItemView implements SdkCardView {
return this._subTitle; return this._subTitle;
} }
get expiration(): string { get expiration(): string | undefined {
const normalizedYear = normalizeExpiryYearFormat(this.expYear); const normalizedYear = this.expYear ? normalizeExpiryYearFormat(this.expYear) : undefined;
if (!this.expMonth && !normalizedYear) { if (!this.expMonth && !normalizedYear) {
return null; return undefined;
} }
let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__"; let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__";
@@ -82,14 +80,14 @@ export class CardView extends ItemView implements SdkCardView {
return exp; return exp;
} }
static fromJSON(obj: Partial<Jsonify<CardView>>): CardView { static fromJSON(obj: Partial<Jsonify<CardView>> | undefined): CardView {
return Object.assign(new CardView(), obj); return Object.assign(new CardView(), obj);
} }
// ref https://stackoverflow.com/a/5911300 // 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() === "") { if (cardNum == null || typeof cardNum !== "string" || cardNum.trim() === "") {
return null; return undefined;
} }
// Visa // Visa
@@ -146,25 +144,21 @@ export class CardView extends ItemView implements SdkCardView {
return "Visa"; return "Visa";
} }
return null; return undefined;
} }
/** /**
* Converts an SDK CardView to a CardView. * Converts an SDK CardView to a CardView.
*/ */
static fromSdkCardView(obj: SdkCardView): CardView | undefined { static fromSdkCardView(obj: SdkCardView): CardView {
if (obj == null) {
return undefined;
}
const cardView = new CardView(); const cardView = new CardView();
cardView.cardholderName = obj.cardholderName ?? null; cardView.cardholderName = obj.cardholderName;
cardView.brand = obj.brand ?? null; cardView.brand = obj.brand;
cardView.number = obj.number ?? null; cardView.number = obj.number;
cardView.expMonth = obj.expMonth ?? null; cardView.expMonth = obj.expMonth;
cardView.expYear = obj.expYear ?? null; cardView.expYear = obj.expYear;
cardView.code = obj.code ?? null; cardView.code = obj.code;
return cardView; return cardView;
} }

View File

@@ -180,15 +180,12 @@ describe("CipherView", () => {
folderId: "folderId", folderId: "folderId",
collectionIds: ["collectionId"], collectionIds: ["collectionId"],
name: "name", name: "name",
notes: null,
type: CipherType.Login, type: CipherType.Login,
favorite: true, favorite: true,
edit: true, edit: true,
reprompt: CipherRepromptType.None, reprompt: CipherRepromptType.None,
organizationUseTotp: false, organizationUseTotp: false,
viewPassword: true, viewPassword: true,
localData: undefined,
permissions: undefined,
attachments: [ attachments: [
{ {
id: "attachmentId", id: "attachmentId",
@@ -224,7 +221,6 @@ describe("CipherView", () => {
passwordHistory: [], passwordHistory: [],
creationDate: new Date("2022-01-01T12:00:00.000Z"), creationDate: new Date("2022-01-01T12:00:00.000Z"),
revisionDate: new Date("2022-01-02T12:00:00.000Z"), revisionDate: new Date("2022-01-02T12:00:00.000Z"),
deletedDate: null,
}); });
}); });
}); });
@@ -283,18 +279,12 @@ describe("CipherView", () => {
restore: true, restore: true,
delete: true, delete: true,
}, },
deletedDate: undefined,
creationDate: "2022-01-02T12:00:00.000Z", creationDate: "2022-01-02T12:00:00.000Z",
revisionDate: "2022-01-02T12:00:00.000Z", revisionDate: "2022-01-02T12:00:00.000Z",
attachments: [], attachments: [],
passwordHistory: [], passwordHistory: [],
login: undefined,
identity: undefined,
card: undefined,
secureNote: undefined,
sshKey: undefined,
fields: [], fields: [],
} as SdkCipherView); });
}); });
}); });
}); });

View File

@@ -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 { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { asUuid, uuidAsString } from "@bitwarden/common/platform/abstractions/sdk/sdk.service"; 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 { CipherView as SdkCipherView } from "@bitwarden/sdk-internal";
import { View } from "../../../models/view/view"; import { View } from "../../../models/view/view";
@@ -26,18 +25,18 @@ import { SshKeyView } from "./ssh-key.view";
export class CipherView implements View, InitializerMetadata { export class CipherView implements View, InitializerMetadata {
readonly initializerKey = InitializerKey.CipherView; readonly initializerKey = InitializerKey.CipherView;
id: string = null; id: string = "";
organizationId: string | undefined = null; organizationId?: string;
folderId: string = null; folderId?: string;
name: string = null; name: string = "";
notes: string = null; notes?: string;
type: CipherType = null; type: CipherType = CipherType.Login;
favorite = false; favorite = false;
organizationUseTotp = false; organizationUseTotp = false;
permissions: CipherPermissionsApi = new CipherPermissionsApi(); permissions?: CipherPermissionsApi = new CipherPermissionsApi();
edit = false; edit = false;
viewPassword = true; viewPassword = true;
localData: LocalData; localData?: LocalData;
login = new LoginView(); login = new LoginView();
identity = new IdentityView(); identity = new IdentityView();
card = new CardView(); card = new CardView();
@@ -46,11 +45,11 @@ export class CipherView implements View, InitializerMetadata {
attachments: AttachmentView[] = []; attachments: AttachmentView[] = [];
fields: FieldView[] = []; fields: FieldView[] = [];
passwordHistory: PasswordHistoryView[] = []; passwordHistory: PasswordHistoryView[] = [];
collectionIds: string[] = null; collectionIds: string[] = [];
revisionDate: Date = null; revisionDate: Date;
creationDate: Date = null; creationDate: Date;
deletedDate: Date | null = null; deletedDate?: Date;
archivedDate: Date | null = null; archivedDate?: Date;
reprompt: CipherRepromptType = CipherRepromptType.None; reprompt: CipherRepromptType = CipherRepromptType.None;
// We need a copy of the encrypted key so we can pass it to // We need a copy of the encrypted key so we can pass it to
// the SdkCipherView during encryption // the SdkCipherView during encryption
@@ -63,6 +62,7 @@ export class CipherView implements View, InitializerMetadata {
constructor(c?: Cipher) { constructor(c?: Cipher) {
if (!c) { if (!c) {
this.creationDate = this.revisionDate = new Date();
return; return;
} }
@@ -86,7 +86,7 @@ export class CipherView implements View, InitializerMetadata {
this.key = c.key; this.key = c.key;
} }
private get item() { private get item(): ItemView | undefined {
switch (this.type) { switch (this.type) {
case CipherType.Login: case CipherType.Login:
return this.login; return this.login;
@@ -102,10 +102,10 @@ export class CipherView implements View, InitializerMetadata {
break; break;
} }
return null; return undefined;
} }
get subTitle(): string { get subTitle(): string | undefined {
return this.item?.subTitle; return this.item?.subTitle;
} }
@@ -114,7 +114,7 @@ export class CipherView implements View, InitializerMetadata {
} }
get hasAttachments(): boolean { get hasAttachments(): boolean {
return this.attachments && this.attachments.length > 0; return !!this.attachments && this.attachments.length > 0;
} }
get hasOldAttachments(): boolean { get hasOldAttachments(): boolean {
@@ -132,11 +132,11 @@ export class CipherView implements View, InitializerMetadata {
return this.fields && this.fields.length > 0; return this.fields && this.fields.length > 0;
} }
get passwordRevisionDisplayDate(): Date { get passwordRevisionDisplayDate(): Date | undefined {
if (this.type !== CipherType.Login || this.login == null) { if (this.type !== CipherType.Login || this.login == null) {
return null; return undefined;
} else if (this.login.password == null || this.login.password === "") { } else if (this.login.password == null || this.login.password === "") {
return null; return undefined;
} }
return this.login.passwordRevisionDate; 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. * Determines if the cipher can be launched in a new browser tab.
*/ */
get canLaunch(): boolean { get canLaunch(): boolean {
return this.type === CipherType.Login && this.login.canLaunch; return this.type === CipherType.Login && this.login!.canLaunch;
} }
linkedFieldValue(id: LinkedIdType) { linkedFieldValue(id: LinkedIdType) {
const linkedFieldOption = this.linkedFieldOptions?.get(id); const linkedFieldOption = this.linkedFieldOptions?.get(id);
if (linkedFieldOption == null) {
return null;
}
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const item = this.item; const item = this.item;
return this.item[linkedFieldOption.propertyKey as keyof typeof item]; if (linkedFieldOption == null || item == null) {
return undefined;
} }
linkedFieldI18nKey(id: LinkedIdType): string { return item[linkedFieldOption.propertyKey as keyof typeof item];
return this.linkedFieldOptions.get(id)?.i18nKey;
} }
// This is used as a marker to indicate that the cipher view object still has its prototype // 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; return this;
} }
static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView { static fromJSON(obj: Partial<DeepJsonify<CipherView>>): CipherView | null {
if (obj == null) { if (obj == null) {
return null; return null;
} }
const view = new CipherView(); const view = new CipherView();
const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); view.type = obj.type ?? CipherType.Login;
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); view.id = obj.id ?? "";
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); view.name = obj.name ?? "";
const archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); if (obj.creationDate) {
const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a)); view.creationDate = new Date(obj.creationDate);
const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f)); }
const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)); if (obj.revisionDate) {
const permissions = CipherPermissionsApi.fromJSON(obj.permissions); view.revisionDate = new Date(obj.revisionDate);
let key: EncString | undefined; }
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) { if (obj.key != null) {
let key: EncString | undefined;
if (typeof obj.key === "string") { if (typeof obj.key === "string") {
// If the key is a string, we need to parse it as EncString // If the key is a string, we need to parse it as EncString
key = EncString.fromJSON(obj.key); 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 // If the key is already an EncString instance, we can use it directly
key = obj.key; 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) { switch (obj.type) {
case CipherType.Card: case CipherType.Card:
view.card = CardView.fromJSON(obj.card); view.card = CardView.fromJSON(obj.card);
@@ -264,46 +255,54 @@ export class CipherView implements View, InitializerMetadata {
} }
const cipherView = new CipherView(); const cipherView = new CipherView();
cipherView.id = uuidAsString(obj.id) ?? null; cipherView.id = uuidAsString(obj.id);
cipherView.organizationId = uuidAsString(obj.organizationId) ?? null; cipherView.organizationId = uuidAsString(obj.organizationId);
cipherView.folderId = uuidAsString(obj.folderId) ?? null; cipherView.folderId = uuidAsString(obj.folderId);
cipherView.name = obj.name; cipherView.name = obj.name;
cipherView.notes = obj.notes ?? null; cipherView.notes = obj.notes;
cipherView.type = obj.type; cipherView.type = obj.type;
cipherView.favorite = obj.favorite; cipherView.favorite = obj.favorite;
cipherView.organizationUseTotp = obj.organizationUseTotp; cipherView.organizationUseTotp = obj.organizationUseTotp;
cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions); cipherView.permissions = obj.permissions
? CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions)
: undefined;
cipherView.edit = obj.edit; cipherView.edit = obj.edit;
cipherView.viewPassword = obj.viewPassword; cipherView.viewPassword = obj.viewPassword;
cipherView.localData = fromSdkLocalData(obj.localData); cipherView.localData = fromSdkLocalData(obj.localData);
cipherView.attachments = cipherView.attachments =
obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? []; obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)!) ?? [];
cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? []; cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)!) ?? [];
cipherView.passwordHistory = 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.collectionIds = obj.collectionIds?.map((i) => uuidAsString(i)) ?? [];
cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); cipherView.revisionDate = new Date(obj.revisionDate);
cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); cipherView.creationDate = new Date(obj.creationDate);
cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); cipherView.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate);
cipherView.archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate); cipherView.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate);
cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None;
cipherView.key = EncString.fromJSON(obj.key); cipherView.key = obj.key ? EncString.fromJSON(obj.key) : undefined;
switch (obj.type) { switch (obj.type) {
case CipherType.Card: case CipherType.Card:
cipherView.card = CardView.fromSdkCardView(obj.card); cipherView.card = obj.card ? CardView.fromSdkCardView(obj.card) : new CardView();
break; break;
case CipherType.Identity: case CipherType.Identity:
cipherView.identity = IdentityView.fromSdkIdentityView(obj.identity); cipherView.identity = obj.identity
? IdentityView.fromSdkIdentityView(obj.identity)
: new IdentityView();
break; break;
case CipherType.Login: case CipherType.Login:
cipherView.login = LoginView.fromSdkLoginView(obj.login); cipherView.login = obj.login ? LoginView.fromSdkLoginView(obj.login) : new LoginView();
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
cipherView.secureNote = SecureNoteView.fromSdkSecureNoteView(obj.secureNote); cipherView.secureNote = obj.secureNote
? SecureNoteView.fromSdkSecureNoteView(obj.secureNote)
: new SecureNoteView();
break; break;
case CipherType.SshKey: case CipherType.SshKey:
cipherView.sshKey = SshKeyView.fromSdkSshKeyView(obj.sshKey); cipherView.sshKey = obj.sshKey
? SshKeyView.fromSdkSshKeyView(obj.sshKey)
: new SshKeyView();
break; break;
default: default:
break; break;
@@ -354,19 +353,19 @@ export class CipherView implements View, InitializerMetadata {
switch (this.type) { switch (this.type) {
case CipherType.Card: case CipherType.Card:
sdkCipherView.card = this.card.toSdkCardView(); sdkCipherView.card = this.card?.toSdkCardView();
break; break;
case CipherType.Identity: case CipherType.Identity:
sdkCipherView.identity = this.identity.toSdkIdentityView(); sdkCipherView.identity = this.identity?.toSdkIdentityView();
break; break;
case CipherType.Login: case CipherType.Login:
sdkCipherView.login = this.login.toSdkLoginView(); sdkCipherView.login = this.login?.toSdkLoginView();
break; break;
case CipherType.SecureNote: case CipherType.SecureNote:
sdkCipherView.secureNote = this.secureNote.toSdkSecureNoteView(); sdkCipherView.secureNote = this.secureNote?.toSdkSecureNoteView();
break; break;
case CipherType.SshKey: case CipherType.SshKey:
sdkCipherView.sshKey = this.sshKey.toSdkSshKeyView(); sdkCipherView.sshKey = this.sshKey?.toSdkSshKeyView();
break; break;
default: default:
break; break;

View File

@@ -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 { Jsonify } from "type-fest";
import { import {
@@ -10,21 +8,55 @@ import {
import { ItemView } from "./item.view"; import { ItemView } from "./item.view";
export class Fido2CredentialView extends ItemView { 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 = false;
creationDate!: Date;
constructor(f?: {
credentialId: string; credentialId: string;
keyType: "public-key"; keyType: "public-key";
keyAlgorithm: "ECDSA"; keyAlgorithm: "ECDSA";
keyCurve: "P-256"; keyCurve: "P-256";
keyValue: string; keyValue: string;
rpId: string; rpId: string;
userHandle: string; userHandle?: string;
userName: string; userName?: string;
counter: number; counter: number;
rpName: string; rpName?: string;
userDisplayName: string; userDisplayName?: string;
discoverable: boolean; discoverable?: boolean;
creationDate: Date = null; 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 { get subTitle(): string | undefined {
return this.userDisplayName; return this.userDisplayName;
} }
@@ -43,21 +75,21 @@ export class Fido2CredentialView extends ItemView {
return undefined; return undefined;
} }
const view = new Fido2CredentialView(); return new Fido2CredentialView({
view.credentialId = obj.credentialId; credentialId: obj.credentialId,
view.keyType = obj.keyType as "public-key"; keyType: obj.keyType as "public-key",
view.keyAlgorithm = obj.keyAlgorithm as "ECDSA"; keyAlgorithm: obj.keyAlgorithm as "ECDSA",
view.keyCurve = obj.keyCurve as "P-256"; keyCurve: obj.keyCurve as "P-256",
view.rpId = obj.rpId; keyValue: obj.keyValue,
view.userHandle = obj.userHandle; rpId: obj.rpId,
view.userName = obj.userName; userHandle: obj.userHandle,
view.counter = parseInt(obj.counter); userName: obj.userName,
view.rpName = obj.rpName; counter: parseInt(obj.counter),
view.userDisplayName = obj.userDisplayName; rpName: obj.rpName,
view.discoverable = obj.discoverable?.toLowerCase() === "true" ? true : false; userDisplayName: obj.userDisplayName,
view.creationDate = obj.creationDate ? new Date(obj.creationDate) : null; discoverable: obj.discoverable?.toLowerCase() === "true",
creationDate: new Date(obj.creationDate),
return view; });
} }
toSdkFido2CredentialFullView(): Fido2CredentialFullView { toSdkFido2CredentialFullView(): Fido2CredentialFullView {

View File

@@ -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 { Jsonify } from "type-fest";
import { FieldView as SdkFieldView, FieldType as SdkFieldType } from "@bitwarden/sdk-internal"; 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"; import { Field } from "../domain/field";
export class FieldView implements View { export class FieldView implements View {
name: string = null; name?: string;
value: string = null; value?: string;
type: FieldType = null; type: FieldType = FieldType.Text;
newField = false; // Marks if the field is new and hasn't been saved newField = false; // Marks if the field is new and hasn't been saved
showValue = false; showValue = false;
showCount = false; showCount = false;
linkedId: LinkedIdType = null; linkedId?: LinkedIdType;
constructor(f?: Field) { constructor(f?: Field) {
if (!f) { if (!f) {
@@ -26,8 +24,8 @@ export class FieldView implements View {
this.linkedId = f.linkedId; this.linkedId = f.linkedId;
} }
get maskedValue(): string { get maskedValue(): string | undefined {
return this.value != null ? "••••••••" : null; return this.value != null ? "••••••••" : undefined;
} }
static fromJSON(obj: Partial<Jsonify<FieldView>>): FieldView { static fromJSON(obj: Partial<Jsonify<FieldView>>): FieldView {

View File

@@ -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 { Jsonify } from "type-fest";
import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal"; import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal";
@@ -12,65 +10,65 @@ import { ItemView } from "./item.view";
export class IdentityView extends ItemView implements SdkIdentityView { export class IdentityView extends ItemView implements SdkIdentityView {
@linkedFieldOption(LinkedId.Title, { sortPosition: 0 }) @linkedFieldOption(LinkedId.Title, { sortPosition: 0 })
title: string = null; title: string | undefined;
@linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 }) @linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 })
middleName: string = null; middleName: string | undefined;
@linkedFieldOption(LinkedId.Address1, { sortPosition: 12 }) @linkedFieldOption(LinkedId.Address1, { sortPosition: 12 })
address1: string = null; address1: string | undefined;
@linkedFieldOption(LinkedId.Address2, { sortPosition: 13 }) @linkedFieldOption(LinkedId.Address2, { sortPosition: 13 })
address2: string = null; address2: string | undefined;
@linkedFieldOption(LinkedId.Address3, { sortPosition: 14 }) @linkedFieldOption(LinkedId.Address3, { sortPosition: 14 })
address3: string = null; address3: string | undefined;
@linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" }) @linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" })
city: string = null; city: string | undefined;
@linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" }) @linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" })
state: string = null; state: string | undefined;
@linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" }) @linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" })
postalCode: string = null; postalCode: string | undefined;
@linkedFieldOption(LinkedId.Country, { sortPosition: 18 }) @linkedFieldOption(LinkedId.Country, { sortPosition: 18 })
country: string = null; country: string | undefined;
@linkedFieldOption(LinkedId.Company, { sortPosition: 6 }) @linkedFieldOption(LinkedId.Company, { sortPosition: 6 })
company: string = null; company: string | undefined;
@linkedFieldOption(LinkedId.Email, { sortPosition: 10 }) @linkedFieldOption(LinkedId.Email, { sortPosition: 10 })
email: string = null; email: string | undefined;
@linkedFieldOption(LinkedId.Phone, { sortPosition: 11 }) @linkedFieldOption(LinkedId.Phone, { sortPosition: 11 })
phone: string = null; phone: string | undefined;
@linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 }) @linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 })
ssn: string = null; ssn: string | undefined;
@linkedFieldOption(LinkedId.Username, { sortPosition: 5 }) @linkedFieldOption(LinkedId.Username, { sortPosition: 5 })
username: string = null; username: string | undefined;
@linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 }) @linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 })
passportNumber: string = null; passportNumber: string | undefined;
@linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 }) @linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 })
licenseNumber: string = null; licenseNumber: string | undefined;
private _firstName: string = null; private _firstName: string | undefined;
private _lastName: string = null; private _lastName: string | undefined;
private _subTitle: string = null; private _subTitle: string | undefined;
constructor() { constructor() {
super(); super();
} }
@linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 }) @linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 })
get firstName(): string { get firstName(): string | undefined {
return this._firstName; return this._firstName;
} }
set firstName(value: string) { set firstName(value: string | undefined) {
this._firstName = value; this._firstName = value;
this._subTitle = null; this._subTitle = undefined;
} }
@linkedFieldOption(LinkedId.LastName, { sortPosition: 4 }) @linkedFieldOption(LinkedId.LastName, { sortPosition: 4 })
get lastName(): string { get lastName(): string | undefined {
return this._lastName; return this._lastName;
} }
set lastName(value: string) { set lastName(value: string | undefined) {
this._lastName = value; 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)) { if (this._subTitle == null && (this.firstName != null || this.lastName != null)) {
this._subTitle = ""; this._subTitle = "";
if (this.firstName != null) { if (this.firstName != null) {
@@ -88,7 +86,7 @@ export class IdentityView extends ItemView implements SdkIdentityView {
} }
@linkedFieldOption(LinkedId.FullName, { sortPosition: 3 }) @linkedFieldOption(LinkedId.FullName, { sortPosition: 3 })
get fullName(): string { get fullName(): string | undefined {
if ( if (
this.title != null || this.title != null ||
this.firstName != null || this.firstName != null ||
@@ -111,11 +109,11 @@ export class IdentityView extends ItemView implements SdkIdentityView {
return name.trim(); return name.trim();
} }
return null; return undefined;
} }
get fullAddress(): string { get fullAddress(): string | undefined {
let address = this.address1; let address = this.address1 ?? "";
if (!Utils.isNullOrWhitespace(this.address2)) { if (!Utils.isNullOrWhitespace(this.address2)) {
if (!Utils.isNullOrWhitespace(address)) { if (!Utils.isNullOrWhitespace(address)) {
address += ", "; address += ", ";
@@ -131,9 +129,9 @@ export class IdentityView extends ItemView implements SdkIdentityView {
return address; return address;
} }
get fullAddressPart2(): string { get fullAddressPart2(): string | undefined {
if (this.city == null && this.state == null && this.postalCode == null) { if (this.city == null && this.state == null && this.postalCode == null) {
return null; return undefined;
} }
const city = this.city || "-"; const city = this.city || "-";
const state = this.state; const state = this.state;
@@ -146,7 +144,7 @@ export class IdentityView extends ItemView implements SdkIdentityView {
return addressPart2; return addressPart2;
} }
get fullAddressForCopy(): string { get fullAddressForCopy(): string | undefined {
let address = this.fullAddress; let address = this.fullAddress;
if (this.city != null || this.state != null || this.postalCode != null) { if (this.city != null || this.state != null || this.postalCode != null) {
address += "\n" + this.fullAddressPart2; address += "\n" + this.fullAddressPart2;
@@ -157,38 +155,34 @@ export class IdentityView extends ItemView implements SdkIdentityView {
return address; return address;
} }
static fromJSON(obj: Partial<Jsonify<IdentityView>>): IdentityView { static fromJSON(obj: Partial<Jsonify<IdentityView>> | undefined): IdentityView {
return Object.assign(new IdentityView(), obj); return Object.assign(new IdentityView(), obj);
} }
/** /**
* Converts the SDK IdentityView to an IdentityView. * Converts the SDK IdentityView to an IdentityView.
*/ */
static fromSdkIdentityView(obj: SdkIdentityView): IdentityView | undefined { static fromSdkIdentityView(obj: SdkIdentityView): IdentityView {
if (obj == null) {
return undefined;
}
const identityView = new IdentityView(); const identityView = new IdentityView();
identityView.title = obj.title ?? null; identityView.title = obj.title;
identityView.firstName = obj.firstName ?? null; identityView.firstName = obj.firstName;
identityView.middleName = obj.middleName ?? null; identityView.middleName = obj.middleName;
identityView.lastName = obj.lastName ?? null; identityView.lastName = obj.lastName;
identityView.address1 = obj.address1 ?? null; identityView.address1 = obj.address1;
identityView.address2 = obj.address2 ?? null; identityView.address2 = obj.address2;
identityView.address3 = obj.address3 ?? null; identityView.address3 = obj.address3;
identityView.city = obj.city ?? null; identityView.city = obj.city;
identityView.state = obj.state ?? null; identityView.state = obj.state;
identityView.postalCode = obj.postalCode ?? null; identityView.postalCode = obj.postalCode;
identityView.country = obj.country ?? null; identityView.country = obj.country;
identityView.company = obj.company ?? null; identityView.company = obj.company;
identityView.email = obj.email ?? null; identityView.email = obj.email;
identityView.phone = obj.phone ?? null; identityView.phone = obj.phone;
identityView.ssn = obj.ssn ?? null; identityView.ssn = obj.ssn;
identityView.username = obj.username ?? null; identityView.username = obj.username;
identityView.passportNumber = obj.passportNumber ?? null; identityView.passportNumber = obj.passportNumber;
identityView.licenseNumber = obj.licenseNumber ?? null; identityView.licenseNumber = obj.licenseNumber;
return identityView; return identityView;
} }

View File

@@ -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 { View } from "../../../models/view/view";
import { LinkedMetadata } from "../../linked-field-option.decorator"; import { LinkedMetadata } from "../../linked-field-option.decorator";
export abstract class ItemView implements View { export abstract class ItemView implements View {
linkedFieldOptions: Map<number, LinkedMetadata>; linkedFieldOptions?: Map<number, LinkedMetadata>;
abstract get subTitle(): string; abstract get subTitle(): string | undefined;
} }

View File

@@ -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 { Jsonify } from "type-fest";
import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal"; import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal";
@@ -11,13 +9,13 @@ import { Utils } from "../../../platform/misc/utils";
import { LoginUri } from "../domain/login-uri"; import { LoginUri } from "../domain/login-uri";
export class LoginUriView implements View { export class LoginUriView implements View {
match: UriMatchStrategySetting = null; match?: UriMatchStrategySetting;
private _uri: string = null; private _uri?: string;
private _domain: string = null; private _domain?: string;
private _hostname: string = null; private _hostname?: string;
private _host: string = null; private _host?: string;
private _canLaunch: boolean = null; private _canLaunch?: boolean;
constructor(u?: LoginUri) { constructor(u?: LoginUri) {
if (!u) { if (!u) {
@@ -27,59 +25,59 @@ export class LoginUriView implements View {
this.match = u.match; this.match = u.match;
} }
get uri(): string { get uri(): string | undefined {
return this._uri; return this._uri;
} }
set uri(value: string) { set uri(value: string | undefined) {
this._uri = value; this._uri = value;
this._domain = null; this._domain = undefined;
this._canLaunch = null; this._canLaunch = undefined;
} }
get domain(): string { get domain(): string | undefined {
if (this._domain == null && this.uri != null) { if (this._domain == null && this.uri != null) {
this._domain = Utils.getDomain(this.uri); this._domain = Utils.getDomain(this.uri);
if (this._domain === "") { if (this._domain === "") {
this._domain = null; this._domain = undefined;
} }
} }
return this._domain; return this._domain;
} }
get hostname(): string { get hostname(): string | undefined {
if (this.match === UriMatchStrategy.RegularExpression) { if (this.match === UriMatchStrategy.RegularExpression) {
return null; return undefined;
} }
if (this._hostname == null && this.uri != null) { if (this._hostname == null && this.uri != null) {
this._hostname = Utils.getHostname(this.uri); this._hostname = Utils.getHostname(this.uri);
if (this._hostname === "") { if (this._hostname === "") {
this._hostname = null; this._hostname = undefined;
} }
} }
return this._hostname; return this._hostname;
} }
get host(): string { get host(): string | undefined {
if (this.match === UriMatchStrategy.RegularExpression) { if (this.match === UriMatchStrategy.RegularExpression) {
return null; return undefined;
} }
if (this._host == null && this.uri != null) { if (this._host == null && this.uri != null) {
this._host = Utils.getHost(this.uri); this._host = Utils.getHost(this.uri);
if (this._host === "") { if (this._host === "") {
this._host = null; this._host = undefined;
} }
} }
return this._host; return this._host;
} }
get hostnameOrUri(): string { get hostnameOrUri(): string | undefined {
return this.hostname != null ? this.hostname : this.uri; return this.hostname != null ? this.hostname : this.uri;
} }
get hostOrUri(): string { get hostOrUri(): string | undefined {
return this.host != null ? this.host : this.uri; return this.host != null ? this.host : this.uri;
} }
@@ -104,7 +102,10 @@ export class LoginUriView implements View {
return this._canLaunch; 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)) return this.uri.indexOf("://") < 0 && !Utils.isNullOrWhitespace(Utils.getDomain(this.uri))
? "http://" + this.uri ? "http://" + this.uri
: this.uri; : this.uri;
@@ -141,7 +142,7 @@ export class LoginUriView implements View {
matchesUri( matchesUri(
targetUri: string, targetUri: string,
equivalentDomains: Set<string>, equivalentDomains: Set<string>,
defaultUriMatch: UriMatchStrategySetting = null, defaultUriMatch?: UriMatchStrategySetting,
/** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */
overrideNeverMatchStrategy?: true, overrideNeverMatchStrategy?: true,
): boolean { ): boolean {
@@ -198,7 +199,7 @@ export class LoginUriView implements View {
if (Utils.DomainMatchBlacklist.has(this.domain)) { if (Utils.DomainMatchBlacklist.has(this.domain)) {
const domainUrlHost = Utils.getHost(targetUri); const domainUrlHost = Utils.getHost(targetUri);
return !Utils.DomainMatchBlacklist.get(this.domain).has(domainUrlHost); return !Utils.DomainMatchBlacklist.get(this.domain)!.has(domainUrlHost);
} }
return true; return true;

View File

@@ -29,11 +29,6 @@ describe("LoginView", () => {
}); });
describe("fromSdkLoginView", () => { 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", () => { it("should return a LoginView from an SdkLoginView", () => {
jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk); jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk);

View File

@@ -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 { LoginView as SdkLoginView } from "@bitwarden/sdk-internal";
import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { UriMatchStrategySetting } from "../../../models/domain/domain-service";
@@ -15,15 +13,15 @@ import { LoginUriView } from "./login-uri.view";
export class LoginView extends ItemView { export class LoginView extends ItemView {
@linkedFieldOption(LinkedId.Username, { sortPosition: 0 }) @linkedFieldOption(LinkedId.Username, { sortPosition: 0 })
username: string = null; username: string | undefined;
@linkedFieldOption(LinkedId.Password, { sortPosition: 1 }) @linkedFieldOption(LinkedId.Password, { sortPosition: 1 })
password: string = null; password: string | undefined;
passwordRevisionDate?: Date = null; passwordRevisionDate?: Date;
totp: string = null; totp: string | undefined;
uris: LoginUriView[] = []; uris: LoginUriView[] = [];
autofillOnPageLoad: boolean = null; autofillOnPageLoad: boolean | undefined;
fido2Credentials: Fido2CredentialView[] = null; fido2Credentials: Fido2CredentialView[] = [];
constructor(l?: Login) { constructor(l?: Login) {
super(); super();
@@ -35,15 +33,15 @@ export class LoginView extends ItemView {
this.autofillOnPageLoad = l.autofillOnPageLoad; this.autofillOnPageLoad = l.autofillOnPageLoad;
} }
get uri(): string { get uri(): string | undefined {
return this.hasUris ? this.uris[0].uri : null; return this.hasUris ? this.uris[0].uri : undefined;
} }
get maskedPassword(): string { get maskedPassword(): string | undefined {
return this.password != null ? "••••••••" : null; return this.password != null ? "••••••••" : undefined;
} }
get subTitle(): string { get subTitle(): string | undefined {
// if there's a passkey available, use that as a fallback // if there's a passkey available, use that as a fallback
if (Utils.isNullOrEmpty(this.username) && this.fido2Credentials?.length > 0) { if (Utils.isNullOrEmpty(this.username) && this.fido2Credentials?.length > 0) {
return this.fido2Credentials[0].userName; return this.fido2Credentials[0].userName;
@@ -60,14 +58,14 @@ export class LoginView extends ItemView {
return !Utils.isNullOrWhitespace(this.totp); return !Utils.isNullOrWhitespace(this.totp);
} }
get launchUri(): string { get launchUri(): string | undefined {
if (this.hasUris) { if (this.hasUris) {
const uri = this.uris.find((u) => u.canLaunch); const uri = this.uris.find((u) => u.canLaunch);
if (uri != null) { if (uri != null) {
return uri.launchUri; return uri.launchUri;
} }
} }
return null; return undefined;
} }
get hasUris(): boolean { get hasUris(): boolean {
@@ -81,7 +79,7 @@ export class LoginView extends ItemView {
matchesUri( matchesUri(
targetUri: string, targetUri: string,
equivalentDomains: Set<string>, equivalentDomains: Set<string>,
defaultUriMatch: UriMatchStrategySetting = null, defaultUriMatch?: UriMatchStrategySetting,
/** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */ /** When present, will override the match strategy for the cipher if it is `Never` with `Domain` */
overrideNeverMatchStrategy?: true, overrideNeverMatchStrategy?: true,
): boolean { ): boolean {
@@ -94,17 +92,20 @@ export class LoginView extends ItemView {
); );
} }
static fromJSON(obj: Partial<DeepJsonify<LoginView>>): LoginView { static fromJSON(obj: Partial<DeepJsonify<LoginView>> | undefined): LoginView {
const passwordRevisionDate = if (obj == undefined) {
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); return new LoginView();
const uris = obj.uris.map((uri) => LoginUriView.fromJSON(uri)); }
const fido2Credentials = obj.fido2Credentials?.map((key) => Fido2CredentialView.fromJSON(key));
return Object.assign(new LoginView(), obj, { const loginView = Object.assign(new LoginView(), obj) as LoginView;
passwordRevisionDate,
uris, loginView.passwordRevisionDate =
fido2Credentials, 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 * the FIDO2 credentials in encrypted form. We can decrypt them later using a separate
* call to client.vault().ciphers().decrypt_fido2_credentials(). * call to client.vault().ciphers().decrypt_fido2_credentials().
*/ */
static fromSdkLoginView(obj: SdkLoginView): LoginView | undefined { static fromSdkLoginView(obj: SdkLoginView): LoginView {
if (obj == null) {
return undefined;
}
const loginView = new LoginView(); const loginView = new LoginView();
loginView.username = obj.username ?? null; loginView.username = obj.username;
loginView.password = obj.password ?? null; loginView.password = obj.password;
loginView.passwordRevisionDate = loginView.passwordRevisionDate =
obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); obj.passwordRevisionDate == null ? undefined : new Date(obj.passwordRevisionDate);
loginView.totp = obj.totp ?? null; loginView.totp = obj.totp;
loginView.autofillOnPageLoad = obj.autofillOnPageLoad ?? null; loginView.autofillOnPageLoad = obj.autofillOnPageLoad;
loginView.uris = loginView.uris =
obj.uris obj.uris
?.filter((uri) => uri.uri != null && uri.uri !== "") ?.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 // FIDO2 credentials are not decrypted here, they remain encrypted
loginView.fido2Credentials = null; loginView.fido2Credentials = [];
return loginView; return loginView;
} }

View File

@@ -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 { Jsonify } from "type-fest";
import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal"; import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal";
@@ -10,7 +8,7 @@ import { SecureNote } from "../domain/secure-note";
import { ItemView } from "./item.view"; import { ItemView } from "./item.view";
export class SecureNoteView extends ItemView implements SdkSecureNoteView { export class SecureNoteView extends ItemView implements SdkSecureNoteView {
type: SecureNoteType = null; type: SecureNoteType = SecureNoteType.Generic;
constructor(n?: SecureNote) { constructor(n?: SecureNote) {
super(); super();
@@ -21,24 +19,20 @@ export class SecureNoteView extends ItemView implements SdkSecureNoteView {
this.type = n.type; this.type = n.type;
} }
get subTitle(): string { get subTitle(): string | undefined {
return null; return undefined;
} }
static fromJSON(obj: Partial<Jsonify<SecureNoteView>>): SecureNoteView { static fromJSON(obj: Partial<Jsonify<SecureNoteView>> | undefined): SecureNoteView {
return Object.assign(new SecureNoteView(), obj); return Object.assign(new SecureNoteView(), obj);
} }
/** /**
* Converts the SDK SecureNoteView to a SecureNoteView. * Converts the SDK SecureNoteView to a SecureNoteView.
*/ */
static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView | undefined { static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView {
if (!obj) {
return undefined;
}
const secureNoteView = new SecureNoteView(); const secureNoteView = new SecureNoteView();
secureNoteView.type = obj.type ?? null; secureNoteView.type = obj.type;
return secureNoteView; return secureNoteView;
} }

View File

@@ -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 { Jsonify } from "type-fest";
import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal"; import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal";
import { SshKey } from "../domain/ssh-key";
import { ItemView } from "./item.view"; import { ItemView } from "./item.view";
export class SshKeyView extends ItemView { export class SshKeyView extends ItemView {
privateKey: string = null; privateKey!: string;
publicKey: string = null; publicKey!: string;
keyFingerprint: string = null; keyFingerprint!: string;
constructor(n?: SshKey) {
super();
if (!n) {
return;
}
}
get maskedPrivateKey(): string { get maskedPrivateKey(): string {
if (!this.privateKey || this.privateKey.length === 0) { if (!this.privateKey || this.privateKey.length === 0) {
@@ -43,23 +32,19 @@ export class SshKeyView extends ItemView {
return this.keyFingerprint; return this.keyFingerprint;
} }
static fromJSON(obj: Partial<Jsonify<SshKeyView>>): SshKeyView { static fromJSON(obj: Partial<Jsonify<SshKeyView>> | undefined): SshKeyView {
return Object.assign(new SshKeyView(), obj); return Object.assign(new SshKeyView(), obj);
} }
/** /**
* Converts the SDK SshKeyView to a SshKeyView. * Converts the SDK SshKeyView to a SshKeyView.
*/ */
static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView | undefined { static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView {
if (!obj) {
return undefined;
}
const sshKeyView = new SshKeyView(); const sshKeyView = new SshKeyView();
sshKeyView.privateKey = obj.privateKey ?? null; sshKeyView.privateKey = obj.privateKey;
sshKeyView.publicKey = obj.publicKey ?? null; sshKeyView.publicKey = obj.publicKey;
sshKeyView.keyFingerprint = obj.fingerprint ?? null; sshKeyView.keyFingerprint = obj.fingerprint;
return sshKeyView; return sshKeyView;
} }

View File

@@ -298,6 +298,10 @@ describe("CipherViewLikeUtils", () => {
(cipherView.attachments as any) = null; (cipherView.attachments as any) = null;
expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false); expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false);
cipherView.attachments = [];
expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false);
}); });
}); });

View File

@@ -193,7 +193,6 @@ export abstract class BaseImporter {
if (this.isNullOrWhitespace(loginUri.uri)) { if (this.isNullOrWhitespace(loginUri.uri)) {
return null; return null;
} }
loginUri.match = null;
return [loginUri]; return [loginUri];
} }
@@ -205,7 +204,6 @@ export abstract class BaseImporter {
if (this.isNullOrWhitespace(loginUri.uri)) { if (this.isNullOrWhitespace(loginUri.uri)) {
return; return;
} }
loginUri.match = null;
returnArr.push(loginUri); returnArr.push(loginUri);
}); });
return returnArr.length === 0 ? null : returnArr; return returnArr.length === 0 ? null : returnArr;
@@ -236,7 +234,7 @@ export abstract class BaseImporter {
return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname; return hostname.startsWith("www.") ? hostname.replace("www.", "") : hostname;
} }
protected isNullOrWhitespace(str: string): boolean { protected isNullOrWhitespace(str: string | undefined | null): boolean {
return Utils.isNullOrWhitespace(str); return Utils.isNullOrWhitespace(str);
} }

View File

@@ -11,9 +11,6 @@ const CipherData = [
title: "should parse app name", title: "should parse app name",
csv: androidData, csv: androidData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "com.xyz.example.app.android", name: "com.xyz.example.app.android",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "username@example.com", username: "username@example.com",
@@ -24,7 +21,6 @@ const CipherData = [
}), }),
], ],
}), }),
notes: null,
type: 1, type: 1,
}), }),
}, },
@@ -32,9 +28,6 @@ const CipherData = [
title: "should parse password", title: "should parse password",
csv: simplePasswordData, csv: simplePasswordData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "www.example.com", name: "www.example.com",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "username@example.com", username: "username@example.com",
@@ -45,7 +38,6 @@ const CipherData = [
}), }),
], ],
}), }),
notes: null,
type: 1, type: 1,
}), }),
}, },
@@ -54,6 +46,7 @@ const CipherData = [
describe("Chrome CSV Importer", () => { describe("Chrome CSV Importer", () => {
CipherData.forEach((data) => { CipherData.forEach((data) => {
it(data.title, async () => { it(data.title, async () => {
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
const importer = new ChromeCsvImporter(); const importer = new ChromeCsvImporter();
const result = await importer.parse(data.csv); const result = await importer.parse(data.csv);
expect(result != null).toBe(true); expect(result != null).toBe(true);

View File

@@ -59,12 +59,12 @@ describe("Dashlane CSV Importer", () => {
const cipher = result.ciphers.shift(); const cipher = result.ciphers.shift();
expect(cipher.type).toBe(CipherType.Card); expect(cipher.type).toBe(CipherType.Card);
expect(cipher.name).toBe("John's savings account"); 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.cardholderName).toBe("John Doe");
expect(cipher.card.number).toBe("accountNumber"); expect(cipher.card.number).toBe("accountNumber");
expect(cipher.card.code).toBeNull(); expect(cipher.card.code).toBeUndefined();
expect(cipher.card.expMonth).toBeNull(); expect(cipher.card.expMonth).toBeUndefined();
expect(cipher.card.expYear).toBeNull(); expect(cipher.card.expYear).toBeUndefined();
expect(cipher.fields.length).toBe(4); expect(cipher.fields.length).toBe(4);
@@ -112,7 +112,7 @@ describe("Dashlane CSV Importer", () => {
expect(cipher.name).toBe("John Doe card"); expect(cipher.name).toBe("John Doe card");
expect(cipher.identity.fullName).toBe("John Doe"); expect(cipher.identity.fullName).toBe("John Doe");
expect(cipher.identity.firstName).toBe("John"); 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.lastName).toBe("Doe");
expect(cipher.identity.licenseNumber).toBe("123123123"); expect(cipher.identity.licenseNumber).toBe("123123123");
@@ -133,7 +133,7 @@ describe("Dashlane CSV Importer", () => {
expect(cipher2.name).toBe("John Doe passport"); expect(cipher2.name).toBe("John Doe passport");
expect(cipher2.identity.fullName).toBe("John Doe"); expect(cipher2.identity.fullName).toBe("John Doe");
expect(cipher2.identity.firstName).toBe("John"); 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.lastName).toBe("Doe");
expect(cipher2.identity.passportNumber).toBe("123123123"); expect(cipher2.identity.passportNumber).toBe("123123123");
@@ -154,7 +154,7 @@ describe("Dashlane CSV Importer", () => {
expect(cipher3.name).toBe("John Doe license"); expect(cipher3.name).toBe("John Doe license");
expect(cipher3.identity.fullName).toBe("John Doe"); expect(cipher3.identity.fullName).toBe("John Doe");
expect(cipher3.identity.firstName).toBe("John"); 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.lastName).toBe("Doe");
expect(cipher3.identity.licenseNumber).toBe("1234556"); expect(cipher3.identity.licenseNumber).toBe("1234556");
expect(cipher3.identity.state).toBe("DC"); expect(cipher3.identity.state).toBe("DC");
@@ -173,7 +173,7 @@ describe("Dashlane CSV Importer", () => {
expect(cipher4.name).toBe("John Doe social_security"); expect(cipher4.name).toBe("John Doe social_security");
expect(cipher4.identity.fullName).toBe("John Doe"); expect(cipher4.identity.fullName).toBe("John Doe");
expect(cipher4.identity.firstName).toBe("John"); 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.lastName).toBe("Doe");
expect(cipher4.identity.ssn).toBe("123123123"); expect(cipher4.identity.ssn).toBe("123123123");

View File

@@ -11,9 +11,6 @@ const CipherData = [
title: "should parse password", title: "should parse password",
csv: simplePasswordData, csv: simplePasswordData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com", name: "example.com",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "foo", username: "foo",
@@ -24,7 +21,6 @@ const CipherData = [
}), }),
], ],
}), }),
notes: null,
type: 1, type: 1,
}), }),
}, },
@@ -32,9 +28,6 @@ const CipherData = [
title: 'should skip "chrome://FirefoxAccounts"', title: 'should skip "chrome://FirefoxAccounts"',
csv: firefoxAccountsData, csv: firefoxAccountsData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com", name: "example.com",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "foo", username: "foo",
@@ -45,7 +38,6 @@ const CipherData = [
}), }),
], ],
}), }),
notes: null,
type: 1, type: 1,
}), }),
}, },
@@ -54,6 +46,7 @@ const CipherData = [
describe("Firefox CSV Importer", () => { describe("Firefox CSV Importer", () => {
CipherData.forEach((data) => { CipherData.forEach((data) => {
it(data.title, async () => { it(data.title, async () => {
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
const importer = new FirefoxCsvImporter(); const importer = new FirefoxCsvImporter();
const result = await importer.parse(data.csv); const result = await importer.parse(data.csv);
expect(result != null).toBe(true); expect(result != null).toBe(true);

View File

@@ -51,10 +51,10 @@ describe("Keeper CSV Importer", () => {
expect(result != null).toBe(true); expect(result != null).toBe(true);
const cipher = result.ciphers.shift(); const cipher = result.ciphers.shift();
expect(cipher.login.totp).toBeNull(); expect(cipher.login.totp).toBeUndefined();
const cipher2 = result.ciphers.shift(); const cipher2 = result.ciphers.shift();
expect(cipher2.login.totp).toBeNull(); expect(cipher2.login.totp).toBeUndefined();
const cipher3 = result.ciphers.shift(); const cipher3 = result.ciphers.shift();
expect(cipher3.login.totp).toEqual( expect(cipher3.login.totp).toEqual(

View File

@@ -51,7 +51,7 @@ describe("Keeper Json Importer", () => {
expect(result != null).toBe(true); expect(result != null).toBe(true);
const cipher = result.ciphers.shift(); const cipher = result.ciphers.shift();
expect(cipher.login.totp).toBeNull(); expect(cipher.login.totp).toBeUndefined();
// 2nd Cipher // 2nd Cipher
const cipher2 = result.ciphers.shift(); const cipher2 = result.ciphers.shift();

View File

@@ -37,9 +37,6 @@ Expiration Date:June,2020
Notes:some text Notes:some text
",Credit-card,,0`, ",Credit-card,,0`,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "Credit-card", name: "Credit-card",
notes: "some text\n", notes: "some text\n",
type: 3, type: 3,
@@ -71,11 +68,7 @@ Start Date:,
Expiration Date:, Expiration Date:,
Notes:",empty,,0`, Notes:",empty,,0`,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "empty", name: "empty",
notes: null,
type: 3, type: 3,
card: { card: {
expMonth: undefined, expMonth: undefined,
@@ -101,11 +94,7 @@ Start Date:,
Expiration Date:January, Expiration Date:January,
Notes:",noyear,,0`, Notes:",noyear,,0`,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "noyear", name: "noyear",
notes: null,
type: 3, type: 3,
card: { card: {
cardholderName: "John Doe", cardholderName: "John Doe",
@@ -139,11 +128,7 @@ Start Date:,
Expiration Date:,2020 Expiration Date:,2020
Notes:",nomonth,,0`, Notes:",nomonth,,0`,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "nomonth", name: "nomonth",
notes: null,
type: 3, type: 3,
card: { card: {
cardholderName: "John Doe", cardholderName: "John Doe",
@@ -171,6 +156,7 @@ Notes:",nomonth,,0`,
describe("Lastpass CSV Importer", () => { describe("Lastpass CSV Importer", () => {
CipherData.forEach((data) => { CipherData.forEach((data) => {
it(data.title, async () => { it(data.title, async () => {
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
const importer = new LastPassCsvImporter(); const importer = new LastPassCsvImporter();
const result = await importer.parse(data.csv); const result = await importer.parse(data.csv);
expect(result != null).toBe(true); expect(result != null).toBe(true);

View File

@@ -468,8 +468,8 @@ describe("Myki CSV Importer", () => {
const cipher = result.ciphers.shift(); const cipher = result.ciphers.shift();
expect(cipher.name).toEqual("2FA nickname"); expect(cipher.name).toEqual("2FA nickname");
expect(cipher.login.username).toBeNull(); expect(cipher.login.username).toBeUndefined();
expect(cipher.login.password).toBeNull(); expect(cipher.login.password).toBeUndefined();
expect(cipher.login.totp).toBe("someTOTPSeed"); expect(cipher.login.totp).toBe("someTOTPSeed");
expect(cipher.notes).toEqual("Additional information field content."); expect(cipher.notes).toEqual("Additional information field content.");

View File

@@ -17,8 +17,8 @@ const namesTestData = [
fullName: "MyFirstName", fullName: "MyFirstName",
expected: Object.assign(new IdentityView(), { expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName", firstName: "MyFirstName",
middleName: null, middleName: undefined,
lastName: null, lastName: undefined,
}), }),
}, },
{ {
@@ -26,7 +26,7 @@ const namesTestData = [
fullName: "MyFirstName MyLastName", fullName: "MyFirstName MyLastName",
expected: Object.assign(new IdentityView(), { expected: Object.assign(new IdentityView(), {
firstName: "MyFirstName", firstName: "MyFirstName",
middleName: null, middleName: undefined,
lastName: "MyLastName", lastName: "MyLastName",
}), }),
}, },

View File

@@ -393,7 +393,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("Michael"); expect(identity.firstName).toEqual("Michael");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Scarn"); expect(identity.lastName).toEqual("Scarn");
expect(identity.address1).toEqual("2120 Mifflin Rd."); expect(identity.address1).toEqual("2120 Mifflin Rd.");
expect(identity.state).toEqual("Pennsylvania"); expect(identity.state).toEqual("Pennsylvania");
@@ -423,7 +423,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("Cash"); expect(identity.firstName).toEqual("Cash");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Bandit"); expect(identity.lastName).toEqual("Bandit");
expect(identity.state).toEqual("Washington"); expect(identity.state).toEqual("Washington");
expect(identity.country).toEqual("United States of America"); expect(identity.country).toEqual("United States of America");
@@ -447,7 +447,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("George"); expect(identity.firstName).toEqual("George");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Engels"); expect(identity.lastName).toEqual("Engels");
expect(identity.company).toEqual("National Public Library"); expect(identity.company).toEqual("National Public Library");
expect(identity.phone).toEqual("9995555555"); expect(identity.phone).toEqual("9995555555");
@@ -472,7 +472,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("David"); expect(identity.firstName).toEqual("David");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Global"); expect(identity.lastName).toEqual("Global");
expect(identity.passportNumber).toEqual("76436847"); expect(identity.passportNumber).toEqual("76436847");
@@ -499,7 +499,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("Chef"); expect(identity.firstName).toEqual("Chef");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Coldroom"); expect(identity.lastName).toEqual("Coldroom");
expect(identity.company).toEqual("Super Cool Store Co."); expect(identity.company).toEqual("Super Cool Store Co.");
@@ -523,7 +523,7 @@ describe("1Password 1Pux Importer", () => {
const identity = cipher.identity; const identity = cipher.identity;
expect(identity.firstName).toEqual("Jack"); expect(identity.firstName).toEqual("Jack");
expect(identity.middleName).toBeNull(); expect(identity.middleName).toBeUndefined();
expect(identity.lastName).toEqual("Judd"); expect(identity.lastName).toEqual("Judd");
expect(identity.ssn).toEqual("131-216-1900"); expect(identity.ssn).toEqual("131-216-1900");
}); });
@@ -682,12 +682,12 @@ describe("1Password 1Pux Importer", () => {
expect(folders[3].name).toBe("Education"); expect(folders[3].name).toBe("Education");
expect(folders[4].name).toBe("Starter Kit"); expect(folders[4].name).toBe("Starter Kit");
// Check that ciphers have a folder assigned to them // Check that folder/cipher relationships
expect(result.ciphers.filter((c) => c.folderId === folders[0].id).length).toBeGreaterThan(0); expect(result.folderRelationships.filter(([_, f]) => f == 0).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[1].id).length).toBeGreaterThan(0); expect(result.folderRelationships.filter(([_, f]) => f == 1).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[2].id).length).toBeGreaterThan(0); expect(result.folderRelationships.filter(([_, f]) => f == 2).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[3].id).length).toBeGreaterThan(0); expect(result.folderRelationships.filter(([_, f]) => f == 3).length).toBeGreaterThan(0);
expect(result.ciphers.filter((c) => c.folderId === folders[4].id).length).toBeGreaterThan(0); expect(result.folderRelationships.filter(([_, f]) => f == 4).length).toBeGreaterThan(0);
}); });
it("should create collections if part of an organization", async () => { it("should create collections if part of an organization", async () => {

View File

@@ -11,9 +11,6 @@ const CipherData = [
title: "should parse URLs in new CSV format", title: "should parse URLs in new CSV format",
csv: simplePasswordData, csv: simplePasswordData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com (example_user)", name: "example.com (example_user)",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "example_user", username: "example_user",
@@ -33,9 +30,6 @@ const CipherData = [
title: "should parse URLs in old CSV format", title: "should parse URLs in old CSV format",
csv: oldSimplePasswordData, csv: oldSimplePasswordData,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "example.com (example_user)", name: "example.com (example_user)",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "example_user", username: "example_user",
@@ -45,6 +39,7 @@ const CipherData = [
uri: "https://example.com", uri: "https://example.com",
}), }),
], ],
totp: null,
}), }),
type: 1, type: 1,
}), }),
@@ -54,6 +49,7 @@ const CipherData = [
describe("Safari CSV Importer", () => { describe("Safari CSV Importer", () => {
CipherData.forEach((data) => { CipherData.forEach((data) => {
it(data.title, async () => { it(data.title, async () => {
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
const importer = new SafariCsvImporter(); const importer = new SafariCsvImporter();
const result = await importer.parse(data.csv); const result = await importer.parse(data.csv);
expect(result != null).toBe(true); expect(result != null).toBe(true);

View File

@@ -11,9 +11,6 @@ const CipherData = [
title: "should parse Zoho Vault CSV format", title: "should parse Zoho Vault CSV format",
csv: samplezohovaultcsvdata, csv: samplezohovaultcsvdata,
expected: Object.assign(new CipherView(), { expected: Object.assign(new CipherView(), {
id: null,
organizationId: null,
folderId: null,
name: "XYZ Test", name: "XYZ Test",
login: Object.assign(new LoginView(), { login: Object.assign(new LoginView(), {
username: "email@domain.de", username: "email@domain.de",
@@ -41,6 +38,7 @@ describe("Zoho Vault CSV Importer", () => {
CipherData.forEach((data) => { CipherData.forEach((data) => {
it(data.title, async () => { it(data.title, async () => {
jest.useFakeTimers().setSystemTime(data.expected.creationDate);
const importer = new ZohoVaultCsvImporter(); const importer = new ZohoVaultCsvImporter();
const result = await importer.parse(data.csv); const result = await importer.parse(data.csv);
expect(result != null).toBe(true); expect(result != null).toBe(true);

View File

@@ -86,7 +86,10 @@ describe("AdditionalOptionsSectionComponent", () => {
expect(cipherFormProvider.patchCipher).toHaveBeenCalled(); expect(cipherFormProvider.patchCipher).toHaveBeenCalled();
const patchFn = cipherFormProvider.patchCipher.mock.lastCall[0]; 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); expect(updated).toEqual(expectedCipher);
}); });

View File

@@ -66,7 +66,7 @@ export class DeleteAttachmentComponent {
await this.cipherService.deleteAttachmentWithServer( await this.cipherService.deleteAttachmentWithServer(
this.cipherId, this.cipherId,
this.attachment.id, this.attachment.id!,
activeUserId, activeUserId,
this.admin, this.admin,
); );

View File

@@ -67,6 +67,7 @@ describe("CardDetailsSectionComponent", () => {
cardView.brand = "Visa"; cardView.brand = "Visa";
cardView.expMonth = ""; cardView.expMonth = "";
cardView.code = ""; cardView.code = "";
cardView.expYear = "";
expect(patchCipherSpy).toHaveBeenCalled(); expect(patchCipherSpy).toHaveBeenCalled();
const patchFn = patchCipherSpy.mock.lastCall[0]; const patchFn = patchCipherSpy.mock.lastCall[0];
@@ -85,6 +86,7 @@ describe("CardDetailsSectionComponent", () => {
cardView.number = ""; cardView.number = "";
cardView.expMonth = ""; cardView.expMonth = "";
cardView.code = ""; cardView.code = "";
cardView.brand = "";
cardView.expYear = "2022"; cardView.expYear = "2022";
expect(patchCipherSpy).toHaveBeenCalled(); expect(patchCipherSpy).toHaveBeenCalled();
@@ -122,8 +124,6 @@ describe("CardDetailsSectionComponent", () => {
number, number,
code, code,
brand: cardView.brand, brand: cardView.brand,
expMonth: null,
expYear: null,
}); });
}); });

View File

@@ -52,12 +52,12 @@ export class CardDetailsSectionComponent implements OnInit {
* leaving as just null gets inferred as `unknown` * leaving as just null gets inferred as `unknown`
*/ */
cardDetailsForm = this.formBuilder.group({ cardDetailsForm = this.formBuilder.group({
cardholderName: null as string | null, cardholderName: "",
number: null as string | null, number: "",
brand: null as string | null, brand: "",
expMonth: null as string | null, expMonth: "",
expYear: null as string | number | null, expYear: "" as string | number,
code: null as string | null, code: "",
}); });
/** Available Card Brands */ /** Available Card Brands */
@@ -110,16 +110,14 @@ export class CardDetailsSectionComponent implements OnInit {
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(({ cardholderName, number, brand, expMonth, expYear, code }) => { .subscribe(({ cardholderName, number, brand, expMonth, expYear, code }) => {
this.cipherFormContainer.patchCipher((cipher) => { this.cipherFormContainer.patchCipher((cipher) => {
const expirationYear = normalizeExpiryYearFormat(expYear); const expirationYear = normalizeExpiryYearFormat(expYear) ?? "";
Object.assign(cipher.card, { cipher.card.cardholderName = cardholderName;
cardholderName, cipher.card.number = number;
number, cipher.card.brand = brand;
brand, cipher.card.expMonth = expMonth;
expMonth, cipher.card.expYear = expirationYear;
expYear: expirationYear, cipher.card.code = code;
code,
});
return cipher; return cipher;
}); });
@@ -167,6 +165,7 @@ export class CardDetailsSectionComponent implements OnInit {
expMonth: this.initialValues?.expMonth || "", expMonth: this.initialValues?.expMonth || "",
expYear: this.initialValues?.expYear || "", expYear: this.initialValues?.expYear || "",
code: this.initialValues?.code || "", 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,
});
}
} }

View File

@@ -386,7 +386,7 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
fieldView.type = field.type; fieldView.type = field.type;
fieldView.name = field.name; fieldView.name = field.name;
fieldView.value = value; fieldView.value = value;
fieldView.linkedId = field.linkedId; fieldView.linkedId = field.linkedId ?? undefined;
return fieldView; return fieldView;
}); });

View File

@@ -172,7 +172,7 @@ export class IdentitySectionComponent implements OnInit {
populateFormData(cipherView: CipherView) { populateFormData(cipherView: CipherView) {
const { identity } = cipherView; const { identity } = cipherView;
this.identityForm.setValue({ this.identityForm.patchValue({
title: identity.title, title: identity.title,
firstName: identity.firstName, firstName: identity.firstName,
middleName: identity.middleName, middleName: identity.middleName,

View File

@@ -312,7 +312,7 @@ export class ItemDetailsSectionComponent implements OnInit {
private async initFromExistingCipher(prefillCipher: CipherView) { private async initFromExistingCipher(prefillCipher: CipherView) {
const { name, folderId, collectionIds } = prefillCipher; const { name, folderId, collectionIds } = prefillCipher;
this.itemDetailsForm.setValue({ this.itemDetailsForm.patchValue({
name: name ? name : (this.initialValues?.name ?? ""), name: name ? name : (this.initialValues?.name ?? ""),
organizationId: prefillCipher.organizationId, // We do not allow changing ownership of an existing cipher. organizationId: prefillCipher.organizationId, // We do not allow changing ownership of an existing cipher.
folderId: folderId ? folderId : (this.initialValues?.folderId ?? null), folderId: folderId ? folderId : (this.initialValues?.folderId ?? null),

View File

@@ -36,7 +36,7 @@ export class DefaultCipherFormService implements CipherFormService {
let savedCipher: Cipher; let savedCipher: Cipher;
// Creating a new cipher // Creating a new cipher
if (cipher.id == null) { if (cipher.id == null || cipher.id === "") {
const encrypted = await this.cipherService.encrypt(cipher, activeUserId); const encrypted = await this.cipherService.encrypt(cipher, activeUserId);
savedCipher = await this.cipherService.createWithServer(encrypted, config.admin); savedCipher = await this.cipherService.createWithServer(encrypted, config.admin);
return await this.cipherService.decrypt(savedCipher, activeUserId); return await this.cipherService.decrypt(savedCipher, activeUserId);

View File

@@ -44,7 +44,7 @@ import { VaultAutosizeReadOnlyTextArea } from "../../directives/readonly-textare
export class CustomFieldV2Component implements OnInit, OnChanges { export class CustomFieldV2Component implements OnInit, OnChanges {
@Input({ required: true }) cipher!: CipherView; @Input({ required: true }) cipher!: CipherView;
fieldType = FieldType; fieldType = FieldType;
fieldOptions: Map<number, LinkedMetadata> | null = null; fieldOptions: Map<number, LinkedMetadata> | undefined;
/** Indexes of hidden fields that are revealed */ /** Indexes of hidden fields that are revealed */
revealedHiddenFields: number[] = []; revealedHiddenFields: number[] = [];
@@ -124,7 +124,7 @@ export class CustomFieldV2Component implements OnInit, OnChanges {
case CipherType.Identity: case CipherType.Identity:
return IdentityView.prototype.linkedFieldOptions; return IdentityView.prototype.linkedFieldOptions;
default: default:
return null; return undefined;
} }
} }
} }