2021-02-03 18:44:33 +01:00
|
|
|
import * as fs from "fs";
|
|
|
|
import * as path from "path";
|
|
|
|
|
2022-06-14 17:10:53 +02:00
|
|
|
import { NodeUtils } from "@bitwarden/common/misc/nodeUtils";
|
2023-06-06 22:34:53 +02:00
|
|
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
|
|
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
2023-03-29 16:23:37 +02:00
|
|
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
|
|
|
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
|
|
|
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
2021-02-03 18:44:33 +01:00
|
|
|
|
2023-03-29 16:23:37 +02:00
|
|
|
import { Response } from "../../../models/response";
|
|
|
|
import { CliUtils } from "../../../utils";
|
|
|
|
import { SendTextResponse } from "../models/send-text.response";
|
|
|
|
import { SendResponse } from "../models/send.response";
|
2021-02-03 18:44:33 +01:00
|
|
|
|
|
|
|
export class SendCreateCommand {
|
|
|
|
constructor(
|
|
|
|
private sendService: SendService,
|
2021-12-28 21:38:51 +01:00
|
|
|
private stateService: StateService,
|
2023-03-28 18:37:40 +02:00
|
|
|
private environmentService: EnvironmentService,
|
|
|
|
private sendApiService: SendApiService
|
2021-02-03 18:44:33 +01:00
|
|
|
) {}
|
2021-12-20 18:04:00 +01:00
|
|
|
|
2022-01-19 16:45:14 +01:00
|
|
|
async run(requestJson: any, cmdOptions: Record<string, any>) {
|
2021-02-03 18:44:33 +01:00
|
|
|
let req: any = null;
|
2022-01-19 16:45:14 +01:00
|
|
|
if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
|
2021-02-03 18:44:33 +01:00
|
|
|
requestJson = await CliUtils.readStdin();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requestJson == null || requestJson === "") {
|
|
|
|
return Response.badRequest("`requestJson` was not provided.");
|
|
|
|
}
|
|
|
|
|
2022-01-19 16:45:14 +01:00
|
|
|
if (typeof requestJson !== "string") {
|
|
|
|
req = requestJson;
|
2022-03-04 15:47:46 +01:00
|
|
|
req.deletionDate = req.deletionDate == null ? null : new Date(req.deletionDate);
|
|
|
|
req.expirationDate = req.expirationDate == null ? null : new Date(req.expirationDate);
|
2022-01-19 16:45:14 +01:00
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const reqJson = Buffer.from(requestJson, "base64").toString();
|
|
|
|
req = SendResponse.fromJson(reqJson);
|
2021-02-03 18:44:33 +01:00
|
|
|
|
2022-01-19 16:45:14 +01:00
|
|
|
if (req == null) {
|
|
|
|
throw new Error("Null request");
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return Response.badRequest("Error parsing the encoded request data.");
|
2021-12-20 18:04:00 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-03 18:44:33 +01:00
|
|
|
|
|
|
|
if (
|
|
|
|
req.deletionDate == null ||
|
|
|
|
isNaN(new Date(req.deletionDate).getTime()) ||
|
|
|
|
new Date(req.deletionDate) <= new Date()
|
|
|
|
) {
|
|
|
|
return Response.badRequest("Must specify a valid deletion date after the current time");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.expirationDate != null && isNaN(new Date(req.expirationDate).getTime())) {
|
|
|
|
return Response.badRequest("Unable to parse expirationDate: " + req.expirationDate);
|
|
|
|
}
|
|
|
|
|
2022-01-19 16:45:14 +01:00
|
|
|
const normalizedOptions = new Options(cmdOptions);
|
|
|
|
return this.createSend(req, normalizedOptions);
|
2021-02-03 18:44:33 +01:00
|
|
|
}
|
|
|
|
|
2022-01-19 16:45:14 +01:00
|
|
|
private async createSend(req: SendResponse, options: Options) {
|
2021-02-03 18:44:33 +01:00
|
|
|
const filePath = req.file?.fileName ?? options.file;
|
|
|
|
const text = req.text?.text ?? options.text;
|
|
|
|
const hidden = req.text?.hidden ?? options.hidden;
|
|
|
|
const password = req.password ?? options.password;
|
2021-05-11 21:55:04 +02:00
|
|
|
const maxAccessCount = req.maxAccessCount ?? options.maxAccessCount;
|
2021-12-20 18:04:00 +01:00
|
|
|
|
2021-02-03 18:44:33 +01:00
|
|
|
req.key = null;
|
2021-05-11 21:55:04 +02:00
|
|
|
req.maxAccessCount = maxAccessCount;
|
2021-02-03 18:44:33 +01:00
|
|
|
|
|
|
|
switch (req.type) {
|
|
|
|
case SendType.File:
|
2022-01-19 16:45:14 +01:00
|
|
|
if (process.env.BW_SERVE === "true") {
|
|
|
|
return Response.error(
|
|
|
|
"Creating a file-based Send is unsupported through the `serve` command at this time."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-28 21:38:51 +01:00
|
|
|
if (!(await this.stateService.getCanAccessPremium())) {
|
2021-02-03 18:44:33 +01:00
|
|
|
return Response.error("Premium status is required to use this feature.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filePath == null) {
|
|
|
|
return Response.badRequest(
|
2022-01-19 16:45:14 +01:00
|
|
|
"Must specify a file to Send either with the --file option or in the request JSON."
|
2021-02-03 18:44:33 +01:00
|
|
|
);
|
|
|
|
}
|
2021-12-20 18:04:00 +01:00
|
|
|
|
2021-02-04 05:51:59 +01:00
|
|
|
req.file.fileName = path.basename(filePath);
|
2021-12-20 18:04:00 +01:00
|
|
|
break;
|
2021-02-03 18:44:33 +01:00
|
|
|
case SendType.Text:
|
|
|
|
if (text == null) {
|
|
|
|
return Response.badRequest(
|
2022-01-19 16:45:14 +01:00
|
|
|
"Must specify text content to Send either with the --text option or in the request JSON."
|
2021-12-20 18:04:00 +01:00
|
|
|
);
|
|
|
|
}
|
2021-02-03 18:44:33 +01:00
|
|
|
req.text = new SendTextResponse();
|
|
|
|
req.text.text = text;
|
|
|
|
req.text.hidden = hidden;
|
2021-12-20 18:04:00 +01:00
|
|
|
break;
|
|
|
|
default:
|
2021-02-03 18:44:33 +01:00
|
|
|
return Response.badRequest(
|
2022-01-19 16:45:14 +01:00
|
|
|
"Unknown Send type " + SendType[req.type] + ". Valid types are: file, text"
|
2021-12-20 18:04:00 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2021-02-03 18:44:33 +01:00
|
|
|
let fileBuffer: ArrayBuffer = null;
|
|
|
|
if (req.type === SendType.File) {
|
|
|
|
fileBuffer = NodeUtils.bufferToArrayBuffer(fs.readFileSync(filePath));
|
2021-12-20 18:04:00 +01:00
|
|
|
}
|
|
|
|
|
2021-02-03 18:44:33 +01:00
|
|
|
const sendView = SendResponse.toView(req);
|
|
|
|
const [encSend, fileData] = await this.sendService.encrypt(sendView, fileBuffer, password);
|
|
|
|
// Add dates from template
|
|
|
|
encSend.deletionDate = sendView.deletionDate;
|
|
|
|
encSend.expirationDate = sendView.expirationDate;
|
2021-12-20 18:04:00 +01:00
|
|
|
|
2023-03-28 18:37:40 +02:00
|
|
|
await this.sendApiService.save([encSend, fileData]);
|
|
|
|
const newSend = await this.sendService.getFromState(encSend.id);
|
2021-02-03 18:44:33 +01:00
|
|
|
const decSend = await newSend.decrypt();
|
|
|
|
const res = new SendResponse(decSend, this.environmentService.getWebVaultUrl());
|
2022-01-19 16:45:14 +01:00
|
|
|
return Response.success(res);
|
2021-02-03 18:44:33 +01:00
|
|
|
} catch (e) {
|
|
|
|
return Response.error(e);
|
|
|
|
}
|
2021-12-20 18:04:00 +01:00
|
|
|
}
|
2021-02-03 18:44:33 +01:00
|
|
|
}
|
2022-01-19 16:45:14 +01:00
|
|
|
|
|
|
|
class Options {
|
|
|
|
file: string;
|
|
|
|
text: string;
|
|
|
|
maxAccessCount: number;
|
|
|
|
password: string;
|
|
|
|
hidden: boolean;
|
|
|
|
|
|
|
|
constructor(passedOptions: Record<string, any>) {
|
2022-01-26 17:28:56 +01:00
|
|
|
this.file = passedOptions?.file;
|
|
|
|
this.text = passedOptions?.text;
|
|
|
|
this.password = passedOptions?.password;
|
|
|
|
this.hidden = CliUtils.convertBooleanOption(passedOptions?.hidden);
|
2022-01-19 16:45:14 +01:00
|
|
|
this.maxAccessCount =
|
2022-01-26 17:28:56 +01:00
|
|
|
passedOptions?.maxAccessCount != null ? parseInt(passedOptions.maxAccessCount, null) : null;
|
2022-01-19 16:45:14 +01:00
|
|
|
}
|
|
|
|
}
|