mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-28 12:45:45 +01:00
[PS-11868] Require key for enc string decryption (#10981)
* Specify enc string decryption key and service. * Fix issue with identifying `this` type within extended classes * Folder decryption example * Test enc string changes * Fix test name * test decrypt with key
This commit is contained in:
parent
cc9a72616a
commit
a6b9088940
139
libs/common/src/platform/models/domain/domain-base.spec.ts
Normal file
139
libs/common/src/platform/models/domain/domain-base.spec.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { makeEncString, makeSymmetricCryptoKey } from "../../../../spec";
|
||||||
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
|
import { Utils } from "../../misc/utils";
|
||||||
|
|
||||||
|
import Domain from "./domain-base";
|
||||||
|
import { EncString } from "./enc-string";
|
||||||
|
|
||||||
|
class TestDomain extends Domain {
|
||||||
|
plainText: string;
|
||||||
|
encToString: EncString;
|
||||||
|
encString2: EncString;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("DomainBase", () => {
|
||||||
|
let encryptService: MockProxy<EncryptService>;
|
||||||
|
const key = makeSymmetricCryptoKey(64);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
encryptService = mock<EncryptService>();
|
||||||
|
});
|
||||||
|
|
||||||
|
function setUpCryptography() {
|
||||||
|
encryptService.encrypt.mockImplementation((value) => {
|
||||||
|
let data: string;
|
||||||
|
if (typeof value === "string") {
|
||||||
|
data = value;
|
||||||
|
} else {
|
||||||
|
data = Utils.fromBufferToUtf8(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(makeEncString(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptService.decryptToUtf8.mockImplementation((value) => {
|
||||||
|
return Promise.resolve(value.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
encryptService.decryptToBytes.mockImplementation((value) => {
|
||||||
|
return Promise.resolve(value.dataBytes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("decryptWithKey", () => {
|
||||||
|
it("domain property types are decryptable", async () => {
|
||||||
|
const domain = new TestDomain();
|
||||||
|
|
||||||
|
await domain["decryptObjWithKey"](
|
||||||
|
// @ts-expect-error -- clear is not of type EncString
|
||||||
|
["plainText"],
|
||||||
|
makeSymmetricCryptoKey(64),
|
||||||
|
mock<EncryptService>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await domain["decryptObjWithKey"](
|
||||||
|
// @ts-expect-error -- Clear is not of type EncString
|
||||||
|
["encToString", "encString2", "plainText"],
|
||||||
|
makeSymmetricCryptoKey(64),
|
||||||
|
mock<EncryptService>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const decrypted = await domain["decryptObjWithKey"](
|
||||||
|
["encToString"],
|
||||||
|
makeSymmetricCryptoKey(64),
|
||||||
|
mock<EncryptService>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-expect-error -- encString2 was not decrypted
|
||||||
|
decrypted as { encToString: string; encString2: string; plainText: string };
|
||||||
|
|
||||||
|
// encString2 was not decrypted, so it's still an EncString
|
||||||
|
decrypted as { encToString: string; encString2: EncString; plainText: string };
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decrypts the encrypted properties", async () => {
|
||||||
|
setUpCryptography();
|
||||||
|
|
||||||
|
const domain = new TestDomain();
|
||||||
|
|
||||||
|
domain.encToString = await encryptService.encrypt("string", key);
|
||||||
|
|
||||||
|
const decrypted = await domain["decryptObjWithKey"](["encToString"], key, encryptService);
|
||||||
|
|
||||||
|
expect(decrypted).toEqual({
|
||||||
|
encToString: "string",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decrypts multiple encrypted properties", async () => {
|
||||||
|
setUpCryptography();
|
||||||
|
|
||||||
|
const domain = new TestDomain();
|
||||||
|
|
||||||
|
domain.encToString = await encryptService.encrypt("string", key);
|
||||||
|
domain.encString2 = await encryptService.encrypt("string2", key);
|
||||||
|
|
||||||
|
const decrypted = await domain["decryptObjWithKey"](
|
||||||
|
["encToString", "encString2"],
|
||||||
|
key,
|
||||||
|
encryptService,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decrypted).toEqual({
|
||||||
|
encToString: "string",
|
||||||
|
encString2: "string2",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not decrypt properties that are not encrypted", async () => {
|
||||||
|
const domain = new TestDomain();
|
||||||
|
domain.plainText = "clear";
|
||||||
|
|
||||||
|
const decrypted = await domain["decryptObjWithKey"]([], key, encryptService);
|
||||||
|
|
||||||
|
expect(decrypted).toEqual({
|
||||||
|
plainText: "clear",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not decrypt properties that were not requested to be decrypted", async () => {
|
||||||
|
setUpCryptography();
|
||||||
|
|
||||||
|
const domain = new TestDomain();
|
||||||
|
|
||||||
|
domain.plainText = "clear";
|
||||||
|
domain.encToString = makeEncString("string");
|
||||||
|
domain.encString2 = makeEncString("string2");
|
||||||
|
|
||||||
|
const decrypted = await domain["decryptObjWithKey"]([], key, encryptService);
|
||||||
|
|
||||||
|
expect(decrypted).toEqual({
|
||||||
|
plainText: "clear",
|
||||||
|
encToString: makeEncString("string"),
|
||||||
|
encString2: makeEncString("string2"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,8 +1,18 @@
|
|||||||
|
import { ConditionalExcept, ConditionalKeys, Constructor } from "type-fest";
|
||||||
|
|
||||||
import { View } from "../../../models/view/view";
|
import { View } from "../../../models/view/view";
|
||||||
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
|
|
||||||
import { EncString } from "./enc-string";
|
import { EncString } from "./enc-string";
|
||||||
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
type EncStringKeys<T> = ConditionalKeys<ConditionalExcept<T, Function>, EncString>;
|
||||||
|
export type DecryptedObject<
|
||||||
|
TEncryptedObject,
|
||||||
|
TDecryptedKeys extends EncStringKeys<TEncryptedObject>,
|
||||||
|
> = Record<TDecryptedKeys, string> & Omit<TEncryptedObject, TDecryptedKeys>;
|
||||||
|
|
||||||
// https://contributing.bitwarden.com/architecture/clients/data-model#domain
|
// https://contributing.bitwarden.com/architecture/clients/data-model#domain
|
||||||
export default class Domain {
|
export default class Domain {
|
||||||
protected buildDomainModel<D extends Domain>(
|
protected buildDomainModel<D extends Domain>(
|
||||||
@ -80,4 +90,60 @@ export default class Domain {
|
|||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts the requested properties of the domain object with the provided key and encrypt service.
|
||||||
|
*
|
||||||
|
* If a property is null, the result will be null.
|
||||||
|
* @see {@link EncString.decryptWithKey} for more details on decryption behavior.
|
||||||
|
*
|
||||||
|
* @param encryptedProperties The properties to decrypt. Type restricted to EncString properties of the domain object.
|
||||||
|
* @param key The key to use for decryption.
|
||||||
|
* @param encryptService The encryption service to use for decryption.
|
||||||
|
* @param _ The constructor of the domain object. Used for type inference if the domain object is not automatically inferred.
|
||||||
|
* @returns An object with the requested properties decrypted and the rest of the domain object untouched.
|
||||||
|
*/
|
||||||
|
protected async decryptObjWithKey<
|
||||||
|
TThis extends Domain,
|
||||||
|
const TEncryptedKeys extends EncStringKeys<TThis>,
|
||||||
|
>(
|
||||||
|
this: TThis,
|
||||||
|
encryptedProperties: TEncryptedKeys[],
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
encryptService: EncryptService,
|
||||||
|
_: Constructor<TThis> = this.constructor as Constructor<TThis>,
|
||||||
|
): Promise<DecryptedObject<TThis, TEncryptedKeys>> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const prop of encryptedProperties) {
|
||||||
|
const value = (this as any)[prop] as EncString;
|
||||||
|
promises.push(this.decryptProperty(prop, value, key, encryptService));
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptedObjects = await Promise.all(promises);
|
||||||
|
const decryptedObject = decryptedObjects.reduce(
|
||||||
|
(acc, obj) => {
|
||||||
|
return { ...acc, ...obj };
|
||||||
|
},
|
||||||
|
{ ...this },
|
||||||
|
);
|
||||||
|
return decryptedObject as DecryptedObject<TThis, TEncryptedKeys>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptProperty<const TEncryptedKeys extends EncStringKeys<this>>(
|
||||||
|
propertyKey: TEncryptedKeys,
|
||||||
|
value: EncString,
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
encryptService: EncryptService,
|
||||||
|
) {
|
||||||
|
let decrypted: string = null;
|
||||||
|
if (value) {
|
||||||
|
decrypted = await value.decryptWithKey(key, encryptService);
|
||||||
|
} else {
|
||||||
|
decrypted = null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
[propertyKey]: decrypted,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
import { makeStaticByteArray } from "../../../../spec";
|
import { makeEncString, makeStaticByteArray } from "../../../../spec";
|
||||||
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { UserKey, OrgKey } from "../../../types/key";
|
import { UserKey, OrgKey } from "../../../types/key";
|
||||||
import { CryptoService } from "../../abstractions/crypto.service";
|
import { CryptoService } from "../../abstractions/crypto.service";
|
||||||
import { EncryptionType } from "../../enums";
|
import { EncryptionType } from "../../enums";
|
||||||
|
import { Utils } from "../../misc/utils";
|
||||||
import { ContainerService } from "../../services/container.service";
|
import { ContainerService } from "../../services/container.service";
|
||||||
|
|
||||||
import { EncString } from "./enc-string";
|
import { EncString } from "./enc-string";
|
||||||
@ -113,6 +114,77 @@ describe("EncString", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("decryptWithKey", () => {
|
||||||
|
const encString = new EncString(EncryptionType.Rsa2048_OaepSha256_B64, "data");
|
||||||
|
|
||||||
|
const cryptoService = mock<CryptoService>();
|
||||||
|
const encryptService = mock<EncryptService>();
|
||||||
|
encryptService.decryptToUtf8
|
||||||
|
.calledWith(encString, expect.anything())
|
||||||
|
.mockResolvedValue("decrypted");
|
||||||
|
|
||||||
|
function setupEncryption() {
|
||||||
|
encryptService.encrypt.mockImplementation(async (data, key) => {
|
||||||
|
if (typeof data === "string") {
|
||||||
|
return makeEncString(data);
|
||||||
|
} else {
|
||||||
|
return makeEncString(Utils.fromBufferToUtf8(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
encryptService.decryptToUtf8.mockImplementation(async (encString, key) => {
|
||||||
|
return encString.data;
|
||||||
|
});
|
||||||
|
encryptService.decryptToBytes.mockImplementation(async (encString, key) => {
|
||||||
|
return encString.dataBytes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
(window as any).bitwardenContainerService = new ContainerService(
|
||||||
|
cryptoService,
|
||||||
|
encryptService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decrypts using the provided key and encryptService", async () => {
|
||||||
|
setupEncryption();
|
||||||
|
|
||||||
|
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||||
|
await encString.decryptWithKey(key, encryptService);
|
||||||
|
|
||||||
|
expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encString, key);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails to decrypt when key is null", async () => {
|
||||||
|
const decrypted = await encString.decryptWithKey(null, encryptService);
|
||||||
|
|
||||||
|
expect(decrypted).toBe("[error: cannot decrypt]");
|
||||||
|
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails to decrypt when encryptService is null", async () => {
|
||||||
|
const decrypted = await encString.decryptWithKey(
|
||||||
|
new SymmetricCryptoKey(makeStaticByteArray(32)),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decrypted).toBe("[error: cannot decrypt]");
|
||||||
|
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fails to decrypt when encryptService throws", async () => {
|
||||||
|
encryptService.decryptToUtf8.mockRejectedValue("error");
|
||||||
|
|
||||||
|
const decrypted = await encString.decryptWithKey(
|
||||||
|
new SymmetricCryptoKey(makeStaticByteArray(32)),
|
||||||
|
encryptService,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(decrypted).toBe("[error: cannot decrypt]");
|
||||||
|
expect(encString.decryptedValue).toBe("[error: cannot decrypt]");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("AesCbc256_B64", () => {
|
describe("AesCbc256_B64", () => {
|
||||||
it("constructor", () => {
|
it("constructor", () => {
|
||||||
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
|
const encString = new EncString(EncryptionType.AesCbc256_B64, "data", "iv");
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { Jsonify, Opaque } from "type-fest";
|
import { Jsonify, Opaque } from "type-fest";
|
||||||
|
|
||||||
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../enums";
|
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../enums";
|
||||||
import { Encrypted } from "../../interfaces/encrypted";
|
import { Encrypted } from "../../interfaces/encrypted";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
|
|
||||||
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
|
||||||
|
|
||||||
|
export const DECRYPT_ERROR = "[error: cannot decrypt]";
|
||||||
|
|
||||||
export class EncString implements Encrypted {
|
export class EncString implements Encrypted {
|
||||||
encryptedString?: EncryptedString;
|
encryptedString?: EncryptedString;
|
||||||
encryptionType?: EncryptionType;
|
encryptionType?: EncryptionType;
|
||||||
@ -167,11 +170,24 @@ export class EncString implements Encrypted {
|
|||||||
const encryptService = Utils.getContainerService().getEncryptService();
|
const encryptService = Utils.getContainerService().getEncryptService();
|
||||||
this.decryptedValue = await encryptService.decryptToUtf8(this, key);
|
this.decryptedValue = await encryptService.decryptToUtf8(this, key);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.decryptedValue = "[error: cannot decrypt]";
|
this.decryptedValue = DECRYPT_ERROR;
|
||||||
}
|
}
|
||||||
return this.decryptedValue;
|
return this.decryptedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decryptWithKey(key: SymmetricCryptoKey, encryptService: EncryptService) {
|
||||||
|
try {
|
||||||
|
if (key == null) {
|
||||||
|
throw new Error("No key to decrypt EncString");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.decryptedValue = await encryptService.decryptToUtf8(this, key);
|
||||||
|
} catch (e) {
|
||||||
|
this.decryptedValue = DECRYPT_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.decryptedValue;
|
||||||
|
}
|
||||||
private async getKeyForDecryption(orgId: string) {
|
private async getKeyForDecryption(orgId: string) {
|
||||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||||
return orgId != null
|
return orgId != null
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { mockEnc, mockFromJson } from "../../../../spec";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { makeEncString, makeSymmetricCryptoKey, mockEnc, mockFromJson } from "../../../../spec";
|
||||||
|
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||||
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
|
import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string";
|
||||||
import { FolderData } from "../../models/data/folder.data";
|
import { FolderData } from "../../models/data/folder.data";
|
||||||
import { Folder } from "../../models/domain/folder";
|
import { Folder } from "../../models/domain/folder";
|
||||||
@ -60,4 +63,42 @@ describe("Folder", () => {
|
|||||||
expect(actual).toMatchObject(expected);
|
expect(actual).toMatchObject(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("decryptWithKey", () => {
|
||||||
|
let encryptService: MockProxy<EncryptService>;
|
||||||
|
const key = makeSymmetricCryptoKey(64);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
encryptService = mock<EncryptService>();
|
||||||
|
encryptService.decryptToUtf8.mockImplementation((value) => {
|
||||||
|
return Promise.resolve(value.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("decrypts the name", async () => {
|
||||||
|
const folder = new Folder();
|
||||||
|
folder.name = makeEncString("encName");
|
||||||
|
|
||||||
|
const view = await folder.decryptWithKey(key, encryptService);
|
||||||
|
|
||||||
|
expect(view).toEqual({
|
||||||
|
name: "encName",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("assigns the folder id and revision date", async () => {
|
||||||
|
const folder = new Folder();
|
||||||
|
folder.id = "id";
|
||||||
|
folder.revisionDate = new Date("2022-01-31T12:00:00.000Z");
|
||||||
|
|
||||||
|
const view = await folder.decryptWithKey(key, encryptService);
|
||||||
|
|
||||||
|
expect(view).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: "id",
|
||||||
|
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
|
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||||
import Domain from "../../../platform/models/domain/domain-base";
|
import Domain from "../../../platform/models/domain/domain-base";
|
||||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||||
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { FolderData } from "../data/folder.data";
|
import { FolderData } from "../data/folder.data";
|
||||||
import { FolderView } from "../view/folder.view";
|
import { FolderView } from "../view/folder.view";
|
||||||
|
|
||||||
|
export class Test extends Domain {
|
||||||
|
id: string;
|
||||||
|
name: EncString;
|
||||||
|
revisionDate: Date;
|
||||||
|
}
|
||||||
|
|
||||||
export class Folder extends Domain {
|
export class Folder extends Domain {
|
||||||
id: string;
|
id: string;
|
||||||
name: EncString;
|
name: EncString;
|
||||||
@ -39,6 +47,17 @@ export class Folder extends Domain {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decryptWithKey(
|
||||||
|
key: SymmetricCryptoKey,
|
||||||
|
encryptService: EncryptService,
|
||||||
|
): Promise<FolderView> {
|
||||||
|
const decrypted = await this.decryptObjWithKey(["name"], key, encryptService, Folder);
|
||||||
|
|
||||||
|
const view = new FolderView(decrypted);
|
||||||
|
view.name = decrypted.name;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
static fromJSON(obj: Jsonify<Folder>) {
|
static fromJSON(obj: Jsonify<Folder>) {
|
||||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||||
return Object.assign(new Folder(), obj, { name: EncString.fromJSON(obj.name), revisionDate });
|
return Object.assign(new Folder(), obj, { name: EncString.fromJSON(obj.name), revisionDate });
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { View } from "../../../models/view/view";
|
import { View } from "../../../models/view/view";
|
||||||
|
import { DecryptedObject } from "../../../platform/models/domain/domain-base";
|
||||||
import { Folder } from "../domain/folder";
|
import { Folder } from "../domain/folder";
|
||||||
import { ITreeNodeObject } from "../domain/tree-node";
|
import { ITreeNodeObject } from "../domain/tree-node";
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ export class FolderView implements View, ITreeNodeObject {
|
|||||||
name: string = null;
|
name: string = null;
|
||||||
revisionDate: Date = null;
|
revisionDate: Date = null;
|
||||||
|
|
||||||
constructor(f?: Folder) {
|
constructor(f?: Folder | DecryptedObject<Folder, "name">) {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user