1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-09 05:57:40 +02:00
bitwarden-browser/src/app/send/add-edit.component.ts
Kyle Spearrin e9273ff79a
Send initial implementation (#687)
* send work

* Bump version to 2.16.2 (#668)

* [SSO] New User Provision flow jslib update (f30d6f8 -> d84d6da) (#672)

* Update jslib (f30d6f8 -> d84d6da)

* Updated imports/constructor to super

* OnlyOrg Policy (#669)

* added localization strings needed for the OnlyOrg policy

* added deprecation warning to policies page

* allowed OnlyOrg policy configuration

* blocked creating new orgs if already in an org with OnlyOrg enabled

* code review cleanup for onlyOrg

* removed a blank line

* code review cleanup for onlyOrg

* send listing actions

* updates

* access id

* update jslib

* re-work key and password derivation

* update jslib

* makeSendKey

* update access path

* store max access count

* update jslib

* l10n work

* l10n for access page

* l10n and cleanup

* fix l10n

Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com>
Co-authored-by: Addison Beck <abeck@bitwarden.com>
2020-11-04 14:49:08 -05:00

277 lines
10 KiB
TypeScript

import { DatePipe } from '@angular/common';
import {
EventEmitter,
Input,
Output,
} from '@angular/core';
import { Component } from '@angular/core';
import { SendType } from 'jslib/enums/sendType';
import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { SendView } from 'jslib/models/view/sendView';
import { SendFileView } from 'jslib/models/view/sendFileView';
import { SendTextView } from 'jslib/models/view/sendTextView';
import { Send } from 'jslib/models/domain/send';
import { SendFile } from 'jslib/models/domain/sendFile';
import { SendText } from 'jslib/models/domain/sendText';
import { SendData } from 'jslib/models/data/sendData';
import { SendRequest } from 'jslib/models/request/sendRequest';
import { Utils } from 'jslib/misc/utils';
@Component({
selector: 'app-send-add-edit',
templateUrl: 'add-edit.component.html',
})
export class AddEditComponent {
@Input() sendId: string;
@Input() type: SendType;
@Output() onSavedSend = new EventEmitter<SendView>();
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
editMode: boolean = false;
send: SendView;
link: string;
title: string;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
formPromise: Promise<any>;
deletePromise: Promise<any>;
sendType = SendType;
typeOptions: any[];
constructor(private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
private apiService: ApiService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private environmentService: EnvironmentService,
private datePipe: DatePipe) {
this.typeOptions = [
{ name: i18nService.t('sendTypeFile'), value: SendType.File },
{ name: i18nService.t('sendTypeText'), value: SendType.Text },
];
}
async ngOnInit() {
await this.load();
}
async load() {
this.editMode = this.sendId != null;
if (this.editMode) {
this.editMode = true;
this.title = this.i18nService.t('editSend');
} else {
this.title = this.i18nService.t('createSend');
}
if (this.send == null) {
if (this.editMode) {
const send = await this.loadSend();
this.send = await send.decrypt();
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
}
}
this.hasPassword = this.send.password != null && this.send.password.trim() !== '';
// Parse dates
this.deletionDate = this.send.deletionDate == null ? null :
this.datePipe.transform(this.send.deletionDate, 'yyyy-MM-ddTHH:mm');
this.expirationDate = this.send.expirationDate == null ? null :
this.datePipe.transform(this.send.expirationDate, 'yyyy-MM-ddTHH:mm');
if (this.editMode) {
let webVaultUrl = this.environmentService.getWebVaultUrl();
if (webVaultUrl == null) {
webVaultUrl = 'https://vault.bitwarden.com';
}
this.link = webVaultUrl + '/#/send/' + this.send.accessId + '/' + this.send.urlB64Key;
}
}
async submit(): Promise<boolean> {
if (this.send.name == null || this.send.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired'));
return false;
}
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById('file') as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('selectFile'));
return;
}
file = files[0];
if (file.size > 104857600) { // 100 MB
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('maxFileSize'));
return;
}
}
const encSend = await this.encryptSend(file);
try {
this.formPromise = this.saveSend(encSend);
await this.formPromise;
this.send.id = encSend[0].id;
this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.editMode ? 'editedSend' : 'createdSend'));
this.onSavedSend.emit(this.send);
return true;
} catch { }
return false;
}
clearExpiration() {
this.expirationDate = null;
}
async delete(): Promise<void> {
if (this.deletePromise != null) {
return;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('deleteSendConfirmation'),
this.i18nService.t('deleteSend'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return;
}
try {
this.deletePromise = this.apiService.deleteSend(this.send.id);
await this.deletePromise;
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedSend'));
await this.load();
this.onDeletedSend.emit(this.send);
} catch { }
}
protected async loadSend(): Promise<Send> {
const response = await this.apiService.getSend(this.sendId);
const data = new SendData(response);
return new Send(data);
}
protected async encryptSend(file: File): Promise<[Send, ArrayBuffer]> {
let fileData: ArrayBuffer = null;
const send = new Send();
send.id = this.send.id;
send.type = this.send.type;
send.disabled = this.send.disabled;
send.maxAccessCount = this.send.maxAccessCount;
if (this.send.key == null) {
this.send.key = await this.cryptoFunctionService.randomBytes(16);
this.send.cryptoKey = await this.cryptoService.makeSendKey(this.send.key);
}
if (this.password != null) {
const passwordHash = await this.cryptoFunctionService.pbkdf2(this.password,
this.send.key, 'sha256', 100000);
send.password = Utils.fromBufferToB64(passwordHash);
}
send.key = await this.cryptoService.encrypt(this.send.key, null);
send.name = await this.cryptoService.encrypt(this.send.name, this.send.cryptoKey);
send.notes = await this.cryptoService.encrypt(this.send.notes, this.send.cryptoKey);
if (send.type === SendType.Text) {
send.text = new SendText();
send.text.text = await this.cryptoService.encrypt(this.send.text.text, this.send.cryptoKey);
send.text.hidden = this.send.text.hidden;
} else if (send.type === SendType.File) {
send.file = new SendFile();
if (file != null) {
fileData = await this.parseFile(send, file);
}
}
// Parse dates
try {
send.deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
} catch {
send.deletionDate = null;
}
try {
send.expirationDate = this.expirationDate == null ? null : new Date(this.expirationDate);
} catch {
send.expirationDate = null;
}
return [send, fileData];
}
protected async saveSend(sendData: [Send, ArrayBuffer]) {
const request = new SendRequest(sendData[0]);
if (sendData[0].id == null) {
if (sendData[0].type === SendType.Text) {
await this.apiService.postSend(request);
} else {
const fd = new FormData();
try {
const blob = new Blob([sendData[1]], { 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]) as any, {
filepath: sendData[0].file.fileName.encryptedString,
contentType: 'application/octet-stream',
} as any);
} else {
throw e;
}
}
await this.apiService.postSendFile(fd);
}
} else {
await this.apiService.putSend(sendData[0].id, request);
}
}
private parseFile(send: Send, file: File): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async (evt) => {
try {
send.file.fileName = await this.cryptoService.encrypt(file.name, this.send.cryptoKey);
const fileData = await this.cryptoService.encryptToBytes(evt.target.result as ArrayBuffer,
this.send.cryptoKey);
resolve(fileData);
} catch (e) {
reject(e);
}
};
reader.onerror = (evt) => {
reject('Error reading file.');
};
});
}
}