mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[PM-328] Move Send to Tools (#5104)
* Move send in libs/common * Move send in libs/angular * Move send in browser * Move send in cli * Move send in desktop * Move send in web
This commit is contained in:
committed by
GitHub
parent
e645688f8a
commit
e238ea20a9
4
libs/common/src/tools/send/enums/send-type.ts
Normal file
4
libs/common/src/tools/send/enums/send-type.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum SendType {
|
||||
Text = 0,
|
||||
File = 1,
|
||||
}
|
||||
19
libs/common/src/tools/send/models/api/send-file.api.ts
Normal file
19
libs/common/src/tools/send/models/api/send-file.api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
|
||||
export class SendFileApi extends BaseResponse {
|
||||
id: string;
|
||||
fileName: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.fileName = this.getResponseProperty("FileName");
|
||||
this.size = this.getResponseProperty("Size");
|
||||
this.sizeName = this.getResponseProperty("SizeName");
|
||||
}
|
||||
}
|
||||
15
libs/common/src/tools/send/models/api/send-text.api.ts
Normal file
15
libs/common/src/tools/send/models/api/send-text.api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
|
||||
export class SendTextApi extends BaseResponse {
|
||||
text: string;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.text = this.getResponseProperty("Text");
|
||||
this.hidden = this.getResponseProperty("Hidden") || false;
|
||||
}
|
||||
}
|
||||
19
libs/common/src/tools/send/models/data/send-file.data.ts
Normal file
19
libs/common/src/tools/send/models/data/send-file.data.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { SendFileApi } from "../api/send-file.api";
|
||||
|
||||
export class SendFileData {
|
||||
id: string;
|
||||
fileName: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
|
||||
constructor(data?: SendFileApi) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = data.id;
|
||||
this.fileName = data.fileName;
|
||||
this.size = data.size;
|
||||
this.sizeName = data.sizeName;
|
||||
}
|
||||
}
|
||||
15
libs/common/src/tools/send/models/data/send-text.data.ts
Normal file
15
libs/common/src/tools/send/models/data/send-text.data.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SendTextApi } from "../api/send-text.api";
|
||||
|
||||
export class SendTextData {
|
||||
text: string;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(data?: SendTextApi) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = data.text;
|
||||
this.hidden = data.hidden;
|
||||
}
|
||||
}
|
||||
56
libs/common/src/tools/send/models/data/send.data.ts
Normal file
56
libs/common/src/tools/send/models/data/send.data.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendResponse } from "../response/send.response";
|
||||
|
||||
import { SendFileData } from "./send-file.data";
|
||||
import { SendTextData } from "./send-text.data";
|
||||
|
||||
export class SendData {
|
||||
id: string;
|
||||
accessId: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
notes: string;
|
||||
file: SendFileData;
|
||||
text: SendTextData;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: string;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
|
||||
constructor(response?: SendResponse) {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = response.id;
|
||||
this.accessId = response.accessId;
|
||||
this.type = response.type;
|
||||
this.name = response.name;
|
||||
this.notes = response.notes;
|
||||
this.key = response.key;
|
||||
this.maxAccessCount = response.maxAccessCount;
|
||||
this.accessCount = response.accessCount;
|
||||
this.revisionDate = response.revisionDate;
|
||||
this.expirationDate = response.expirationDate;
|
||||
this.deletionDate = response.deletionDate;
|
||||
this.password = response.password;
|
||||
this.disabled = response.disable;
|
||||
this.hideEmail = response.hideEmail;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendTextData(response.text);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFileData(response.file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
85
libs/common/src/tools/send/models/domain/send-access.spec.ts
Normal file
85
libs/common/src/tools/send/models/domain/send-access.spec.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { mockEnc } from "../../../../../spec/utils";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendAccessResponse } from "../response/send-access.response";
|
||||
|
||||
import { SendAccess } from "./send-access";
|
||||
import { SendText } from "./send-text";
|
||||
|
||||
describe("SendAccess", () => {
|
||||
let request: SendAccessResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
request = {
|
||||
id: "id",
|
||||
type: SendType.Text,
|
||||
name: "encName",
|
||||
file: null,
|
||||
text: {
|
||||
text: "encText",
|
||||
hidden: true,
|
||||
},
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creatorIdentifier: "creatorIdentifier",
|
||||
} as SendAccessResponse;
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
const request = new SendAccessResponse({});
|
||||
const sendAccess = new SendAccess(request);
|
||||
|
||||
expect(sendAccess).toEqual({
|
||||
id: null,
|
||||
type: undefined,
|
||||
name: null,
|
||||
creatorIdentifier: null,
|
||||
expirationDate: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
const sendAccess = new SendAccess(request);
|
||||
|
||||
expect(sendAccess).toEqual({
|
||||
id: "id",
|
||||
type: 0,
|
||||
name: { encryptedString: "encName", encryptionType: 0 },
|
||||
text: {
|
||||
hidden: true,
|
||||
text: { encryptedString: "encText", encryptionType: 0 },
|
||||
},
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creatorIdentifier: "creatorIdentifier",
|
||||
});
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const sendAccess = new SendAccess();
|
||||
sendAccess.id = "id";
|
||||
sendAccess.type = SendType.Text;
|
||||
sendAccess.name = mockEnc("name");
|
||||
|
||||
const text = Substitute.for<SendText>();
|
||||
text.decrypt(Arg.any()).resolves({} as any);
|
||||
sendAccess.text = text;
|
||||
|
||||
sendAccess.expirationDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
sendAccess.creatorIdentifier = "creatorIdentifier";
|
||||
|
||||
const view = await sendAccess.decrypt(null);
|
||||
|
||||
text.received(1).decrypt(Arg.any());
|
||||
|
||||
expect(view).toEqual({
|
||||
id: "id",
|
||||
type: 0,
|
||||
name: "name",
|
||||
text: {},
|
||||
file: expect.anything(),
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
creatorIdentifier: "creatorIdentifier",
|
||||
});
|
||||
});
|
||||
});
|
||||
77
libs/common/src/tools/send/models/domain/send-access.ts
Normal file
77
libs/common/src/tools/send/models/domain/send-access.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import Domain from "../../../../models/domain/domain-base";
|
||||
import { EncString } from "../../../../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../../models/domain/symmetric-crypto-key";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendAccessResponse } from "../response/send-access.response";
|
||||
import { SendAccessView } from "../view/send-access.view";
|
||||
|
||||
import { SendFile } from "./send-file";
|
||||
import { SendText } from "./send-text";
|
||||
|
||||
export class SendAccess extends Domain {
|
||||
id: string;
|
||||
type: SendType;
|
||||
name: EncString;
|
||||
file: SendFile;
|
||||
text: SendText;
|
||||
expirationDate: Date;
|
||||
creatorIdentifier: string;
|
||||
|
||||
constructor(obj?: SendAccessResponse) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildDomainModel(
|
||||
this,
|
||||
obj,
|
||||
{
|
||||
id: null,
|
||||
name: null,
|
||||
expirationDate: null,
|
||||
creatorIdentifier: null,
|
||||
},
|
||||
["id", "expirationDate", "creatorIdentifier"]
|
||||
);
|
||||
|
||||
this.type = obj.type;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendText(obj.text);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFile(obj.file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(key: SymmetricCryptoKey): Promise<SendAccessView> {
|
||||
const model = new SendAccessView(this);
|
||||
|
||||
await this.decryptObj(
|
||||
model,
|
||||
{
|
||||
name: null,
|
||||
},
|
||||
null,
|
||||
key
|
||||
);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
model.file = await this.file.decrypt(key);
|
||||
break;
|
||||
case SendType.Text:
|
||||
model.text = await this.text.decrypt(key);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
57
libs/common/src/tools/send/models/domain/send-file.spec.ts
Normal file
57
libs/common/src/tools/send/models/domain/send-file.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { mockEnc } from "../../../../../spec/utils";
|
||||
import { SendFileData } from "../data/send-file.data";
|
||||
|
||||
import { SendFile } from "./send-file";
|
||||
|
||||
describe("SendFile", () => {
|
||||
let data: SendFileData;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
id: "id",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: "encFileName",
|
||||
};
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
const data = new SendFileData();
|
||||
const sendFile = new SendFile(data);
|
||||
|
||||
expect(sendFile).toEqual({
|
||||
fileName: null,
|
||||
id: null,
|
||||
size: undefined,
|
||||
sizeName: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
const sendFile = new SendFile(data);
|
||||
|
||||
expect(sendFile).toEqual({
|
||||
id: "id",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
fileName: { encryptedString: "encFileName", encryptionType: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const sendFile = new SendFile();
|
||||
sendFile.id = "id";
|
||||
sendFile.size = "1100";
|
||||
sendFile.sizeName = "1.1 KB";
|
||||
sendFile.fileName = mockEnc("fileName");
|
||||
|
||||
const view = await sendFile.decrypt(null);
|
||||
|
||||
expect(view).toEqual({
|
||||
fileName: "fileName",
|
||||
id: "id",
|
||||
size: "1100",
|
||||
sizeName: "1.1 KB",
|
||||
});
|
||||
});
|
||||
});
|
||||
55
libs/common/src/tools/send/models/domain/send-file.ts
Normal file
55
libs/common/src/tools/send/models/domain/send-file.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import Domain from "../../../../models/domain/domain-base";
|
||||
import { EncString } from "../../../../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../../models/domain/symmetric-crypto-key";
|
||||
import { SendFileData } from "../data/send-file.data";
|
||||
import { SendFileView } from "../view/send-file.view";
|
||||
|
||||
export class SendFile extends Domain {
|
||||
id: string;
|
||||
size: string;
|
||||
sizeName: string;
|
||||
fileName: EncString;
|
||||
|
||||
constructor(obj?: SendFileData) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.size = obj.size;
|
||||
this.buildDomainModel(
|
||||
this,
|
||||
obj,
|
||||
{
|
||||
id: null,
|
||||
sizeName: null,
|
||||
fileName: null,
|
||||
},
|
||||
["id", "sizeName"]
|
||||
);
|
||||
}
|
||||
|
||||
async decrypt(key: SymmetricCryptoKey): Promise<SendFileView> {
|
||||
const view = await this.decryptObj(
|
||||
new SendFileView(this),
|
||||
{
|
||||
fileName: null,
|
||||
},
|
||||
null,
|
||||
key
|
||||
);
|
||||
return view;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<SendFile>) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendFile(), obj, {
|
||||
fileName: EncString.fromJSON(obj.fileName),
|
||||
});
|
||||
}
|
||||
}
|
||||
47
libs/common/src/tools/send/models/domain/send-text.spec.ts
Normal file
47
libs/common/src/tools/send/models/domain/send-text.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { mockEnc } from "../../../../../spec/utils";
|
||||
import { SendTextData } from "../data/send-text.data";
|
||||
|
||||
import { SendText } from "./send-text";
|
||||
|
||||
describe("SendText", () => {
|
||||
let data: SendTextData;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
text: "encText",
|
||||
hidden: false,
|
||||
};
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
const data = new SendTextData();
|
||||
const secureNote = new SendText(data);
|
||||
|
||||
expect(secureNote).toEqual({
|
||||
hidden: undefined,
|
||||
text: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
const secureNote = new SendText(data);
|
||||
|
||||
expect(secureNote).toEqual({
|
||||
hidden: false,
|
||||
text: { encryptedString: "encText", encryptionType: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const secureNote = new SendText();
|
||||
secureNote.text = mockEnc("text");
|
||||
secureNote.hidden = true;
|
||||
|
||||
const view = await secureNote.decrypt(null);
|
||||
|
||||
expect(view).toEqual({
|
||||
text: "text",
|
||||
hidden: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
50
libs/common/src/tools/send/models/domain/send-text.ts
Normal file
50
libs/common/src/tools/send/models/domain/send-text.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import Domain from "../../../../models/domain/domain-base";
|
||||
import { EncString } from "../../../../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../../models/domain/symmetric-crypto-key";
|
||||
import { SendTextData } from "../data/send-text.data";
|
||||
import { SendTextView } from "../view/send-text.view";
|
||||
|
||||
export class SendText extends Domain {
|
||||
text: EncString;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(obj?: SendTextData) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = obj.hidden;
|
||||
this.buildDomainModel(
|
||||
this,
|
||||
obj,
|
||||
{
|
||||
text: null,
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
decrypt(key: SymmetricCryptoKey): Promise<SendTextView> {
|
||||
return this.decryptObj(
|
||||
new SendTextView(this),
|
||||
{
|
||||
text: null,
|
||||
},
|
||||
null,
|
||||
key
|
||||
);
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<SendText>) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendText(), obj, {
|
||||
text: EncString.fromJSON(obj.text),
|
||||
});
|
||||
}
|
||||
}
|
||||
144
libs/common/src/tools/send/models/domain/send.spec.ts
Normal file
144
libs/common/src/tools/send/models/domain/send.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Substitute, Arg, SubstituteOf } from "@fluffy-spoon/substitute";
|
||||
|
||||
import { makeStaticByteArray, mockEnc } from "../../../../../spec/utils";
|
||||
import { CryptoService } from "../../../../abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../../abstractions/encrypt.service";
|
||||
import { EncString } from "../../../../models/domain/enc-string";
|
||||
import { ContainerService } from "../../../../services/container.service";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendData } from "../data/send.data";
|
||||
|
||||
import { Send } from "./send";
|
||||
import { SendText } from "./send-text";
|
||||
|
||||
describe("Send", () => {
|
||||
let data: SendData;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
id: "id",
|
||||
accessId: "accessId",
|
||||
type: SendType.Text,
|
||||
name: "encName",
|
||||
notes: "encNotes",
|
||||
text: {
|
||||
text: "encText",
|
||||
hidden: true,
|
||||
},
|
||||
file: null,
|
||||
key: "encKey",
|
||||
maxAccessCount: null,
|
||||
accessCount: 10,
|
||||
revisionDate: "2022-01-31T12:00:00.000Z",
|
||||
expirationDate: "2022-01-31T12:00:00.000Z",
|
||||
deletionDate: "2022-01-31T12:00:00.000Z",
|
||||
password: "password",
|
||||
disabled: false,
|
||||
hideEmail: true,
|
||||
};
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
const data = new SendData();
|
||||
const send = new Send(data);
|
||||
|
||||
expect(send).toEqual({
|
||||
id: null,
|
||||
accessId: null,
|
||||
type: undefined,
|
||||
name: null,
|
||||
notes: null,
|
||||
text: undefined,
|
||||
file: undefined,
|
||||
key: null,
|
||||
maxAccessCount: undefined,
|
||||
accessCount: undefined,
|
||||
revisionDate: null,
|
||||
expirationDate: null,
|
||||
deletionDate: null,
|
||||
password: undefined,
|
||||
disabled: undefined,
|
||||
hideEmail: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
const send = new Send(data);
|
||||
|
||||
expect(send).toEqual({
|
||||
id: "id",
|
||||
accessId: "accessId",
|
||||
type: SendType.Text,
|
||||
name: { encryptedString: "encName", encryptionType: 0 },
|
||||
notes: { encryptedString: "encNotes", encryptionType: 0 },
|
||||
text: {
|
||||
text: { encryptedString: "encText", encryptionType: 0 },
|
||||
hidden: true,
|
||||
},
|
||||
key: { encryptedString: "encKey", encryptionType: 0 },
|
||||
maxAccessCount: null,
|
||||
accessCount: 10,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
deletionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
password: "password",
|
||||
disabled: false,
|
||||
hideEmail: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const text = Substitute.for<SendText>();
|
||||
text.decrypt(Arg.any()).resolves("textView" as any);
|
||||
|
||||
const send = new Send();
|
||||
send.id = "id";
|
||||
send.accessId = "accessId";
|
||||
send.type = SendType.Text;
|
||||
send.name = mockEnc("name");
|
||||
send.notes = mockEnc("notes");
|
||||
send.text = text;
|
||||
send.key = mockEnc("key");
|
||||
send.accessCount = 10;
|
||||
send.revisionDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
send.expirationDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
send.deletionDate = new Date("2022-01-31T12:00:00.000Z");
|
||||
send.password = "password";
|
||||
send.disabled = false;
|
||||
send.hideEmail = true;
|
||||
|
||||
const cryptoService = Substitute.for<CryptoService>();
|
||||
cryptoService.decryptToBytes(send.key, null).resolves(makeStaticByteArray(32));
|
||||
cryptoService.makeSendKey(Arg.any()).resolves("cryptoKey" as any);
|
||||
|
||||
const encryptService = Substitute.for<EncryptService>();
|
||||
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
const view = await send.decrypt();
|
||||
|
||||
text.received(1).decrypt("cryptoKey" as any);
|
||||
(send.name as SubstituteOf<EncString>).received(1).decrypt(null, "cryptoKey" as any);
|
||||
|
||||
expect(view).toMatchObject({
|
||||
id: "id",
|
||||
accessId: "accessId",
|
||||
name: "name",
|
||||
notes: "notes",
|
||||
type: 0,
|
||||
key: expect.anything(),
|
||||
cryptoKey: "cryptoKey",
|
||||
file: expect.anything(),
|
||||
text: "textView",
|
||||
maxAccessCount: undefined,
|
||||
accessCount: 10,
|
||||
revisionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
expirationDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
deletionDate: new Date("2022-01-31T12:00:00.000Z"),
|
||||
password: "password",
|
||||
disabled: false,
|
||||
hideEmail: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
128
libs/common/src/tools/send/models/domain/send.ts
Normal file
128
libs/common/src/tools/send/models/domain/send.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { Utils } from "../../../../misc/utils";
|
||||
import Domain from "../../../../models/domain/domain-base";
|
||||
import { EncString } from "../../../../models/domain/enc-string";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendData } from "../data/send.data";
|
||||
import { SendView } from "../view/send.view";
|
||||
|
||||
import { SendFile } from "./send-file";
|
||||
import { SendText } from "./send-text";
|
||||
|
||||
export class Send extends Domain {
|
||||
id: string;
|
||||
accessId: string;
|
||||
type: SendType;
|
||||
name: EncString;
|
||||
notes: EncString;
|
||||
file: SendFile;
|
||||
text: SendText;
|
||||
key: EncString;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: Date;
|
||||
expirationDate: Date;
|
||||
deletionDate: Date;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
|
||||
constructor(obj?: SendData) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildDomainModel(
|
||||
this,
|
||||
obj,
|
||||
{
|
||||
id: null,
|
||||
accessId: null,
|
||||
name: null,
|
||||
notes: null,
|
||||
key: null,
|
||||
},
|
||||
["id", "accessId"]
|
||||
);
|
||||
|
||||
this.type = obj.type;
|
||||
this.maxAccessCount = obj.maxAccessCount;
|
||||
this.accessCount = obj.accessCount;
|
||||
this.password = obj.password;
|
||||
this.disabled = obj.disabled;
|
||||
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null;
|
||||
this.deletionDate = obj.deletionDate != null ? new Date(obj.deletionDate) : null;
|
||||
this.expirationDate = obj.expirationDate != null ? new Date(obj.expirationDate) : null;
|
||||
this.hideEmail = obj.hideEmail;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendText(obj.text);
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFile(obj.file);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async decrypt(): Promise<SendView> {
|
||||
const model = new SendView(this);
|
||||
|
||||
const cryptoService = Utils.getContainerService().getCryptoService();
|
||||
|
||||
try {
|
||||
model.key = await cryptoService.decryptToBytes(this.key, null);
|
||||
model.cryptoKey = await cryptoService.makeSendKey(model.key);
|
||||
} catch (e) {
|
||||
// TODO: error?
|
||||
}
|
||||
|
||||
await this.decryptObj(
|
||||
model,
|
||||
{
|
||||
name: null,
|
||||
notes: null,
|
||||
},
|
||||
null,
|
||||
model.cryptoKey
|
||||
);
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
model.file = await this.file.decrypt(model.cryptoKey);
|
||||
break;
|
||||
case SendType.Text:
|
||||
model.text = await this.text.decrypt(model.cryptoKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Jsonify<Send>) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate);
|
||||
const expirationDate = obj.expirationDate == null ? null : new Date(obj.expirationDate);
|
||||
const deletionDate = obj.deletionDate == null ? null : new Date(obj.deletionDate);
|
||||
|
||||
return Object.assign(new Send(), obj, {
|
||||
key: EncString.fromJSON(obj.key),
|
||||
name: EncString.fromJSON(obj.name),
|
||||
notes: EncString.fromJSON(obj.notes),
|
||||
text: SendText.fromJSON(obj.text),
|
||||
file: SendFile.fromJSON(obj.file),
|
||||
revisionDate,
|
||||
expirationDate,
|
||||
deletionDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class SendAccessRequest {
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Send } from "../domain/send";
|
||||
|
||||
import { SendRequest } from "./send.request";
|
||||
|
||||
export class SendWithIdRequest extends SendRequest {
|
||||
id: string;
|
||||
|
||||
constructor(send: Send) {
|
||||
super(send);
|
||||
this.id = send.id;
|
||||
}
|
||||
}
|
||||
48
libs/common/src/tools/send/models/request/send.request.ts
Normal file
48
libs/common/src/tools/send/models/request/send.request.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendFileApi } from "../api/send-file.api";
|
||||
import { SendTextApi } from "../api/send-text.api";
|
||||
import { Send } from "../domain/send";
|
||||
|
||||
export class SendRequest {
|
||||
type: SendType;
|
||||
fileLength?: number;
|
||||
name: string;
|
||||
notes: string;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
text: SendTextApi;
|
||||
file: SendFileApi;
|
||||
password: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
|
||||
constructor(send: Send, fileLength?: number) {
|
||||
this.type = send.type;
|
||||
this.fileLength = fileLength;
|
||||
this.name = send.name ? send.name.encryptedString : null;
|
||||
this.notes = send.notes ? send.notes.encryptedString : null;
|
||||
this.maxAccessCount = send.maxAccessCount;
|
||||
this.expirationDate = send.expirationDate != null ? send.expirationDate.toISOString() : null;
|
||||
this.deletionDate = send.deletionDate != null ? send.deletionDate.toISOString() : null;
|
||||
this.key = send.key != null ? send.key.encryptedString : null;
|
||||
this.password = send.password;
|
||||
this.disabled = send.disabled;
|
||||
this.hideEmail = send.hideEmail;
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.Text:
|
||||
this.text = new SendTextApi();
|
||||
this.text.text = send.text.text != null ? send.text.text.encryptedString : null;
|
||||
this.text.hidden = send.text.hidden;
|
||||
break;
|
||||
case SendType.File:
|
||||
this.file = new SendFileApi();
|
||||
this.file.fileName = send.file.fileName != null ? send.file.fileName.encryptedString : null;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendFileApi } from "../api/send-file.api";
|
||||
import { SendTextApi } from "../api/send-text.api";
|
||||
|
||||
export class SendAccessResponse extends BaseResponse {
|
||||
id: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
file: SendFileApi;
|
||||
text: SendTextApi;
|
||||
expirationDate: Date;
|
||||
creatorIdentifier: string;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
|
||||
const text = this.getResponseProperty("Text");
|
||||
if (text != null) {
|
||||
this.text = new SendTextApi(text);
|
||||
}
|
||||
|
||||
const file = this.getResponseProperty("File");
|
||||
if (file != null) {
|
||||
this.file = new SendFileApi(file);
|
||||
}
|
||||
|
||||
this.expirationDate = this.getResponseProperty("ExpirationDate");
|
||||
this.creatorIdentifier = this.getResponseProperty("CreatorIdentifier");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
|
||||
export class SendFileDownloadDataResponse extends BaseResponse {
|
||||
id: string = null;
|
||||
url: string = null;
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.url = this.getResponseProperty("Url");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { FileUploadType } from "../../../../enums/fileUploadType";
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
|
||||
import { SendResponse } from "./send.response";
|
||||
|
||||
export class SendFileUploadDataResponse extends BaseResponse {
|
||||
fileUploadType: FileUploadType;
|
||||
sendResponse: SendResponse;
|
||||
url: string = null;
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.fileUploadType = this.getResponseProperty("FileUploadType");
|
||||
const sendResponse = this.getResponseProperty("SendResponse");
|
||||
this.sendResponse = sendResponse == null ? null : new SendResponse(sendResponse);
|
||||
this.url = this.getResponseProperty("Url");
|
||||
}
|
||||
}
|
||||
51
libs/common/src/tools/send/models/response/send.response.ts
Normal file
51
libs/common/src/tools/send/models/response/send.response.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { BaseResponse } from "../../../../models/response/base.response";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendFileApi } from "../api/send-file.api";
|
||||
import { SendTextApi } from "../api/send-text.api";
|
||||
|
||||
export class SendResponse extends BaseResponse {
|
||||
id: string;
|
||||
accessId: string;
|
||||
type: SendType;
|
||||
name: string;
|
||||
notes: string;
|
||||
file: SendFileApi;
|
||||
text: SendTextApi;
|
||||
key: string;
|
||||
maxAccessCount?: number;
|
||||
accessCount: number;
|
||||
revisionDate: string;
|
||||
expirationDate: string;
|
||||
deletionDate: string;
|
||||
password: string;
|
||||
disable: boolean;
|
||||
hideEmail: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
this.id = this.getResponseProperty("Id");
|
||||
this.accessId = this.getResponseProperty("AccessId");
|
||||
this.type = this.getResponseProperty("Type");
|
||||
this.name = this.getResponseProperty("Name");
|
||||
this.notes = this.getResponseProperty("Notes");
|
||||
this.key = this.getResponseProperty("Key");
|
||||
this.maxAccessCount = this.getResponseProperty("MaxAccessCount");
|
||||
this.accessCount = this.getResponseProperty("AccessCount");
|
||||
this.revisionDate = this.getResponseProperty("RevisionDate");
|
||||
this.expirationDate = this.getResponseProperty("ExpirationDate");
|
||||
this.deletionDate = this.getResponseProperty("DeletionDate");
|
||||
this.password = this.getResponseProperty("Password");
|
||||
this.disable = this.getResponseProperty("Disabled") || false;
|
||||
this.hideEmail = this.getResponseProperty("HideEmail") || false;
|
||||
|
||||
const text = this.getResponseProperty("Text");
|
||||
if (text != null) {
|
||||
this.text = new SendTextApi(text);
|
||||
}
|
||||
|
||||
const file = this.getResponseProperty("File");
|
||||
if (file != null) {
|
||||
this.file = new SendFileApi(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
libs/common/src/tools/send/models/view/send-access.view.ts
Normal file
27
libs/common/src/tools/send/models/view/send-access.view.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { View } from "../../../../models/view/view";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { SendAccess } from "../domain/send-access";
|
||||
|
||||
import { SendFileView } from "./send-file.view";
|
||||
import { SendTextView } from "./send-text.view";
|
||||
|
||||
export class SendAccessView implements View {
|
||||
id: string = null;
|
||||
name: string = null;
|
||||
type: SendType = null;
|
||||
text = new SendTextView();
|
||||
file = new SendFileView();
|
||||
expirationDate: Date = null;
|
||||
creatorIdentifier: string = null;
|
||||
|
||||
constructor(s?: SendAccess) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = s.id;
|
||||
this.type = s.type;
|
||||
this.expirationDate = s.expirationDate;
|
||||
this.creatorIdentifier = s.creatorIdentifier;
|
||||
}
|
||||
}
|
||||
39
libs/common/src/tools/send/models/view/send-file.view.ts
Normal file
39
libs/common/src/tools/send/models/view/send-file.view.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { View } from "../../../../models/view/view";
|
||||
import { DeepJsonify } from "../../../../types/deep-jsonify";
|
||||
import { SendFile } from "../domain/send-file";
|
||||
|
||||
export class SendFileView implements View {
|
||||
id: string = null;
|
||||
size: string = null;
|
||||
sizeName: string = null;
|
||||
fileName: string = null;
|
||||
|
||||
constructor(f?: SendFile) {
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = f.id;
|
||||
this.size = f.size;
|
||||
this.sizeName = f.sizeName;
|
||||
}
|
||||
|
||||
get fileSize(): number {
|
||||
try {
|
||||
if (this.size != null) {
|
||||
return parseInt(this.size, null);
|
||||
}
|
||||
} catch {
|
||||
// Invalid file size.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendFileView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendFileView(), json);
|
||||
}
|
||||
}
|
||||
28
libs/common/src/tools/send/models/view/send-text.view.ts
Normal file
28
libs/common/src/tools/send/models/view/send-text.view.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { View } from "../../../../models/view/view";
|
||||
import { DeepJsonify } from "../../../../types/deep-jsonify";
|
||||
import { SendText } from "../domain/send-text";
|
||||
|
||||
export class SendTextView implements View {
|
||||
text: string = null;
|
||||
hidden: boolean;
|
||||
|
||||
constructor(t?: SendText) {
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hidden = t.hidden;
|
||||
}
|
||||
|
||||
get maskedText(): string {
|
||||
return this.text != null ? "••••••••" : null;
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendTextView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendTextView(), json);
|
||||
}
|
||||
}
|
||||
94
libs/common/src/tools/send/models/view/send.view.ts
Normal file
94
libs/common/src/tools/send/models/view/send.view.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Utils } from "../../../../misc/utils";
|
||||
import { SymmetricCryptoKey } from "../../../../models/domain/symmetric-crypto-key";
|
||||
import { View } from "../../../../models/view/view";
|
||||
import { DeepJsonify } from "../../../../types/deep-jsonify";
|
||||
import { SendType } from "../../enums/send-type";
|
||||
import { Send } from "../domain/send";
|
||||
|
||||
import { SendFileView } from "./send-file.view";
|
||||
import { SendTextView } from "./send-text.view";
|
||||
|
||||
export class SendView implements View {
|
||||
id: string = null;
|
||||
accessId: string = null;
|
||||
name: string = null;
|
||||
notes: string = null;
|
||||
key: ArrayBuffer;
|
||||
cryptoKey: SymmetricCryptoKey;
|
||||
type: SendType = null;
|
||||
text = new SendTextView();
|
||||
file = new SendFileView();
|
||||
maxAccessCount?: number = null;
|
||||
accessCount = 0;
|
||||
revisionDate: Date = null;
|
||||
deletionDate: Date = null;
|
||||
expirationDate: Date = null;
|
||||
password: string = null;
|
||||
disabled = false;
|
||||
hideEmail = false;
|
||||
|
||||
constructor(s?: Send) {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.id = s.id;
|
||||
this.accessId = s.accessId;
|
||||
this.type = s.type;
|
||||
this.maxAccessCount = s.maxAccessCount;
|
||||
this.accessCount = s.accessCount;
|
||||
this.revisionDate = s.revisionDate;
|
||||
this.deletionDate = s.deletionDate;
|
||||
this.expirationDate = s.expirationDate;
|
||||
this.disabled = s.disabled;
|
||||
this.password = s.password;
|
||||
this.hideEmail = s.hideEmail;
|
||||
}
|
||||
|
||||
get urlB64Key(): string {
|
||||
return Utils.fromBufferToUrlB64(this.key);
|
||||
}
|
||||
|
||||
get maxAccessCountReached(): boolean {
|
||||
if (this.maxAccessCount == null) {
|
||||
return false;
|
||||
}
|
||||
return this.accessCount >= this.maxAccessCount;
|
||||
}
|
||||
|
||||
get expired(): boolean {
|
||||
if (this.expirationDate == null) {
|
||||
return false;
|
||||
}
|
||||
return this.expirationDate <= new Date();
|
||||
}
|
||||
|
||||
get pendingDelete(): boolean {
|
||||
return this.deletionDate <= new Date();
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Utils.merge(
|
||||
{ ...this },
|
||||
{
|
||||
key: Utils.fromBufferToB64(this.key),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static fromJSON(json: DeepJsonify<SendView>) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Object.assign(new SendView(), json, {
|
||||
key: Utils.fromB64ToArray(json.key)?.buffer,
|
||||
cryptoKey: SymmetricCryptoKey.fromJSON(json.cryptoKey),
|
||||
text: SendTextView.fromJSON(json.text),
|
||||
file: SendFileView.fromJSON(json.file),
|
||||
revisionDate: json.revisionDate == null ? null : new Date(json.revisionDate),
|
||||
deletionDate: json.deletionDate == null ? null : new Date(json.deletionDate),
|
||||
expirationDate: json.expirationDate == null ? null : new Date(json.expirationDate),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendAccessRequest } from "../models/request/send-access.request";
|
||||
import { SendRequest } from "../models/request/send.request";
|
||||
import { SendAccessResponse } from "../models/response/send-access.response";
|
||||
import { SendFileDownloadDataResponse } from "../models/response/send-file-download-data.response";
|
||||
import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response";
|
||||
import { SendResponse } from "../models/response/send.response";
|
||||
import { SendAccessView } from "../models/view/send-access.view";
|
||||
|
||||
export abstract class SendApiService {
|
||||
getSend: (id: string) => Promise<SendResponse>;
|
||||
postSendAccess: (
|
||||
id: string,
|
||||
request: SendAccessRequest,
|
||||
apiUrl?: string
|
||||
) => Promise<SendAccessResponse>;
|
||||
getSends: () => Promise<ListResponse<SendResponse>>;
|
||||
postSend: (request: SendRequest) => Promise<SendResponse>;
|
||||
postFileTypeSend: (request: SendRequest) => Promise<SendFileUploadDataResponse>;
|
||||
postSendFile: (sendId: string, fileId: string, data: FormData) => Promise<any>;
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
postSendFileLegacy: (data: FormData) => Promise<SendResponse>;
|
||||
putSend: (id: string, request: SendRequest) => Promise<SendResponse>;
|
||||
putSendRemovePassword: (id: string) => Promise<SendResponse>;
|
||||
deleteSend: (id: string) => Promise<any>;
|
||||
getSendFileDownloadData: (
|
||||
send: SendAccessView,
|
||||
request: SendAccessRequest,
|
||||
apiUrl?: string
|
||||
) => Promise<SendFileDownloadDataResponse>;
|
||||
renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise<SendFileUploadDataResponse>;
|
||||
removePassword: (id: string) => Promise<any>;
|
||||
delete: (id: string) => Promise<any>;
|
||||
save: (sendData: [Send, EncArrayBuffer]) => Promise<any>;
|
||||
}
|
||||
252
libs/common/src/tools/send/services/send-api.service.ts
Normal file
252
libs/common/src/tools/send/services/send-api.service.ts
Normal file
@@ -0,0 +1,252 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import {
|
||||
FileUploadApiMethods,
|
||||
FileUploadService,
|
||||
} from "../../../abstractions/file-upload/file-upload.service";
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer";
|
||||
import { ErrorResponse } from "../../../models/response/error.response";
|
||||
import { ListResponse } from "../../../models/response/list.response";
|
||||
import { SendType } from "../enums/send-type";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendAccessRequest } from "../models/request/send-access.request";
|
||||
import { SendRequest } from "../models/request/send.request";
|
||||
import { SendAccessResponse } from "../models/response/send-access.response";
|
||||
import { SendFileDownloadDataResponse } from "../models/response/send-file-download-data.response";
|
||||
import { SendFileUploadDataResponse } from "../models/response/send-file-upload-data.response";
|
||||
import { SendResponse } from "../models/response/send.response";
|
||||
import { SendAccessView } from "../models/view/send-access.view";
|
||||
|
||||
import { SendApiService as SendApiServiceAbstraction } from "./send-api.service.abstraction";
|
||||
import { InternalSendService } from "./send.service.abstraction";
|
||||
|
||||
export class SendApiService implements SendApiServiceAbstraction {
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private fileUploadService: FileUploadService,
|
||||
private sendService: InternalSendService
|
||||
) {}
|
||||
|
||||
async getSend(id: string): Promise<SendResponse> {
|
||||
const r = await this.apiService.send("GET", "/sends/" + id, null, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async postSendAccess(
|
||||
id: string,
|
||||
request: SendAccessRequest,
|
||||
apiUrl?: string
|
||||
): Promise<SendAccessResponse> {
|
||||
const addSendIdHeader = (headers: Headers) => {
|
||||
headers.set("Send-Id", id);
|
||||
};
|
||||
const r = await this.apiService.send(
|
||||
"POST",
|
||||
"/sends/access/" + id,
|
||||
request,
|
||||
false,
|
||||
true,
|
||||
apiUrl,
|
||||
addSendIdHeader
|
||||
);
|
||||
return new SendAccessResponse(r);
|
||||
}
|
||||
|
||||
async getSendFileDownloadData(
|
||||
send: SendAccessView,
|
||||
request: SendAccessRequest,
|
||||
apiUrl?: string
|
||||
): Promise<SendFileDownloadDataResponse> {
|
||||
const addSendIdHeader = (headers: Headers) => {
|
||||
headers.set("Send-Id", send.id);
|
||||
};
|
||||
const r = await this.apiService.send(
|
||||
"POST",
|
||||
"/sends/" + send.id + "/access/file/" + send.file.id,
|
||||
request,
|
||||
false,
|
||||
true,
|
||||
apiUrl,
|
||||
addSendIdHeader
|
||||
);
|
||||
return new SendFileDownloadDataResponse(r);
|
||||
}
|
||||
|
||||
async getSends(): Promise<ListResponse<SendResponse>> {
|
||||
const r = await this.apiService.send("GET", "/sends", null, true, true);
|
||||
return new ListResponse(r, SendResponse);
|
||||
}
|
||||
|
||||
async postSend(request: SendRequest): Promise<SendResponse> {
|
||||
const r = await this.apiService.send("POST", "/sends", request, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async postFileTypeSend(request: SendRequest): Promise<SendFileUploadDataResponse> {
|
||||
const r = await this.apiService.send("POST", "/sends/file/v2", request, true, true);
|
||||
return new SendFileUploadDataResponse(r);
|
||||
}
|
||||
|
||||
async renewSendFileUploadUrl(
|
||||
sendId: string,
|
||||
fileId: string
|
||||
): Promise<SendFileUploadDataResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/sends/" + sendId + "/file/" + fileId,
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new SendFileUploadDataResponse(r);
|
||||
}
|
||||
|
||||
postSendFile(sendId: string, fileId: string, data: FormData): Promise<any> {
|
||||
return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
async postSendFileLegacy(data: FormData): Promise<SendResponse> {
|
||||
const r = await this.apiService.send("POST", "/sends/file", data, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async putSend(id: string, request: SendRequest): Promise<SendResponse> {
|
||||
const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
async putSendRemovePassword(id: string): Promise<SendResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"PUT",
|
||||
"/sends/" + id + "/remove-password",
|
||||
null,
|
||||
true,
|
||||
true
|
||||
);
|
||||
return new SendResponse(r);
|
||||
}
|
||||
|
||||
deleteSend(id: string): Promise<any> {
|
||||
return this.apiService.send("DELETE", "/sends/" + id, null, true, false);
|
||||
}
|
||||
|
||||
async save(sendData: [Send, EncArrayBuffer]): Promise<any> {
|
||||
const response = await this.upload(sendData);
|
||||
|
||||
const data = new SendData(response);
|
||||
await this.sendService.upsert(data);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<any> {
|
||||
await this.deleteSend(id);
|
||||
await this.sendService.delete(id);
|
||||
}
|
||||
|
||||
async removePassword(id: string): Promise<any> {
|
||||
const response = await this.putSendRemovePassword(id);
|
||||
const data = new SendData(response);
|
||||
await this.sendService.upsert(data);
|
||||
}
|
||||
|
||||
// Send File Upload methoids
|
||||
|
||||
private async upload(sendData: [Send, EncArrayBuffer]): Promise<SendResponse> {
|
||||
const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength);
|
||||
let response: SendResponse;
|
||||
if (sendData[0].id == null) {
|
||||
if (sendData[0].type === SendType.Text) {
|
||||
response = await this.postSend(request);
|
||||
} else {
|
||||
try {
|
||||
const uploadDataResponse = await this.postFileTypeSend(request);
|
||||
response = uploadDataResponse.sendResponse;
|
||||
await this.fileUploadService.upload(
|
||||
uploadDataResponse,
|
||||
sendData[0].file.fileName,
|
||||
sendData[1],
|
||||
this.generateMethods(uploadDataResponse, response)
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) {
|
||||
response = await this.legacyServerSendFileUpload(sendData, request);
|
||||
} else if (e instanceof ErrorResponse) {
|
||||
throw new Error((e as ErrorResponse).getSingleMessage());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
sendData[0].id = response.id;
|
||||
sendData[0].accessId = response.accessId;
|
||||
} else {
|
||||
response = await this.putSend(sendData[0].id, request);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private generateMethods(
|
||||
uploadData: SendFileUploadDataResponse,
|
||||
response: SendResponse
|
||||
): FileUploadApiMethods {
|
||||
return {
|
||||
postDirect: this.generatePostDirectCallback(response),
|
||||
renewFileUploadUrl: this.generateRenewFileUploadUrlCallback(response.id, response.file.id),
|
||||
rollback: this.generateRollbackCallback(response.id),
|
||||
};
|
||||
}
|
||||
|
||||
private generatePostDirectCallback(sendResponse: SendResponse) {
|
||||
return (data: FormData) => {
|
||||
return this.postSendFile(sendResponse.id, sendResponse.file.id, data);
|
||||
};
|
||||
}
|
||||
|
||||
private generateRenewFileUploadUrlCallback(sendId: string, fileId: string) {
|
||||
return async () => {
|
||||
const renewResponse = await this.renewSendFileUploadUrl(sendId, fileId);
|
||||
return renewResponse?.url;
|
||||
};
|
||||
}
|
||||
|
||||
private generateRollbackCallback(sendId: string) {
|
||||
return () => {
|
||||
return this.deleteSend(sendId);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads.
|
||||
* This method still exists for backward compatibility with old server versions.
|
||||
*/
|
||||
async legacyServerSendFileUpload(
|
||||
sendData: [Send, EncArrayBuffer],
|
||||
request: SendRequest
|
||||
): Promise<SendResponse> {
|
||||
const fd = new FormData();
|
||||
try {
|
||||
const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" });
|
||||
fd.append("model", JSON.stringify(request));
|
||||
fd.append("data", blob, sendData[0].file.fileName.encryptedString);
|
||||
} catch (e) {
|
||||
if (Utils.isNode && !Utils.isBrowser) {
|
||||
fd.append("model", JSON.stringify(request));
|
||||
fd.append(
|
||||
"data",
|
||||
Buffer.from(sendData[1].buffer) as any,
|
||||
{
|
||||
filepath: sendData[0].file.fileName.encryptedString,
|
||||
contentType: "application/octet-stream",
|
||||
} as any
|
||||
);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return await this.postSendFileLegacy(fd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer";
|
||||
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendView } from "../models/view/send.view";
|
||||
|
||||
export abstract class SendService {
|
||||
sends$: Observable<Send[]>;
|
||||
sendViews$: Observable<SendView[]>;
|
||||
|
||||
encrypt: (
|
||||
model: SendView,
|
||||
file: File | ArrayBuffer,
|
||||
password: string,
|
||||
key?: SymmetricCryptoKey
|
||||
) => Promise<[Send, EncArrayBuffer]>;
|
||||
get: (id: string) => Send;
|
||||
/**
|
||||
* @deprecated Do not call this, use the sends$ observable collection
|
||||
*/
|
||||
getAll: () => Promise<Send[]>;
|
||||
/**
|
||||
* @deprecated Only use in CLI
|
||||
*/
|
||||
getFromState: (id: string) => Promise<Send>;
|
||||
/**
|
||||
* @deprecated Only use in CLI
|
||||
*/
|
||||
getAllDecryptedFromState: () => Promise<SendView[]>;
|
||||
}
|
||||
|
||||
export abstract class InternalSendService extends SendService {
|
||||
upsert: (send: SendData | SendData[]) => Promise<any>;
|
||||
replace: (sends: { [id: string]: SendData }) => Promise<void>;
|
||||
clear: (userId: string) => Promise<any>;
|
||||
delete: (id: string | string[]) => Promise<any>;
|
||||
}
|
||||
179
libs/common/src/tools/send/services/send.service.spec.ts
Normal file
179
libs/common/src/tools/send/services/send.service.spec.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { any, mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
|
||||
import { CryptoService } from "../../../abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "../../../abstractions/cryptoFunction.service";
|
||||
import { EncryptService } from "../../../abstractions/encrypt.service";
|
||||
import { I18nService } from "../../../abstractions/i18n.service";
|
||||
import { StateService } from "../../../abstractions/state.service";
|
||||
import { EncString } from "../../../models/domain/enc-string";
|
||||
import { ContainerService } from "../../../services/container.service";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendView } from "../models/view/send.view";
|
||||
|
||||
import { SendService } from "./send.service";
|
||||
|
||||
describe("SendService", () => {
|
||||
const cryptoService = mock<CryptoService>();
|
||||
const i18nService = mock<I18nService>();
|
||||
const cryptoFunctionService = mock<CryptoFunctionService>();
|
||||
const encryptService = mock<EncryptService>();
|
||||
|
||||
let sendService: SendService;
|
||||
|
||||
let stateService: MockProxy<StateService>;
|
||||
let activeAccount: BehaviorSubject<string>;
|
||||
let activeAccountUnlocked: BehaviorSubject<boolean>;
|
||||
|
||||
beforeEach(() => {
|
||||
activeAccount = new BehaviorSubject("123");
|
||||
activeAccountUnlocked = new BehaviorSubject(true);
|
||||
|
||||
stateService = mock<StateService>();
|
||||
stateService.activeAccount$ = activeAccount;
|
||||
stateService.activeAccountUnlocked$ = activeAccountUnlocked;
|
||||
(window as any).bitwardenContainerService = new ContainerService(cryptoService, encryptService);
|
||||
|
||||
stateService.getEncryptedSends.calledWith(any()).mockResolvedValue({
|
||||
"1": sendData("1", "Test Send"),
|
||||
});
|
||||
|
||||
stateService.getDecryptedSends
|
||||
.calledWith(any())
|
||||
.mockResolvedValue([sendView("1", "Test Send")]);
|
||||
|
||||
sendService = new SendService(cryptoService, i18nService, cryptoFunctionService, stateService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
activeAccount.complete();
|
||||
activeAccountUnlocked.complete();
|
||||
});
|
||||
|
||||
describe("get", () => {
|
||||
it("exists", async () => {
|
||||
const result = sendService.get("1");
|
||||
|
||||
expect(result).toEqual(send("1", "Test Send"));
|
||||
});
|
||||
|
||||
it("does not exist", async () => {
|
||||
const result = sendService.get("2");
|
||||
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("getAll", async () => {
|
||||
const sends = await sendService.getAll();
|
||||
const send1 = sends[0];
|
||||
|
||||
expect(sends).toHaveLength(1);
|
||||
expect(send1).toEqual(send("1", "Test Send"));
|
||||
});
|
||||
|
||||
describe("getFromState", () => {
|
||||
it("exists", async () => {
|
||||
const result = await sendService.getFromState("1");
|
||||
|
||||
expect(result).toEqual(send("1", "Test Send"));
|
||||
});
|
||||
it("does not exist", async () => {
|
||||
const result = await sendService.getFromState("2");
|
||||
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it("getAllDecryptedFromState", async () => {
|
||||
await sendService.getAllDecryptedFromState();
|
||||
|
||||
expect(stateService.getDecryptedSends).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// InternalSendService
|
||||
|
||||
it("upsert", async () => {
|
||||
await sendService.upsert(sendData("2", "Test 2"));
|
||||
|
||||
expect(await firstValueFrom(sendService.sends$)).toEqual([
|
||||
send("1", "Test Send"),
|
||||
send("2", "Test 2"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("replace", async () => {
|
||||
await sendService.replace({ "2": sendData("2", "test 2") });
|
||||
|
||||
expect(await firstValueFrom(sendService.sends$)).toEqual([send("2", "test 2")]);
|
||||
});
|
||||
|
||||
it("clear", async () => {
|
||||
await sendService.clear();
|
||||
|
||||
expect(await firstValueFrom(sendService.sends$)).toEqual([]);
|
||||
});
|
||||
|
||||
describe("delete", () => {
|
||||
it("exists", async () => {
|
||||
await sendService.delete("1");
|
||||
|
||||
expect(stateService.getEncryptedSends).toHaveBeenCalledTimes(2);
|
||||
expect(stateService.setEncryptedSends).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not exist", async () => {
|
||||
sendService.delete("1");
|
||||
|
||||
expect(stateService.getEncryptedSends).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
// Send object helper functions
|
||||
|
||||
function sendData(id: string, name: string) {
|
||||
const data = new SendData({} as any);
|
||||
data.id = id;
|
||||
data.name = name;
|
||||
data.disabled = false;
|
||||
data.accessCount = 2;
|
||||
data.accessId = "1";
|
||||
data.revisionDate = null;
|
||||
data.expirationDate = null;
|
||||
data.deletionDate = null;
|
||||
data.notes = "Notes!!";
|
||||
data.key = null;
|
||||
return data;
|
||||
}
|
||||
|
||||
function sendView(id: string, name: string) {
|
||||
const data = new SendView({} as any);
|
||||
data.id = id;
|
||||
data.name = name;
|
||||
data.disabled = false;
|
||||
data.accessCount = 2;
|
||||
data.accessId = "1";
|
||||
data.revisionDate = null;
|
||||
data.expirationDate = null;
|
||||
data.deletionDate = null;
|
||||
data.notes = "Notes!!";
|
||||
data.key = null;
|
||||
return data;
|
||||
}
|
||||
|
||||
function send(id: string, name: string) {
|
||||
const data = new Send({} as any);
|
||||
data.id = id;
|
||||
data.name = new EncString(name);
|
||||
data.disabled = false;
|
||||
data.accessCount = 2;
|
||||
data.accessId = "1";
|
||||
data.revisionDate = null;
|
||||
data.expirationDate = null;
|
||||
data.deletionDate = null;
|
||||
data.notes = new EncString("Notes!!");
|
||||
data.key = null;
|
||||
return data;
|
||||
}
|
||||
});
|
||||
264
libs/common/src/tools/send/services/send.service.ts
Normal file
264
libs/common/src/tools/send/services/send.service.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { BehaviorSubject, concatMap } from "rxjs";
|
||||
|
||||
import { CryptoService } from "../../../abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "../../../abstractions/cryptoFunction.service";
|
||||
import { I18nService } from "../../../abstractions/i18n.service";
|
||||
import { StateService } from "../../../abstractions/state.service";
|
||||
import { SEND_KDF_ITERATIONS } from "../../../enums/kdfType";
|
||||
import { Utils } from "../../../misc/utils";
|
||||
import { EncArrayBuffer } from "../../../models/domain/enc-array-buffer";
|
||||
import { EncString } from "../../../models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../models/domain/symmetric-crypto-key";
|
||||
import { SendType } from "../enums/send-type";
|
||||
import { SendData } from "../models/data/send.data";
|
||||
import { Send } from "../models/domain/send";
|
||||
import { SendFile } from "../models/domain/send-file";
|
||||
import { SendText } from "../models/domain/send-text";
|
||||
import { SendView } from "../models/view/send.view";
|
||||
|
||||
import { InternalSendService as InternalSendServiceAbstraction } from "./send.service.abstraction";
|
||||
|
||||
export class SendService implements InternalSendServiceAbstraction {
|
||||
protected _sends: BehaviorSubject<Send[]> = new BehaviorSubject([]);
|
||||
protected _sendViews: BehaviorSubject<SendView[]> = new BehaviorSubject([]);
|
||||
|
||||
sends$ = this._sends.asObservable();
|
||||
sendViews$ = this._sendViews.asObservable();
|
||||
|
||||
constructor(
|
||||
private cryptoService: CryptoService,
|
||||
private i18nService: I18nService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private stateService: StateService
|
||||
) {
|
||||
this.stateService.activeAccountUnlocked$
|
||||
.pipe(
|
||||
concatMap(async (unlocked) => {
|
||||
if (Utils.global.bitwardenContainerService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!unlocked) {
|
||||
this._sends.next([]);
|
||||
this._sendViews.next([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await this.stateService.getEncryptedSends();
|
||||
|
||||
await this.updateObservables(data);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async clearCache(): Promise<void> {
|
||||
await this._sendViews.next([]);
|
||||
}
|
||||
|
||||
async encrypt(
|
||||
model: SendView,
|
||||
file: File | ArrayBuffer,
|
||||
password: string,
|
||||
key?: SymmetricCryptoKey
|
||||
): Promise<[Send, EncArrayBuffer]> {
|
||||
let fileData: EncArrayBuffer = null;
|
||||
const send = new Send();
|
||||
send.id = model.id;
|
||||
send.type = model.type;
|
||||
send.disabled = model.disabled;
|
||||
send.hideEmail = model.hideEmail;
|
||||
send.maxAccessCount = model.maxAccessCount;
|
||||
if (model.key == null) {
|
||||
model.key = await this.cryptoFunctionService.randomBytes(16);
|
||||
model.cryptoKey = await this.cryptoService.makeSendKey(model.key);
|
||||
}
|
||||
if (password != null) {
|
||||
const passwordHash = await this.cryptoFunctionService.pbkdf2(
|
||||
password,
|
||||
model.key,
|
||||
"sha256",
|
||||
SEND_KDF_ITERATIONS
|
||||
);
|
||||
send.password = Utils.fromBufferToB64(passwordHash);
|
||||
}
|
||||
send.key = await this.cryptoService.encrypt(model.key, key);
|
||||
send.name = await this.cryptoService.encrypt(model.name, model.cryptoKey);
|
||||
send.notes = await this.cryptoService.encrypt(model.notes, model.cryptoKey);
|
||||
if (send.type === SendType.Text) {
|
||||
send.text = new SendText();
|
||||
send.text.text = await this.cryptoService.encrypt(model.text.text, model.cryptoKey);
|
||||
send.text.hidden = model.text.hidden;
|
||||
} else if (send.type === SendType.File) {
|
||||
send.file = new SendFile();
|
||||
if (file != null) {
|
||||
if (file instanceof ArrayBuffer) {
|
||||
const [name, data] = await this.encryptFileData(
|
||||
model.file.fileName,
|
||||
file,
|
||||
model.cryptoKey
|
||||
);
|
||||
send.file.fileName = name;
|
||||
fileData = data;
|
||||
} else {
|
||||
fileData = await this.parseFile(send, file, model.cryptoKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [send, fileData];
|
||||
}
|
||||
|
||||
get(id: string): Send {
|
||||
const sends = this._sends.getValue();
|
||||
return sends.find((send) => send.id === id);
|
||||
}
|
||||
|
||||
async getFromState(id: string): Promise<Send> {
|
||||
const sends = await this.stateService.getEncryptedSends();
|
||||
// eslint-disable-next-line
|
||||
if (sends == null || !sends.hasOwnProperty(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Send(sends[id]);
|
||||
}
|
||||
|
||||
async getAll(): Promise<Send[]> {
|
||||
const sends = await this.stateService.getEncryptedSends();
|
||||
const response: Send[] = [];
|
||||
for (const id in sends) {
|
||||
// eslint-disable-next-line
|
||||
if (sends.hasOwnProperty(id)) {
|
||||
response.push(new Send(sends[id]));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
async getAllDecryptedFromState(): Promise<SendView[]> {
|
||||
let decSends = await this.stateService.getDecryptedSends();
|
||||
if (decSends != null) {
|
||||
return decSends;
|
||||
}
|
||||
|
||||
decSends = [];
|
||||
const hasKey = await this.cryptoService.hasKey();
|
||||
if (!hasKey) {
|
||||
throw new Error("No key.");
|
||||
}
|
||||
|
||||
const promises: Promise<any>[] = [];
|
||||
const sends = await this.getAll();
|
||||
sends.forEach((send) => {
|
||||
promises.push(send.decrypt().then((f) => decSends.push(f)));
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
decSends.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
|
||||
await this.stateService.setDecryptedSends(decSends);
|
||||
return decSends;
|
||||
}
|
||||
|
||||
async upsert(send: SendData | SendData[]): Promise<any> {
|
||||
let sends = await this.stateService.getEncryptedSends();
|
||||
if (sends == null) {
|
||||
sends = {};
|
||||
}
|
||||
if (send instanceof SendData) {
|
||||
const s = send as SendData;
|
||||
sends[s.id] = s;
|
||||
} else {
|
||||
(send as SendData[]).forEach((s) => {
|
||||
sends[s.id] = s;
|
||||
});
|
||||
}
|
||||
|
||||
await this.replace(sends);
|
||||
}
|
||||
|
||||
async clear(userId?: string): Promise<any> {
|
||||
if (userId == null || userId == (await this.stateService.getUserId())) {
|
||||
this._sends.next([]);
|
||||
this._sendViews.next([]);
|
||||
}
|
||||
await this.stateService.setDecryptedSends(null, { userId: userId });
|
||||
await this.stateService.setEncryptedSends(null, { userId: userId });
|
||||
}
|
||||
|
||||
async delete(id: string | string[]): Promise<any> {
|
||||
const sends = await this.stateService.getEncryptedSends();
|
||||
if (sends == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof id === "string") {
|
||||
if (sends[id] == null) {
|
||||
return;
|
||||
}
|
||||
delete sends[id];
|
||||
} else {
|
||||
(id as string[]).forEach((i) => {
|
||||
delete sends[i];
|
||||
});
|
||||
}
|
||||
|
||||
await this.replace(sends);
|
||||
}
|
||||
|
||||
async replace(sends: { [id: string]: SendData }): Promise<any> {
|
||||
await this.updateObservables(sends);
|
||||
await this.stateService.setEncryptedSends(sends);
|
||||
}
|
||||
|
||||
private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = async (evt) => {
|
||||
try {
|
||||
const [name, data] = await this.encryptFileData(
|
||||
file.name,
|
||||
evt.target.result as ArrayBuffer,
|
||||
key
|
||||
);
|
||||
send.file.fileName = name;
|
||||
resolve(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject("Error reading file.");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async encryptFileData(
|
||||
fileName: string,
|
||||
data: ArrayBuffer,
|
||||
key: SymmetricCryptoKey
|
||||
): Promise<[EncString, EncArrayBuffer]> {
|
||||
const encFileName = await this.cryptoService.encrypt(fileName, key);
|
||||
const encFileData = await this.cryptoService.encryptToBytes(data, key);
|
||||
return [encFileName, encFileData];
|
||||
}
|
||||
|
||||
private async updateObservables(sendsMap: { [id: string]: SendData }) {
|
||||
const sends = Object.values(sendsMap || {}).map((f) => new Send(f));
|
||||
this._sends.next(sends);
|
||||
|
||||
if (await this.cryptoService.hasKey()) {
|
||||
this._sendViews.next(await this.decryptSends(sends));
|
||||
}
|
||||
}
|
||||
|
||||
private async decryptSends(sends: Send[]) {
|
||||
const decryptSendPromises = sends.map((s) => s.decrypt());
|
||||
const decryptedSends = await Promise.all(decryptSendPromises);
|
||||
|
||||
decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
return decryptedSends;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user