diff --git a/src/background/contextMenus.background.ts b/src/background/contextMenus.background.ts index b55e0454aa..d714744681 100644 --- a/src/background/contextMenus.background.ts +++ b/src/background/contextMenus.background.ts @@ -2,9 +2,8 @@ import BrowserApi from '../browser/browserApi'; import MainBackground from './main.background'; -import CipherService from '../services/cipher.service'; - import { + CipherService, PasswordGenerationService, } from 'jslib/abstractions'; diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 39033718b7..863f4e2f37 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -3,6 +3,7 @@ import { CipherType } from 'jslib/enums'; import { ApiService, AppIdService, + CipherService, ConstantsService, ContainerService, CryptoService, @@ -19,6 +20,7 @@ import { import { ApiService as ApiServiceAbstraction, AppIdService as AppIdServiceAbstraction, + CipherService as CipherServiceAbstraction, CryptoService as CryptoServiceAbstraction, EnvironmentService as EnvironmentServiceAbstraction, FolderService as FolderServiceAbstraction, @@ -47,7 +49,6 @@ import AutofillService from '../services/autofill.service'; import BrowserMessagingService from '../services/browserMessaging.service'; import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; import BrowserStorageService from '../services/browserStorage.service'; -import CipherService from '../services/cipher.service'; import CollectionService from '../services/collection.service'; import i18nService from '../services/i18n.service'; import LockService from '../services/lock.service'; @@ -67,7 +68,7 @@ export default class MainBackground { environmentService: EnvironmentServiceAbstraction; userService: UserServiceAbstraction; settingsService: SettingsServiceAbstraction; - cipherService: CipherService; + cipherService: CipherServiceAbstraction; folderService: FolderServiceAbstraction; collectionService: CollectionService; lockService: LockService; diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index fe723c8e39..59585b0a0b 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -3,6 +3,7 @@ import { CipherType } from 'jslib/enums'; import { UtilsService } from 'jslib/services/utils.service'; import { + CipherService, PlatformUtilsService, } from 'jslib/abstractions'; @@ -11,7 +12,6 @@ import BrowserApi from '../browser/browserApi'; import MainBackground from './main.background'; import AutofillService from '../services/autofill.service'; -import CipherService from '../services/cipher.service'; export default class RuntimeBackground { private runtime: any; diff --git a/src/background/webRequest.background.ts b/src/background/webRequest.background.ts index dda6a1b000..326c964d4f 100644 --- a/src/background/webRequest.background.ts +++ b/src/background/webRequest.background.ts @@ -1,6 +1,7 @@ -import CipherService from '../services/cipher.service'; - -import { PlatformUtilsService } from 'jslib/abstractions'; +import { + CipherService, + PlatformUtilsService, +} from 'jslib/abstractions'; export default class WebRequestBackground { private pendingAuthRequests: any[] = []; diff --git a/src/popup/app/services/background.service.ts b/src/popup/app/services/background.service.ts index b9b730ad2a..ce3fc0e59b 100644 --- a/src/popup/app/services/background.service.ts +++ b/src/popup/app/services/background.service.ts @@ -2,6 +2,7 @@ import { ConstantsService } from 'jslib/services/constants.service'; import { ApiService } from 'jslib/abstractions/api.service'; import { AppIdService } from 'jslib/abstractions/appId.service'; +import { CipherService } from 'jslib/abstractions/cipher.service'; import { CryptoService } from 'jslib/abstractions/crypto.service'; import { EnvironmentService } from 'jslib/abstractions/environment.service'; import { FolderService } from 'jslib/abstractions/folder.service'; @@ -26,7 +27,7 @@ export const cryptoService = getBackgroundService('cryptoService' export const userService = getBackgroundService('userService'); export const apiService = getBackgroundService('apiService'); export const folderService = getBackgroundService('folderService'); -export const cipherService = getBackgroundService('cipherService'); +export const cipherService = getBackgroundService('cipherService'); export const syncService = getBackgroundService('syncService'); export const autofillService = getBackgroundService('autofillService'); export const passwordGenerationService = getBackgroundService('passwordGenerationService'); diff --git a/src/services/autofill.service.ts b/src/services/autofill.service.ts index 0dfea5d9a7..8eda332922 100644 --- a/src/services/autofill.service.ts +++ b/src/services/autofill.service.ts @@ -7,11 +7,10 @@ import AutofillField from '../models/domain/autofillField'; import AutofillPageDetails from '../models/domain/autofillPageDetails'; import AutofillScript from '../models/domain/autofillScript'; -import CipherService from './cipher.service'; - import { UtilsService } from 'jslib/services'; import { + CipherService, PlatformUtilsService, TokenService, TotpService, diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts deleted file mode 100644 index 441db77bd0..0000000000 --- a/src/services/cipher.service.ts +++ /dev/null @@ -1,523 +0,0 @@ -import { CipherType } from 'jslib/enums'; - -import { CipherData } from 'jslib/models/data'; - -import { - Cipher, - CipherString, - Field, - SymmetricCryptoKey, -} from 'jslib/models/domain'; - -import { CipherRequest } from 'jslib/models/request'; - -import { - CipherResponse, - ErrorResponse, -} from 'jslib/models/response'; - -import { ConstantsService } from 'jslib/services'; - -import { - ApiService, - CryptoService, - SettingsService, - StorageService, - UserService, -} from 'jslib/abstractions'; - -const Keys = { - ciphersPrefix: 'ciphers_', - localData: 'sitesLocalData', - neverDomains: 'neverDomains', -}; - -export default class CipherService { - static sortCiphersByLastUsed(a: any, b: any): number { - const aLastUsed = a.localData && a.localData.lastUsedDate ? a.localData.lastUsedDate as number : null; - const bLastUsed = b.localData && b.localData.lastUsedDate ? b.localData.lastUsedDate as number : null; - - if (aLastUsed != null && bLastUsed != null && aLastUsed < bLastUsed) { - return 1; - } - if (aLastUsed != null && bLastUsed == null) { - return -1; - } - - if (bLastUsed != null && aLastUsed != null && aLastUsed > bLastUsed) { - return -1; - } - if (bLastUsed != null && aLastUsed == null) { - return 1; - } - - return 0; - } - - static sortCiphersByLastUsedThenName(a: any, b: any): number { - const result = CipherService.sortCiphersByLastUsed(a, b); - if (result !== 0) { - return result; - } - - const nameA = (a.name + '_' + a.username).toUpperCase(); - const nameB = (b.name + '_' + b.username).toUpperCase(); - - if (nameA < nameB) { - return -1; - } - if (nameA > nameB) { - return 1; - } - - return 0; - } - - decryptedCipherCache: any[]; - - constructor(private cryptoService: CryptoService, private userService: UserService, - private settingsService: SettingsService, private apiService: ApiService, - private storageService: StorageService) { - } - - clearCache(): void { - this.decryptedCipherCache = null; - } - - async encrypt(model: any): Promise { - const cipher = new Cipher(); - cipher.id = model.id; - cipher.folderId = model.folderId; - cipher.favorite = model.favorite; - cipher.organizationId = model.organizationId; - cipher.type = model.type; - cipher.collectionIds = model.collectionIds; - - const key = await this.cryptoService.getOrgKey(cipher.organizationId); - await Promise.all([ - this.encryptObjProperty(model, cipher, { - name: null, - notes: null, - }, key), - this.encryptCipherData(model, cipher, key), - this.encryptFields(model.fields, key).then((fields) => { - cipher.fields = fields; - }), - ]); - - return cipher; - } - - async encryptFields(fieldsModel: any[], key: SymmetricCryptoKey): Promise { - if (!fieldsModel || !fieldsModel.length) { - return null; - } - - const self = this; - const encFields: Field[] = []; - await fieldsModel.reduce((promise, field) => { - return promise.then(() => { - return self.encryptField(field, key); - }).then((encField: Field) => { - encFields.push(encField); - }); - }, Promise.resolve()); - - return encFields; - } - - async encryptField(fieldModel: any, key: SymmetricCryptoKey): Promise { - const field = new Field(); - field.type = fieldModel.type; - - await this.encryptObjProperty(fieldModel, field, { - name: null, - value: null, - }, key); - - return field; - } - - async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const localData = await this.storageService.get(Keys.localData); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); - if (ciphers == null || !ciphers.hasOwnProperty(id)) { - return null; - } - - return new Cipher(ciphers[id], false, localData ? localData[id] : null); - } - - async getAll(): Promise { - const userId = await this.userService.getUserId(); - const localData = await this.storageService.get(Keys.localData); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); - const response: Cipher[] = []; - for (const id in ciphers) { - if (ciphers.hasOwnProperty(id)) { - response.push(new Cipher(ciphers[id], false, localData ? localData[id] : null)); - } - } - return response; - } - - async getAllDecrypted(): Promise { - if (this.decryptedCipherCache != null) { - return this.decryptedCipherCache; - } - - const decCiphers: any[] = []; - const key = await this.cryptoService.getKey(); - if (key == null) { - throw new Error('No key.'); - } - - const promises: any[] = []; - const ciphers = await this.getAll(); - ciphers.forEach((cipher) => { - promises.push(cipher.decrypt().then((c: any) => { - decCiphers.push(c); - })); - }); - - await Promise.all(promises); - this.decryptedCipherCache = decCiphers; - return this.decryptedCipherCache; - } - - async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise { - const ciphers = await this.getAllDecrypted(); - const ciphersToReturn: any[] = []; - - ciphers.forEach((cipher) => { - if (folder && cipher.folderId === groupingId) { - ciphersToReturn.push(cipher); - } else if (!folder && cipher.collectionIds != null && cipher.collectionIds.indexOf(groupingId) > -1) { - ciphersToReturn.push(cipher); - } - }); - - return ciphersToReturn; - } - - async getAllDecryptedForDomain(domain: string, includeOtherTypes?: any[]): Promise { - if (domain == null && !includeOtherTypes) { - return Promise.resolve([]); - } - - const eqDomainsPromise = domain == null ? Promise.resolve([]) : - this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => { - let matches: any[] = []; - eqDomains.forEach((eqDomain) => { - if (eqDomain.length && eqDomain.indexOf(domain) >= 0) { - matches = matches.concat(eqDomain); - } - }); - - if (!matches.length) { - matches.push(domain); - } - - return matches; - }); - - const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); - const matchingDomains = result[0]; - const ciphers = result[1]; - const ciphersToReturn: any[] = []; - - ciphers.forEach((cipher) => { - if (domain && cipher.type === CipherType.Login && cipher.login.domain && - matchingDomains.indexOf(cipher.login.domain) > -1) { - ciphersToReturn.push(cipher); - } else if (includeOtherTypes && includeOtherTypes.indexOf(cipher.type) > -1) { - ciphersToReturn.push(cipher); - } - }); - - return ciphersToReturn; - } - - async getLastUsedForDomain(domain: string): Promise { - const ciphers = await this.getAllDecryptedForDomain(domain); - if (ciphers.length === 0) { - return null; - } - - const sortedCiphers = ciphers.sort(CipherService.sortCiphersByLastUsed); - return sortedCiphers[0]; - } - - async updateLastUsedDate(id: string): Promise { - let ciphersLocalData = await this.storageService.get(Keys.localData); - if (!ciphersLocalData) { - ciphersLocalData = {}; - } - - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastUsedDate = new Date().getTime(); - } else { - ciphersLocalData[id] = { - lastUsedDate: new Date().getTime(), - }; - } - - await this.storageService.save(Keys.localData, ciphersLocalData); - - if (this.decryptedCipherCache == null) { - return; - } - - for (let i = 0; i < this.decryptedCipherCache.length; i++) { - const cached = this.decryptedCipherCache[i]; - if (cached.id === id) { - cached.localData = ciphersLocalData[id]; - break; - } - } - } - - async saveNeverDomain(domain: string): Promise { - if (domain == null) { - return; - } - - let domains = await this.storageService.get<{ [id: string]: any; }>(Keys.neverDomains); - if (!domains) { - domains = {}; - } - domains[domain] = null; - await this.storageService.save(Keys.neverDomains, domains); - } - - async saveWithServer(cipher: Cipher): Promise { - const request = new CipherRequest(cipher); - - let response: CipherResponse; - if (cipher.id == null) { - response = await this.apiService.postCipher(request); - cipher.id = response.id; - } else { - response = await this.apiService.putCipher(cipher.id, request); - } - - const userId = await this.userService.getUserId(); - const data = new CipherData(response, userId, cipher.collectionIds); - await this.upsert(data); - } - - saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any): Promise { - const self = this; - - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsArrayBuffer(unencryptedFile); - - reader.onload = async (evt: any) => { - const key = await self.cryptoService.getOrgKey(cipher.organizationId); - const encFileName = await self.cryptoService.encrypt(unencryptedFile.name, key); - const encData = await self.cryptoService.encryptToBytes(evt.target.result, key); - - const fd = new FormData(); - const blob = new Blob([encData], { type: 'application/octet-stream' }); - fd.append('data', blob, encFileName.encryptedString); - - let response: CipherResponse; - try { - response = await self.apiService.postCipherAttachment(cipher.id, fd); - } catch (e) { - reject((e as ErrorResponse).getSingleMessage()); - return; - } - - const userId = await self.userService.getUserId(); - const data = new CipherData(response, userId, cipher.collectionIds); - this.upsert(data); - resolve(new Cipher(data)); - - }; - - reader.onerror = (evt) => { - reject('Error reading file.'); - }; - }); - } - - async upsert(cipher: CipherData | CipherData[]): Promise { - const userId = await this.userService.getUserId(); - let ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); - if (ciphers == null) { - ciphers = {}; - } - - if (cipher instanceof CipherData) { - const c = cipher as CipherData; - ciphers[c.id] = c; - } else { - (cipher as CipherData[]).forEach((c) => { - ciphers[c.id] = c; - }); - } - - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async replace(ciphers: { [id: string]: CipherData; }): Promise { - const userId = await this.userService.getUserId(); - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async clear(userId: string): Promise { - await this.storageService.remove(Keys.ciphersPrefix + userId); - this.decryptedCipherCache = null; - } - - async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); - if (ciphers == null) { - return; - } - - if (typeof id === 'string') { - const i = id as string; - delete ciphers[id]; - } else { - (id as string[]).forEach((i) => { - delete ciphers[i]; - }); - } - - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async deleteWithServer(id: string): Promise { - await this.apiService.deleteCipher(id); - await this.delete(id); - } - - async deleteAttachment(id: string, attachmentId: string): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await this.storageService.get<{ [id: string]: CipherData; }>( - Keys.ciphersPrefix + userId); - - if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { - return; - } - - for (let i = 0; i < ciphers[id].attachments.length; i++) { - if (ciphers[id].attachments[i].id === attachmentId) { - ciphers[id].attachments.splice(i, 1); - } - } - - await this.storageService.save(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { - try { - await this.apiService.deleteCipherAttachment(id, attachmentId); - } catch (e) { - return Promise.reject((e as ErrorResponse).getSingleMessage()); - } - await this.deleteAttachment(id, attachmentId); - } - - sortCiphersByLastUsed(a: any, b: any): number { - return CipherService.sortCiphersByLastUsed(a, b); - } - - sortCiphersByLastUsedThenName(a: any, b: any): number { - return CipherService.sortCiphersByLastUsedThenName(a, b); - } - - // Helpers - - private encryptObjProperty(model: any, obj: any, map: any, key: SymmetricCryptoKey): Promise { - const promises = []; - const self = this; - - for (const prop in map) { - if (!map.hasOwnProperty(prop)) { - continue; - } - - // tslint:disable-next-line - (function (theProp, theObj) { - const p = Promise.resolve().then(() => { - const modelProp = model[(map[theProp] || theProp)]; - if (modelProp && modelProp !== '') { - return self.cryptoService.encrypt(modelProp, key); - } - return null; - }).then((val: CipherString) => { - theObj[theProp] = val; - }); - promises.push(p); - })(prop, obj); - } - - return Promise.all(promises); - } - - private encryptCipherData(cipher: Cipher, model: any, key: SymmetricCryptoKey): Promise { - switch (cipher.type) { - case CipherType.Login: - model.login = {}; - return this.encryptObjProperty(cipher.login, model.login, { - uri: null, - username: null, - password: null, - totp: null, - }, key); - case CipherType.SecureNote: - model.secureNote = { - type: cipher.secureNote.type, - }; - return Promise.resolve(); - case CipherType.Card: - model.card = {}; - return this.encryptObjProperty(cipher.card, model.card, { - cardholderName: null, - brand: null, - number: null, - expMonth: null, - expYear: null, - code: null, - }, key); - case CipherType.Identity: - model.identity = {}; - return this.encryptObjProperty(cipher.identity, model.identity, { - title: null, - firstName: null, - middleName: null, - lastName: null, - address1: null, - address2: null, - address3: null, - city: null, - state: null, - postalCode: null, - country: null, - company: null, - email: null, - phone: null, - ssn: null, - username: null, - passportNumber: null, - licenseNumber: null, - }, key); - default: - throw new Error('Unknown cipher type.'); - } - } -} diff --git a/src/services/lock.service.ts b/src/services/lock.service.ts index 311a43e8f3..a7487f1ec9 100644 --- a/src/services/lock.service.ts +++ b/src/services/lock.service.ts @@ -1,9 +1,11 @@ -import CipherService from './cipher.service'; import CollectionService from './collection.service'; -import { ConstantsService } from 'jslib/services'; +import { + ConstantsService, +} from 'jslib/services'; import { + CipherService, CryptoService, FolderService, PlatformUtilsService, diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts index 72876aa2cd..e533cd2a15 100644 --- a/src/services/sync.service.ts +++ b/src/services/sync.service.ts @@ -1,8 +1,8 @@ -import CipherService from './cipher.service'; import CollectionService from './collection.service'; import { ApiService, + CipherService, CryptoService, FolderService, MessagingService,