mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
[PM-24533] Initialize Archive Feature (#16226)
* [PM-19237] Add Archive Filter Type (#13852) * Browser can archive and unarchive items * Create Archive Cipher Service * Add flag and premium permissions to Archive --------- Co-authored-by: SmithThe4th <gsmith@bitwarden.com> Co-authored-by: Shane <smelton@bitwarden.com> Co-authored-by: Patrick Pimentel <ppimentel@bitwarden.com>
This commit is contained in:
@@ -53,6 +53,9 @@ export enum FeatureFlag {
|
||||
IpcChannelFramework = "ipc-channel-framework",
|
||||
InactiveUserServerNotification = "pm-25130-receive-push-notifications-for-inactive-users",
|
||||
PushNotificationsWhenLocked = "pm-19388-push-notifications-when-locked",
|
||||
|
||||
/* Innovation */
|
||||
PM19148_InnovationArchive = "pm-19148-innovation-archive",
|
||||
}
|
||||
|
||||
export type AllowedFeatureFlagTypes = boolean | number | string;
|
||||
@@ -112,6 +115,9 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.IpcChannelFramework]: FALSE,
|
||||
[FeatureFlag.InactiveUserServerNotification]: FALSE,
|
||||
[FeatureFlag.PushNotificationsWhenLocked]: FALSE,
|
||||
|
||||
/* Innovation */
|
||||
[FeatureFlag.PM19148_InnovationArchive]: FALSE,
|
||||
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
|
||||
|
||||
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
|
||||
|
||||
@@ -36,6 +36,7 @@ export class CipherExport {
|
||||
req.creationDate = null;
|
||||
req.revisionDate = null;
|
||||
req.deletedDate = null;
|
||||
req.archivedDate = null;
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -84,6 +85,7 @@ export class CipherExport {
|
||||
view.creationDate = req.creationDate ? new Date(req.creationDate) : view.creationDate;
|
||||
view.revisionDate = req.revisionDate ? new Date(req.revisionDate) : view.revisionDate;
|
||||
view.deletedDate = req.deletedDate ? new Date(req.deletedDate) : view.deletedDate;
|
||||
view.archivedDate = req.archivedDate ? new Date(req.archivedDate) : view.archivedDate;
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -128,6 +130,7 @@ export class CipherExport {
|
||||
domain.creationDate = req.creationDate ? new Date(req.creationDate) : null;
|
||||
domain.revisionDate = req.revisionDate ? new Date(req.revisionDate) : null;
|
||||
domain.deletedDate = req.deletedDate ? new Date(req.deletedDate) : null;
|
||||
domain.archivedDate = req.archivedDate ? new Date(req.archivedDate) : null;
|
||||
return domain;
|
||||
}
|
||||
|
||||
@@ -149,6 +152,7 @@ export class CipherExport {
|
||||
revisionDate: Date = null;
|
||||
creationDate: Date = null;
|
||||
deletedDate: Date = null;
|
||||
archivedDate: Date = null;
|
||||
key: string;
|
||||
|
||||
// Use build method instead of ctor so that we can control order of JSON stringify for pretty print
|
||||
@@ -195,5 +199,6 @@ export class CipherExport {
|
||||
this.creationDate = o.creationDate;
|
||||
this.revisionDate = o.revisionDate;
|
||||
this.deletedDate = o.deletedDate;
|
||||
this.archivedDate = o.archivedDate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ export class CipherData {
|
||||
passwordHistory?: PasswordHistoryData[];
|
||||
collectionIds?: string[];
|
||||
creationDate: string;
|
||||
deletedDate: string | null;
|
||||
deletedDate: string | undefined;
|
||||
archivedDate: string | undefined;
|
||||
reprompt: CipherRepromptType;
|
||||
key: string;
|
||||
|
||||
@@ -63,6 +64,7 @@ export class CipherData {
|
||||
this.collectionIds = collectionIds != null ? collectionIds : response.collectionIds;
|
||||
this.creationDate = response.creationDate;
|
||||
this.deletedDate = response.deletedDate;
|
||||
this.archivedDate = response.archivedDate;
|
||||
this.reprompt = response.reprompt;
|
||||
this.key = response.key;
|
||||
|
||||
|
||||
@@ -60,13 +60,14 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
localData: null,
|
||||
creationDate: null,
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: undefined,
|
||||
attachments: null,
|
||||
fields: null,
|
||||
passwordHistory: null,
|
||||
key: null,
|
||||
permissions: undefined,
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,7 +85,7 @@ describe("Cipher DTO", () => {
|
||||
cipher.name = mockEnc("EncryptedString");
|
||||
cipher.notes = mockEnc("EncryptedString");
|
||||
cipher.creationDate = new Date("2022-01-01T12:00:00.000Z");
|
||||
cipher.deletedDate = null;
|
||||
cipher.deletedDate = undefined;
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
cipher.key = mockEnc("EncKey");
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
@@ -123,7 +124,7 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
localData: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
@@ -149,10 +150,11 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncryptedString",
|
||||
archivedDate: undefined,
|
||||
login: {
|
||||
uris: [
|
||||
{
|
||||
@@ -224,10 +226,11 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
localData: null,
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
reprompt: 0,
|
||||
key: { encryptedString: "EncryptedString", encryptionType: 0 },
|
||||
archivedDate: undefined,
|
||||
login: {
|
||||
passwordRevisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
autofillOnPageLoad: false,
|
||||
@@ -302,10 +305,11 @@ describe("Cipher DTO", () => {
|
||||
cipher.name = mockEnc("EncryptedString");
|
||||
cipher.notes = mockEnc("EncryptedString");
|
||||
cipher.creationDate = new Date("2022-01-01T12:00:00.000Z");
|
||||
cipher.deletedDate = null;
|
||||
cipher.deletedDate = undefined;
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
cipher.key = mockEnc("EncKey");
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
cipher.archivedDate = undefined;
|
||||
|
||||
const loginView = new LoginView();
|
||||
loginView.username = "username";
|
||||
@@ -347,10 +351,11 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
localData: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -372,13 +377,14 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncKey",
|
||||
secureNote: {
|
||||
type: SecureNoteType.Generic,
|
||||
},
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -401,7 +407,7 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
localData: null,
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
secureNote: { type: SecureNoteType.Generic },
|
||||
attachments: null,
|
||||
@@ -409,6 +415,7 @@ describe("Cipher DTO", () => {
|
||||
passwordHistory: null,
|
||||
key: { encryptedString: "EncKey", encryptionType: 0 },
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -431,12 +438,13 @@ describe("Cipher DTO", () => {
|
||||
cipher.name = mockEnc("EncryptedString");
|
||||
cipher.notes = mockEnc("EncryptedString");
|
||||
cipher.creationDate = new Date("2022-01-01T12:00:00.000Z");
|
||||
cipher.deletedDate = null;
|
||||
cipher.deletedDate = undefined;
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
cipher.secureNote = new SecureNote();
|
||||
cipher.secureNote.type = SecureNoteType.Generic;
|
||||
cipher.key = mockEnc("EncKey");
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
cipher.archivedDate = undefined;
|
||||
|
||||
const keyService = mock<KeyService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
@@ -470,10 +478,11 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
localData: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -495,7 +504,7 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
reprompt: CipherRepromptType.None,
|
||||
card: {
|
||||
@@ -507,6 +516,7 @@ describe("Cipher DTO", () => {
|
||||
code: "EncryptedString",
|
||||
},
|
||||
key: "EncKey",
|
||||
archivedDate: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -529,7 +539,7 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
localData: null,
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
card: {
|
||||
cardholderName: { encryptedString: "EncryptedString", encryptionType: 0 },
|
||||
@@ -544,6 +554,7 @@ describe("Cipher DTO", () => {
|
||||
passwordHistory: null,
|
||||
key: { encryptedString: "EncKey", encryptionType: 0 },
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -566,10 +577,11 @@ describe("Cipher DTO", () => {
|
||||
cipher.name = mockEnc("EncryptedString");
|
||||
cipher.notes = mockEnc("EncryptedString");
|
||||
cipher.creationDate = new Date("2022-01-01T12:00:00.000Z");
|
||||
cipher.deletedDate = null;
|
||||
cipher.deletedDate = undefined;
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
cipher.key = mockEnc("EncKey");
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
cipher.archivedDate = undefined;
|
||||
|
||||
const cardView = new CardView();
|
||||
cardView.cardholderName = "cardholderName";
|
||||
@@ -611,10 +623,11 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
localData: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -636,10 +649,11 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncKey",
|
||||
archivedDate: undefined,
|
||||
identity: {
|
||||
title: "EncryptedString",
|
||||
firstName: "EncryptedString",
|
||||
@@ -682,8 +696,9 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
localData: null,
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
archivedDate: undefined,
|
||||
identity: {
|
||||
title: { encryptedString: "EncryptedString", encryptionType: 0 },
|
||||
firstName: { encryptedString: "EncryptedString", encryptionType: 0 },
|
||||
@@ -731,10 +746,11 @@ describe("Cipher DTO", () => {
|
||||
cipher.name = mockEnc("EncryptedString");
|
||||
cipher.notes = mockEnc("EncryptedString");
|
||||
cipher.creationDate = new Date("2022-01-01T12:00:00.000Z");
|
||||
cipher.deletedDate = null;
|
||||
cipher.deletedDate = undefined;
|
||||
cipher.reprompt = CipherRepromptType.None;
|
||||
cipher.key = mockEnc("EncKey");
|
||||
cipher.permissions = new CipherPermissionsApi();
|
||||
cipher.archivedDate = undefined;
|
||||
|
||||
const identityView = new IdentityView();
|
||||
identityView.firstName = "firstName";
|
||||
@@ -776,10 +792,11 @@ describe("Cipher DTO", () => {
|
||||
collectionIds: undefined,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creationDate: new Date("2022-01-01T12:00:00.000Z"),
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
reprompt: 0,
|
||||
localData: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
archivedDate: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -793,6 +810,7 @@ describe("Cipher DTO", () => {
|
||||
|
||||
const revisionDate = new Date("2022-08-04T01:06:40.441Z");
|
||||
const deletedDate = new Date("2022-09-04T01:06:40.441Z");
|
||||
const archivedDate = new Date("2022-10-04T01:06:40.441Z");
|
||||
const actual = Cipher.fromJSON({
|
||||
name: "myName",
|
||||
notes: "myNotes",
|
||||
@@ -801,6 +819,7 @@ describe("Cipher DTO", () => {
|
||||
fields: ["field1", "field2"] as any,
|
||||
passwordHistory: ["ph1", "ph2"] as any,
|
||||
deletedDate: deletedDate.toISOString(),
|
||||
archivedDate: archivedDate.toISOString(),
|
||||
} as Jsonify<Cipher>);
|
||||
|
||||
expect(actual).toMatchObject({
|
||||
@@ -811,6 +830,7 @@ describe("Cipher DTO", () => {
|
||||
fields: ["field1_fromJSON", "field2_fromJSON"],
|
||||
passwordHistory: ["ph1_fromJSON", "ph2_fromJSON"],
|
||||
deletedDate: deletedDate,
|
||||
archivedDate: archivedDate,
|
||||
});
|
||||
expect(actual).toBeInstanceOf(Cipher);
|
||||
});
|
||||
@@ -862,7 +882,8 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
archivedDate: undefined,
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncryptedString",
|
||||
login: {
|
||||
@@ -1084,6 +1105,7 @@ describe("Cipher DTO", () => {
|
||||
],
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: undefined,
|
||||
archivedDate: undefined,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
};
|
||||
|
||||
@@ -1105,7 +1127,8 @@ describe("Cipher DTO", () => {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
archivedDate: undefined,
|
||||
reprompt: CipherRepromptType.None,
|
||||
key: "EncryptedString",
|
||||
login: {
|
||||
|
||||
@@ -56,7 +56,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
passwordHistory: Password[];
|
||||
collectionIds: string[];
|
||||
creationDate: Date;
|
||||
deletedDate: Date;
|
||||
deletedDate: Date | undefined;
|
||||
archivedDate: Date | undefined;
|
||||
reprompt: CipherRepromptType;
|
||||
key: EncString;
|
||||
|
||||
@@ -94,7 +95,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
this.collectionIds = obj.collectionIds;
|
||||
this.localData = localData;
|
||||
this.creationDate = obj.creationDate != null ? new Date(obj.creationDate) : null;
|
||||
this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : null;
|
||||
this.deletedDate = obj.deletedDate != null ? new Date(obj.deletedDate) : undefined;
|
||||
this.archivedDate = obj.archivedDate != null ? new Date(obj.archivedDate) : undefined;
|
||||
this.reprompt = obj.reprompt;
|
||||
|
||||
switch (this.type) {
|
||||
@@ -244,10 +246,11 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
c.type = this.type;
|
||||
c.collectionIds = this.collectionIds;
|
||||
c.creationDate = this.creationDate != null ? this.creationDate.toISOString() : null;
|
||||
c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : null;
|
||||
c.deletedDate = this.deletedDate != null ? this.deletedDate.toISOString() : undefined;
|
||||
c.reprompt = this.reprompt;
|
||||
c.key = this.key?.encryptedString;
|
||||
c.permissions = this.permissions;
|
||||
c.archivedDate = this.archivedDate != null ? this.archivedDate.toISOString() : undefined;
|
||||
|
||||
this.buildDataModel(this, c, {
|
||||
name: null,
|
||||
@@ -296,11 +299,12 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
const notes = EncString.fromJSON(obj.notes);
|
||||
const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate);
|
||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||
const deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate);
|
||||
const attachments = obj.attachments?.map((a: any) => Attachment.fromJSON(a));
|
||||
const fields = obj.fields?.map((f: any) => Field.fromJSON(f));
|
||||
const passwordHistory = obj.passwordHistory?.map((ph: any) => Password.fromJSON(ph));
|
||||
const key = EncString.fromJSON(obj.key);
|
||||
const archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate);
|
||||
|
||||
Object.assign(domain, obj, {
|
||||
name,
|
||||
@@ -312,6 +316,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
fields,
|
||||
passwordHistory,
|
||||
key,
|
||||
archivedDate,
|
||||
});
|
||||
|
||||
switch (obj.type) {
|
||||
@@ -369,6 +374,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
revisionDate: this.revisionDate?.toISOString(),
|
||||
creationDate: this.creationDate?.toISOString(),
|
||||
deletedDate: this.deletedDate?.toISOString(),
|
||||
archivedDate: this.archivedDate?.toISOString(),
|
||||
reprompt: this.reprompt,
|
||||
// Initialize all cipher-type-specific properties as undefined
|
||||
login: undefined,
|
||||
@@ -434,7 +440,8 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
sdkCipher.passwordHistory?.map((ph) => Password.fromSdkPasswordHistory(ph)) ?? [];
|
||||
cipher.creationDate = new Date(sdkCipher.creationDate);
|
||||
cipher.revisionDate = new Date(sdkCipher.revisionDate);
|
||||
cipher.deletedDate = sdkCipher.deletedDate ? new Date(sdkCipher.deletedDate) : null;
|
||||
cipher.deletedDate = sdkCipher.deletedDate ? new Date(sdkCipher.deletedDate) : undefined;
|
||||
cipher.archivedDate = sdkCipher.archivedDate ? new Date(sdkCipher.archivedDate) : undefined;
|
||||
cipher.reprompt = sdkCipher.reprompt;
|
||||
|
||||
// Cipher type specific properties
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
|
||||
export class CipherBulkArchiveRequest {
|
||||
ids: CipherId[];
|
||||
|
||||
constructor(ids: CipherId[]) {
|
||||
this.ids = ids == null ? [] : ids;
|
||||
}
|
||||
}
|
||||
|
||||
export class CipherBulkUnarchiveRequest {
|
||||
ids: CipherId[];
|
||||
|
||||
constructor(ids: CipherId[]) {
|
||||
this.ids = ids == null ? [] : ids;
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export class CipherRequest {
|
||||
attachments: { [id: string]: string };
|
||||
attachments2: { [id: string]: AttachmentRequest };
|
||||
lastKnownRevisionDate: Date;
|
||||
archivedDate: Date | null;
|
||||
reprompt: CipherRepromptType;
|
||||
key: string;
|
||||
|
||||
@@ -47,6 +48,7 @@ export class CipherRequest {
|
||||
this.notes = cipher.notes ? cipher.notes.encryptedString : null;
|
||||
this.favorite = cipher.favorite;
|
||||
this.lastKnownRevisionDate = cipher.revisionDate;
|
||||
this.archivedDate = cipher.archivedDate;
|
||||
this.reprompt = cipher.reprompt;
|
||||
this.key = cipher.key?.encryptedString;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export class CipherResponse extends BaseResponse {
|
||||
collectionIds: string[];
|
||||
creationDate: string;
|
||||
deletedDate: string;
|
||||
archivedDate: string;
|
||||
reprompt: CipherRepromptType;
|
||||
key: string;
|
||||
|
||||
@@ -62,6 +63,7 @@ export class CipherResponse extends BaseResponse {
|
||||
this.collectionIds = this.getResponseProperty("CollectionIds");
|
||||
this.creationDate = this.getResponseProperty("CreationDate");
|
||||
this.deletedDate = this.getResponseProperty("DeletedDate");
|
||||
this.archivedDate = this.getResponseProperty("ArchivedDate");
|
||||
|
||||
const login = this.getResponseProperty("Login");
|
||||
if (login != null) {
|
||||
|
||||
@@ -163,6 +163,7 @@ describe("CipherView", () => {
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
revisionDate: "2022-01-02T12:00:00.000Z",
|
||||
deletedDate: undefined,
|
||||
archivedDate: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ export class CipherView implements View, InitializerMetadata {
|
||||
collectionIds: string[] = null;
|
||||
revisionDate: Date = null;
|
||||
creationDate: Date = null;
|
||||
deletedDate: Date = null;
|
||||
deletedDate: Date | null = null;
|
||||
archivedDate: Date | null = null;
|
||||
reprompt: CipherRepromptType = CipherRepromptType.None;
|
||||
// We need a copy of the encrypted key so we can pass it to
|
||||
// the SdkCipherView during encryption
|
||||
@@ -79,6 +80,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
this.revisionDate = c.revisionDate;
|
||||
this.creationDate = c.creationDate;
|
||||
this.deletedDate = c.deletedDate;
|
||||
this.archivedDate = c.archivedDate;
|
||||
// Old locally stored ciphers might have reprompt == null. If so set it to None.
|
||||
this.reprompt = c.reprompt ?? CipherRepromptType.None;
|
||||
this.key = c.key;
|
||||
@@ -143,6 +145,10 @@ export class CipherView implements View, InitializerMetadata {
|
||||
return this.deletedDate != null;
|
||||
}
|
||||
|
||||
get isArchived(): boolean {
|
||||
return this.archivedDate != null;
|
||||
}
|
||||
|
||||
get linkedFieldOptions() {
|
||||
return this.item?.linkedFieldOptions;
|
||||
}
|
||||
@@ -197,6 +203,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
const creationDate = obj.creationDate == null ? null : new Date(obj.creationDate);
|
||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
const deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||
const archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate);
|
||||
const attachments = obj.attachments?.map((a: any) => AttachmentView.fromJSON(a));
|
||||
const fields = obj.fields?.map((f: any) => FieldView.fromJSON(f));
|
||||
const passwordHistory = obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph));
|
||||
@@ -217,6 +224,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
creationDate: creationDate,
|
||||
revisionDate: revisionDate,
|
||||
deletedDate: deletedDate,
|
||||
archivedDate: archivedDate,
|
||||
attachments: attachments,
|
||||
fields: fields,
|
||||
passwordHistory: passwordHistory,
|
||||
@@ -277,6 +285,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate);
|
||||
cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate);
|
||||
cipherView.archivedDate = obj.archivedDate == null ? null : new Date(obj.archivedDate);
|
||||
cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None;
|
||||
cipherView.key = EncString.fromJSON(obj.key);
|
||||
|
||||
@@ -330,6 +339,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
revisionDate: (this.revisionDate ?? new Date()).toISOString(),
|
||||
creationDate: (this.creationDate ?? new Date()).toISOString(),
|
||||
deletedDate: this.deletedDate?.toISOString(),
|
||||
archivedDate: this.archivedDate?.toISOString(),
|
||||
reprompt: this.reprompt ?? CipherRepromptType.None,
|
||||
key: this.key?.toSdk(),
|
||||
// Cipher type specific properties are set in the switch statement below
|
||||
|
||||
@@ -68,6 +68,7 @@ const cipherData: CipherData = {
|
||||
deletedDate: null,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
key: "EncKey",
|
||||
archivedDate: null,
|
||||
reprompt: CipherRepromptType.None,
|
||||
login: {
|
||||
uris: [
|
||||
|
||||
@@ -286,6 +286,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
cipher.collectionIds = model.collectionIds;
|
||||
cipher.creationDate = model.creationDate;
|
||||
cipher.revisionDate = model.revisionDate;
|
||||
cipher.archivedDate = model.archivedDate;
|
||||
cipher.reprompt = model.reprompt;
|
||||
cipher.edit = model.edit;
|
||||
|
||||
@@ -634,6 +635,10 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
);
|
||||
defaultMatch ??= await firstValueFrom(this.domainSettingsService.defaultUriMatchStrategy$);
|
||||
|
||||
const archiveFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
return ciphers.filter((cipher) => {
|
||||
const type = CipherViewLikeUtils.getType(cipher);
|
||||
const login = CipherViewLikeUtils.getLogin(cipher);
|
||||
@@ -643,6 +648,10 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (archiveFeatureEnabled && CipherViewLikeUtils.isArchived(cipher)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(includeOtherTypes) && includeOtherTypes.includes(type) && !cipherIsLogin) {
|
||||
return true;
|
||||
}
|
||||
@@ -666,8 +675,16 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
userId: UserId,
|
||||
): Promise<CipherView[]> {
|
||||
const ciphers = await this.getAllDecrypted(userId);
|
||||
const archiveFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
return ciphers
|
||||
.filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type))
|
||||
.filter(
|
||||
(cipher) =>
|
||||
cipher.deletedDate == null &&
|
||||
(!archiveFeatureEnabled || !cipher.isArchived) &&
|
||||
type.includes(cipher.type),
|
||||
)
|
||||
.sort((a, b) => this.sortCiphersByLastUsedThenName(a, b));
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ const cipherData: CipherData = {
|
||||
name: "EncryptedString",
|
||||
notes: "EncryptedString",
|
||||
creationDate: "2022-01-01T12:00:00.000Z",
|
||||
deletedDate: null,
|
||||
deletedDate: undefined,
|
||||
archivedDate: undefined,
|
||||
permissions: new CipherPermissionsApi(),
|
||||
key: "EncKey",
|
||||
reprompt: CipherRepromptType.None,
|
||||
|
||||
@@ -110,6 +110,32 @@ describe("CipherViewLikeUtils", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isArchived", () => {
|
||||
it("returns true when the cipher is archived", () => {
|
||||
const cipherListView = {
|
||||
id: "1",
|
||||
archivedDate: "2024-02-02",
|
||||
type: "identity",
|
||||
} as unknown as CipherListView;
|
||||
const cipherView = createCipherView();
|
||||
cipherView.archivedDate = new Date();
|
||||
|
||||
expect(CipherViewLikeUtils.isArchived(cipherListView)).toBe(true);
|
||||
expect(CipherViewLikeUtils.isArchived(cipherView)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when the cipher is not archived", () => {
|
||||
const cipherListView = {
|
||||
id: "2",
|
||||
type: "identity",
|
||||
} as unknown as CipherListView;
|
||||
const cipherView = createCipherView();
|
||||
|
||||
expect(CipherViewLikeUtils.isArchived(cipherListView)).toBe(false);
|
||||
expect(CipherViewLikeUtils.isArchived(cipherView)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isDeleted", () => {
|
||||
it("returns true when the cipher is deleted", () => {
|
||||
const cipherListView = { deletedDate: "2024-02-02", type: "identity" } as CipherListView;
|
||||
|
||||
@@ -71,6 +71,15 @@ export class CipherViewLikeUtils {
|
||||
return cipher.type === CipherType.Card ? cipher.card : null;
|
||||
};
|
||||
|
||||
/** @returns `true` when the cipher has been archived, `false` otherwise. */
|
||||
static isArchived = (cipher: CipherViewLike): boolean => {
|
||||
if (this.isCipherListView(cipher)) {
|
||||
return !!cipher.archivedDate;
|
||||
}
|
||||
|
||||
return cipher.isArchived;
|
||||
};
|
||||
|
||||
/** @returns `true` when the cipher has been deleted, `false` otherwise. */
|
||||
static isDeleted = (cipher: CipherViewLike): boolean => {
|
||||
if (this.isCipherListView(cipher)) {
|
||||
|
||||
Reference in New Issue
Block a user