mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-27 12:36:14 +01:00
send service and syncing send data (#205)
* send service and syncing send data * Update send.service.ts
This commit is contained in:
parent
85faee21ee
commit
6563dccf3b
22
src/abstractions/send.service.ts
Normal file
22
src/abstractions/send.service.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { SendData } from '../models/data/sendData';
|
||||
|
||||
import { Send } from '../models/domain/send';
|
||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
import { SendView } from '../models/view/sendView';
|
||||
|
||||
export abstract class SendService {
|
||||
decryptedSendCache: SendView[];
|
||||
|
||||
clearCache: () => void;
|
||||
encrypt: (model: SendView, file: File, password: string, key?: SymmetricCryptoKey) => Promise<[Send, ArrayBuffer]>;
|
||||
get: (id: string) => Promise<Send>;
|
||||
getAll: () => Promise<Send[]>;
|
||||
getAllDecrypted: () => Promise<SendView[]>;
|
||||
saveWithServer: (sendData: [Send, ArrayBuffer]) => Promise<any>;
|
||||
upsert: (send: SendData | SendData[]) => Promise<any>;
|
||||
replace: (sends: { [id: string]: SendData; }) => Promise<any>;
|
||||
clear: (userId: string) => Promise<any>;
|
||||
delete: (id: string | string[]) => Promise<any>;
|
||||
deleteWithServer: (id: string) => Promise<any>;
|
||||
}
|
@ -5,6 +5,7 @@ import { DomainsResponse } from './domainsResponse';
|
||||
import { FolderResponse } from './folderResponse';
|
||||
import { PolicyResponse } from './policyResponse';
|
||||
import { ProfileResponse } from './profileResponse';
|
||||
import { SendResponse } from './sendResponse';
|
||||
|
||||
export class SyncResponse extends BaseResponse {
|
||||
profile?: ProfileResponse;
|
||||
@ -13,6 +14,7 @@ export class SyncResponse extends BaseResponse {
|
||||
ciphers: CipherResponse[] = [];
|
||||
domains?: DomainsResponse;
|
||||
policies?: PolicyResponse[] = [];
|
||||
sends: SendResponse[] = [];
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@ -46,5 +48,10 @@ export class SyncResponse extends BaseResponse {
|
||||
if (policies != null) {
|
||||
this.policies = policies.map((p: any) => new PolicyResponse(p));
|
||||
}
|
||||
|
||||
const sends = this.getResponseProperty('Sends');
|
||||
if (sends != null) {
|
||||
this.sends = sends.map((s: any) => new SendResponse(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
236
src/services/send.service.ts
Normal file
236
src/services/send.service.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import { SendData } from '../models/data/sendData';
|
||||
|
||||
import { SendRequest } from '../models/request/sendRequest';
|
||||
|
||||
import { SendResponse } from '../models/response/sendResponse';
|
||||
|
||||
import { Send } from '../models/domain/send';
|
||||
import { SendFile } from '../models/domain/sendFile';
|
||||
import { SendText } from '../models/domain/sendText';
|
||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
import { SendType } from '../enums/sendType';
|
||||
|
||||
import { SendView } from '../models/view/sendView';
|
||||
|
||||
import { ApiService } from '../abstractions/api.service';
|
||||
import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
|
||||
import { I18nService } from '../abstractions/i18n.service';
|
||||
import { SendService as SendServiceAbstraction } from '../abstractions/send.service';
|
||||
import { StorageService } from '../abstractions/storage.service';
|
||||
import { UserService } from '../abstractions/user.service';
|
||||
|
||||
import { Utils } from '../misc/utils';
|
||||
|
||||
const Keys = {
|
||||
sendsPrefix: 'sends_',
|
||||
};
|
||||
|
||||
export class SendService implements SendServiceAbstraction {
|
||||
decryptedSendCache: SendView[];
|
||||
|
||||
constructor(private cryptoService: CryptoService, private userService: UserService,
|
||||
private apiService: ApiService, private storageService: StorageService,
|
||||
private i18nService: I18nService, private cryptoFunctionService: CryptoFunctionService) { }
|
||||
|
||||
clearCache(): void {
|
||||
this.decryptedSendCache = null;
|
||||
}
|
||||
|
||||
async encrypt(model: SendView, file: File, password: string,
|
||||
key?: SymmetricCryptoKey): Promise<[Send, ArrayBuffer]> {
|
||||
let fileData: ArrayBuffer = null;
|
||||
const send = new Send();
|
||||
send.id = model.id;
|
||||
send.type = model.type;
|
||||
send.disabled = model.disabled;
|
||||
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', 100000);
|
||||
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) {
|
||||
fileData = await this.parseFile(send, file, model.cryptoKey);
|
||||
}
|
||||
}
|
||||
|
||||
return [send, fileData];
|
||||
}
|
||||
|
||||
async get(id: string): Promise<Send> {
|
||||
const userId = await this.userService.getUserId();
|
||||
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
|
||||
Keys.sendsPrefix + userId);
|
||||
if (sends == null || !sends.hasOwnProperty(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Send(sends[id]);
|
||||
}
|
||||
|
||||
async getAll(): Promise<Send[]> {
|
||||
const userId = await this.userService.getUserId();
|
||||
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
|
||||
Keys.sendsPrefix + userId);
|
||||
const response: Send[] = [];
|
||||
for (const id in sends) {
|
||||
if (sends.hasOwnProperty(id)) {
|
||||
response.push(new Send(sends[id]));
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
async getAllDecrypted(): Promise<SendView[]> {
|
||||
if (this.decryptedSendCache != null) {
|
||||
return this.decryptedSendCache;
|
||||
}
|
||||
|
||||
const hasKey = await this.cryptoService.hasKey();
|
||||
if (!hasKey) {
|
||||
throw new Error('No key.');
|
||||
}
|
||||
|
||||
const decSends: SendView[] = [];
|
||||
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'));
|
||||
|
||||
this.decryptedSendCache = decSends;
|
||||
return this.decryptedSendCache;
|
||||
}
|
||||
|
||||
async saveWithServer(sendData: [Send, ArrayBuffer]): Promise<any> {
|
||||
const request = new SendRequest(sendData[0]);
|
||||
let response: SendResponse;
|
||||
if (sendData[0].id == null) {
|
||||
if (sendData[0].type === SendType.Text) {
|
||||
response = 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;
|
||||
}
|
||||
}
|
||||
response = await this.apiService.postSendFile(fd);
|
||||
}
|
||||
sendData[0].id = response.id;
|
||||
} else {
|
||||
response = await this.apiService.putSend(sendData[0].id, request);
|
||||
}
|
||||
|
||||
const userId = await this.userService.getUserId();
|
||||
const data = new SendData(response, userId);
|
||||
await this.upsert(data);
|
||||
|
||||
}
|
||||
|
||||
async upsert(send: SendData | SendData[]): Promise<any> {
|
||||
const userId = await this.userService.getUserId();
|
||||
let sends = await this.storageService.get<{ [id: string]: SendData; }>(
|
||||
Keys.sendsPrefix + userId);
|
||||
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.storageService.save(Keys.sendsPrefix + userId, sends);
|
||||
this.decryptedSendCache = null;
|
||||
}
|
||||
|
||||
async replace(sends: { [id: string]: SendData; }): Promise<any> {
|
||||
const userId = await this.userService.getUserId();
|
||||
await this.storageService.save(Keys.sendsPrefix + userId, sends);
|
||||
this.decryptedSendCache = null;
|
||||
}
|
||||
|
||||
async clear(userId: string): Promise<any> {
|
||||
await this.storageService.remove(Keys.sendsPrefix + userId);
|
||||
this.decryptedSendCache = null;
|
||||
}
|
||||
|
||||
async delete(id: string | string[]): Promise<any> {
|
||||
const userId = await this.userService.getUserId();
|
||||
const sends = await this.storageService.get<{ [id: string]: SendData; }>(
|
||||
Keys.sendsPrefix + userId);
|
||||
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.storageService.save(Keys.sendsPrefix + userId, sends);
|
||||
this.decryptedSendCache = null;
|
||||
}
|
||||
|
||||
async deleteWithServer(id: string): Promise<any> {
|
||||
await this.apiService.deleteSend(id);
|
||||
await this.delete(id);
|
||||
}
|
||||
|
||||
private parseFile(send: Send, file: File, key: SymmetricCryptoKey): 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, key);
|
||||
const fileData = await this.cryptoService.encryptToBytes(evt.target.result as ArrayBuffer, key);
|
||||
resolve(fileData);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
reader.onerror = (evt) => {
|
||||
reject('Error reading file.');
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { FolderService } from '../abstractions/folder.service';
|
||||
import { MessagingService } from '../abstractions/messaging.service';
|
||||
import { PolicyService } from '../abstractions/policy.service';
|
||||
import { SendService } from '../abstractions/send.service';
|
||||
import { SettingsService } from '../abstractions/settings.service';
|
||||
import { StorageService } from '../abstractions/storage.service';
|
||||
import { SyncService as SyncServiceAbstraction } from '../abstractions/sync.service';
|
||||
@ -15,6 +16,7 @@ import { CollectionData } from '../models/data/collectionData';
|
||||
import { FolderData } from '../models/data/folderData';
|
||||
import { OrganizationData } from '../models/data/organizationData';
|
||||
import { PolicyData } from '../models/data/policyData';
|
||||
import { SendData } from '../models/data/sendData';
|
||||
|
||||
import { CipherResponse } from '../models/response/cipherResponse';
|
||||
import { CollectionDetailsResponse } from '../models/response/collectionResponse';
|
||||
@ -26,6 +28,7 @@ import {
|
||||
} from '../models/response/notificationResponse';
|
||||
import { PolicyResponse } from '../models/response/policyResponse';
|
||||
import { ProfileResponse } from '../models/response/profileResponse';
|
||||
import { SendResponse } from '../models/response/sendResponse';
|
||||
|
||||
const Keys = {
|
||||
lastSyncPrefix: 'lastSync_',
|
||||
@ -39,7 +42,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
private cipherService: CipherService, private cryptoService: CryptoService,
|
||||
private collectionService: CollectionService, private storageService: StorageService,
|
||||
private messagingService: MessagingService, private policyService: PolicyService,
|
||||
private logoutCallback: (expired: boolean) => Promise<void>) {
|
||||
private sendService: SendService, private logoutCallback: (expired: boolean) => Promise<void>) {
|
||||
}
|
||||
|
||||
async getLastSync(): Promise<Date> {
|
||||
@ -94,6 +97,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
await this.syncFolders(userId, response.folders);
|
||||
await this.syncCollections(response.collections);
|
||||
await this.syncCiphers(userId, response.ciphers);
|
||||
await this.syncSends(userId, response.sends);
|
||||
await this.syncSettings(userId, response.domains);
|
||||
await this.syncPolicies(response.policies);
|
||||
|
||||
@ -287,6 +291,14 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
return await this.cipherService.replace(ciphers);
|
||||
}
|
||||
|
||||
private async syncSends(userId: string, response: SendResponse[]) {
|
||||
const sends: { [id: string]: SendData; } = {};
|
||||
response.forEach((s) => {
|
||||
sends[s.id] = new SendData(s, userId);
|
||||
});
|
||||
return await this.sendService.replace(sends);
|
||||
}
|
||||
|
||||
private async syncSettings(userId: string, response: DomainsResponse) {
|
||||
let eqDomains: string[][] = [];
|
||||
if (response != null && response.equivalentDomains != null) {
|
||||
|
Loading…
Reference in New Issue
Block a user