diff --git a/src/index.ts b/src/index.ts index 2edf7e05e4..da1f6af9e0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,5 +5,6 @@ import * as Domain from './models/domain'; import * as Request from './models/request'; import * as Response from './models/response'; import * as Services from './services'; +import * as View from './models/view'; -export { Abstractions, Enums, Data, Domain, Request, Response, Services }; +export { Abstractions, Enums, Data, Domain, Request, Response, Services, View }; diff --git a/src/models/domain/attachment.ts b/src/models/domain/attachment.ts index 28859be366..7861066e0e 100644 --- a/src/models/domain/attachment.ts +++ b/src/models/domain/attachment.ts @@ -3,6 +3,8 @@ import { AttachmentData } from '../data/attachmentData'; import { CipherString } from './cipherString'; import Domain from './domain'; +import { AttachmentView } from '../view/attachmentView'; + export class Attachment extends Domain { id: string; url: string; @@ -25,15 +27,8 @@ export class Attachment extends Domain { }, alreadyEncrypted, ['id', 'url', 'sizeName']); } - decrypt(orgId: string): Promise { - const model = { - id: this.id, - size: this.size, - sizeName: this.sizeName, - url: this.url, - }; - - return this.decryptObj(model, { + decrypt(orgId: string): Promise { + return this.decryptObj(new AttachmentView(this), { fileName: null, }, orgId); } diff --git a/src/models/domain/card.ts b/src/models/domain/card.ts index ee6fbd0616..19a2ad9e50 100644 --- a/src/models/domain/card.ts +++ b/src/models/domain/card.ts @@ -3,6 +3,8 @@ import { CardData } from '../data/cardData'; import { CipherString } from './cipherString'; import Domain from './domain'; +import { CardView } from '../view/cardView'; + export class Card extends Domain { cardholderName: CipherString; brand: CipherString; @@ -27,8 +29,8 @@ export class Card extends Domain { }, alreadyEncrypted, []); } - decrypt(orgId: string): Promise { - return this.decryptObj({}, { + decrypt(orgId: string): Promise { + return this.decryptObj(new CardView(this), { cardholderName: null, brand: null, number: null, diff --git a/src/models/domain/cipher.ts b/src/models/domain/cipher.ts index 9dfbafdac0..7f9c18d1f0 100644 --- a/src/models/domain/cipher.ts +++ b/src/models/domain/cipher.ts @@ -1,9 +1,9 @@ import { CipherType } from '../../enums/cipherType'; -import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; - import { CipherData } from '../data/cipherData'; +import { CipherView } from '../view/cipherView'; + import { Attachment } from './attachment'; import { Card } from './card'; import { CipherString } from './cipherString'; @@ -89,23 +89,8 @@ export class Cipher extends Domain { } } - async decrypt(): Promise { - const model = { - id: this.id, - organizationId: this.organizationId, - folderId: this.folderId, - favorite: this.favorite, - type: this.type, - localData: this.localData, - login: null as any, - card: null as any, - identity: null as any, - secureNote: null as any, - subTitle: null as string, - attachments: null as any[], - fields: null as any[], - collectionIds: this.collectionIds, - }; + async decrypt(): Promise { + const model = new CipherView(this); await this.decryptObj(model, { name: null, @@ -115,44 +100,15 @@ export class Cipher extends Domain { switch (this.type) { case CipherType.Login: model.login = await this.login.decrypt(this.organizationId); - model.subTitle = model.login.username; - if (model.login.uri) { - const containerService = (window as any).bitwardenContainerService; - if (containerService) { - const platformUtilsService: PlatformUtilsService = - containerService.getPlatformUtilsService(); - model.login.domain = platformUtilsService.getDomain(model.login.uri); - } else { - throw new Error('window.bitwardenContainerService not initialized.'); - } - } break; case CipherType.SecureNote: model.secureNote = await this.secureNote.decrypt(this.organizationId); - model.subTitle = null; break; case CipherType.Card: model.card = await this.card.decrypt(this.organizationId); - model.subTitle = model.card.brand; - if (model.card.number && model.card.number.length >= 4) { - if (model.subTitle !== '') { - model.subTitle += ', '; - } - model.subTitle += ('*' + model.card.number.substr(model.card.number.length - 4)); - } break; case CipherType.Identity: model.identity = await this.identity.decrypt(this.organizationId); - model.subTitle = ''; - if (model.identity.firstName) { - model.subTitle = model.identity.firstName; - } - if (model.identity.lastName) { - if (model.subTitle !== '') { - model.subTitle += ' '; - } - model.subTitle += model.identity.lastName; - } break; default: break; diff --git a/src/models/domain/domain.ts b/src/models/domain/domain.ts index cdb220776b..7fe644740f 100644 --- a/src/models/domain/domain.ts +++ b/src/models/domain/domain.ts @@ -1,22 +1,25 @@ import { CipherString } from '../domain/cipherString'; +import { View } from '../view/view'; + export default abstract class Domain { - protected buildDomainModel(model: any, obj: any, map: any, alreadyEncrypted: boolean, notEncList: any[] = []) { + protected buildDomainModel(domain: D, dataObj: any, map: any, + alreadyEncrypted: boolean, notEncList: any[] = []) { for (const prop in map) { if (!map.hasOwnProperty(prop)) { continue; } - const objProp = obj[(map[prop] || prop)]; + const objProp = dataObj[(map[prop] || prop)]; if (alreadyEncrypted === true || notEncList.indexOf(prop) > -1) { - model[prop] = objProp ? objProp : null; + (domain as any)[prop] = objProp ? objProp : null; } else { - model[prop] = objProp ? new CipherString(objProp) : null; + (domain as any)[prop] = objProp ? new CipherString(objProp) : null; } } } - protected async decryptObj(model: any, map: any, orgId: string) { + protected async decryptObj(viewModel: T, map: any, orgId: string): Promise { const promises = []; const self: any = this; @@ -34,13 +37,13 @@ export default abstract class Domain { } return null; }).then((val: any) => { - model[theProp] = val; + (viewModel as any)[theProp] = val; }); promises.push(p); })(prop); } await Promise.all(promises); - return model; + return viewModel; } } diff --git a/src/models/domain/field.ts b/src/models/domain/field.ts index d118ad2463..023fefe5a9 100644 --- a/src/models/domain/field.ts +++ b/src/models/domain/field.ts @@ -5,6 +5,8 @@ import { FieldData } from '../data/fieldData'; import { CipherString } from './cipherString'; import Domain from './domain'; +import { FieldView } from '../view/fieldView'; + export class Field extends Domain { name: CipherString; vault: CipherString; @@ -23,12 +25,8 @@ export class Field extends Domain { }, alreadyEncrypted, []); } - decrypt(orgId: string): Promise { - const model = { - type: this.type, - }; - - return this.decryptObj(model, { + decrypt(orgId: string): Promise { + return this.decryptObj(new FieldView(this), { name: null, value: null, }, orgId); diff --git a/src/models/domain/identity.ts b/src/models/domain/identity.ts index 2b8323e113..ade03260ea 100644 --- a/src/models/domain/identity.ts +++ b/src/models/domain/identity.ts @@ -3,6 +3,8 @@ import { IdentityData } from '../data/identityData'; import { CipherString } from './cipherString'; import Domain from './domain'; +import { IdentityView } from '../view/identityView'; + export class Identity extends Domain { title: CipherString; firstName: CipherString; @@ -51,8 +53,8 @@ export class Identity extends Domain { }, alreadyEncrypted, []); } - decrypt(orgId: string): Promise { - return this.decryptObj({}, { + decrypt(orgId: string): Promise { + return this.decryptObj(new IdentityView(this), { title: null, firstName: null, middleName: null, diff --git a/src/models/domain/login.ts b/src/models/domain/login.ts index 5d0f50c378..1c98dc7031 100644 --- a/src/models/domain/login.ts +++ b/src/models/domain/login.ts @@ -1,5 +1,7 @@ import { LoginData } from '../data/loginData'; +import { LoginView } from '../view/loginView'; + import { CipherString } from './cipherString'; import Domain from './domain'; @@ -23,8 +25,8 @@ export class Login extends Domain { }, alreadyEncrypted, []); } - decrypt(orgId: string): Promise { - return this.decryptObj({}, { + decrypt(orgId: string): Promise { + return this.decryptObj(new LoginView(this), { uri: null, username: null, password: null, diff --git a/src/models/domain/secureNote.ts b/src/models/domain/secureNote.ts index 8feb7de3ad..79724a4193 100644 --- a/src/models/domain/secureNote.ts +++ b/src/models/domain/secureNote.ts @@ -4,6 +4,8 @@ import { SecureNoteData } from '../data/secureNoteData'; import Domain from './domain'; +import { SecureNoteView } from '../view/secureNoteView'; + export class SecureNote extends Domain { type: SecureNoteType; @@ -16,9 +18,7 @@ export class SecureNote extends Domain { this.type = obj.type; } - decrypt(orgId: string): any { - return { - type: this.type, - }; + decrypt(orgId: string): Promise { + return Promise.resolve(new SecureNoteView(this)); } } diff --git a/src/models/view/attachmentView.ts b/src/models/view/attachmentView.ts new file mode 100644 index 0000000000..49ea57326d --- /dev/null +++ b/src/models/view/attachmentView.ts @@ -0,0 +1,18 @@ +import { View } from './view'; + +import { Attachment } from '../domain/attachment'; + +export class AttachmentView implements View { + id: string; + url: string; + size: number; + sizeName: string; + fileName: string; + + constructor(a: Attachment) { + this.id = a.id; + this.url = a.url; + this.size = a.size; + this.sizeName = a.sizeName; + } +} diff --git a/src/models/view/cardView.ts b/src/models/view/cardView.ts new file mode 100644 index 0000000000..cb4b2380fe --- /dev/null +++ b/src/models/view/cardView.ts @@ -0,0 +1,16 @@ +import { View } from './view'; + +import { Card } from '../domain/card'; + +export class CardView implements View { + cardholderName: string; + brand: string; + number: string; + expMonth: string; + expYear: string; + code: string; + + constructor(c?: Card) { + // ctor + } +} diff --git a/src/models/view/cipherView.ts b/src/models/view/cipherView.ts new file mode 100644 index 0000000000..d111226913 --- /dev/null +++ b/src/models/view/cipherView.ts @@ -0,0 +1,80 @@ +import { CipherType } from '../../enums/cipherType'; + +import { Cipher } from '../domain/cipher'; + +import { AttachmentView } from './attachmentView'; +import { CardView } from './cardView'; +import { FieldView } from './fieldView'; +import { IdentityView } from './identityView'; +import { LoginView } from './loginView'; +import { SecureNoteView } from './secureNoteView'; +import { View } from './view'; + +export class CipherView implements View { + id: string; + organizationId: string; + folderId: string; + name: string; + notes: string; + type: CipherType; + favorite: boolean; + localData: any; + login: LoginView; + identity: IdentityView; + card: CardView; + secureNote: SecureNoteView; + attachments: AttachmentView[]; + fields: FieldView[]; + collectionIds: string[]; + + // tslint:disable-next-line + private _subTitle: string; + + constructor(c: Cipher) { + this.id = c.id; + this.organizationId = c.organizationId; + this.folderId = c.folderId; + this.favorite = c.favorite; + this.type = c.type; + this.localData = c.localData; + this.collectionIds = c.collectionIds; + } + + get subTitle(): string { + if (this._subTitle == null) { + switch (this.type) { + case CipherType.Login: + this._subTitle = this.login.username; + break; + case CipherType.SecureNote: + this._subTitle = null; + break; + case CipherType.Card: + this._subTitle = this.card.brand; + if (this.card.number != null && this.card.number.length >= 4) { + if (this._subTitle !== '') { + this._subTitle += ', '; + } + this._subTitle += ('*' + this.card.number.substr(this.card.number.length - 4)); + } + break; + case CipherType.Identity: + this._subTitle = ''; + if (this.identity.firstName != null) { + this._subTitle = this.identity.firstName; + } + if (this.identity.lastName != null) { + if (this._subTitle !== '') { + this._subTitle += ' '; + } + this._subTitle += this.identity.lastName; + } + break; + default: + break; + } + } + + return this._subTitle; + } +} diff --git a/src/models/view/fieldView.ts b/src/models/view/fieldView.ts new file mode 100644 index 0000000000..f25f86cc6e --- /dev/null +++ b/src/models/view/fieldView.ts @@ -0,0 +1,15 @@ +import { FieldType } from '../../enums/fieldType'; + +import { View } from './view'; + +import { Field } from '../domain/field'; + +export class FieldView implements View { + name: string; + vault: string; + type: FieldType; + + constructor(f: Field) { + this.type = f.type; + } +} diff --git a/src/models/view/identityView.ts b/src/models/view/identityView.ts new file mode 100644 index 0000000000..fb644c84a4 --- /dev/null +++ b/src/models/view/identityView.ts @@ -0,0 +1,28 @@ +import { View } from './view'; + +import { Identity } from '../domain/identity'; + +export class IdentityView implements View { + title: string; + firstName: string; + middleName: string; + lastName: string; + address1: string; + address2: string; + address3: string; + city: string; + state: string; + postalCode: string; + country: string; + company: string; + email: string; + phone: string; + ssn: string; + username: string; + passportNumber: string; + licenseNumber: string; + + constructor(i?: Identity) { + // ctor + } +} diff --git a/src/models/view/index.ts b/src/models/view/index.ts new file mode 100644 index 0000000000..0e07c256be --- /dev/null +++ b/src/models/view/index.ts @@ -0,0 +1,8 @@ +export { AttachmentView } from './attachmentView'; +export { CardView } from './cardView'; +export { CipherView } from './cipherView'; +export { FieldView } from './fieldView'; +export { IdentityView } from './identityView'; +export { LoginView } from './loginView'; +export { SecureNoteView } from './secureNoteView'; +export { View } from './view'; diff --git a/src/models/view/loginView.ts b/src/models/view/loginView.ts new file mode 100644 index 0000000000..c2e5270f05 --- /dev/null +++ b/src/models/view/loginView.ts @@ -0,0 +1,34 @@ +import { View } from './view'; + +import { Login } from '../domain/login'; + +import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; + +export class LoginView implements View { + uri: string; + username: string; + password: string; + maskedPassword: string; + totp: string; + + // tslint:disable-next-line + private _domain: string; + + constructor(l?: Login) { + // ctor + } + + get domain(): string { + if (this._domain == null && this.uri != null) { + const containerService = (window as any).bitwardenContainerService; + if (containerService) { + const platformUtilsService: PlatformUtilsService = containerService.getPlatformUtilsService(); + this._domain = platformUtilsService.getDomain(this.uri); + } else { + throw new Error('window.bitwardenContainerService not initialized.'); + } + } + + return this._domain; + } +} diff --git a/src/models/view/secureNoteView.ts b/src/models/view/secureNoteView.ts new file mode 100644 index 0000000000..b4eae6d446 --- /dev/null +++ b/src/models/view/secureNoteView.ts @@ -0,0 +1,13 @@ +import { SecureNoteType } from '../../enums/secureNoteType'; + +import { View } from './view'; + +import { SecureNote } from '../domain/secureNote'; + +export class SecureNoteView implements View { + type: SecureNoteType; + + constructor(n: SecureNote) { + this.type = n.type; + } +} diff --git a/src/models/view/view.ts b/src/models/view/view.ts new file mode 100644 index 0000000000..c295888ef1 --- /dev/null +++ b/src/models/view/view.ts @@ -0,0 +1,2 @@ +export class View { +} diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index 9f6e8fba3e..bec5c30dd6 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -4,6 +4,7 @@ import { CipherData } from '../models/data/cipherData'; import { Cipher } from '../models/domain/cipher'; import { CipherString } from '../models/domain/cipherString'; +import Domain from '../models/domain/domain'; import { Field } from '../models/domain/field'; import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey'; @@ -12,6 +13,13 @@ import { CipherRequest } from '../models/request/cipherRequest'; import { CipherResponse } from '../models/response/cipherResponse'; import { ErrorResponse } from '../models/response/errorResponse'; +import { CardView } from '../models/view/cardView'; +import { CipherView } from '../models/view/cipherView'; +import { FieldView } from '../models/view/fieldView'; +import { IdentityView } from '../models/view/identityView'; +import { LoginView } from '../models/view/loginView'; +import { View } from '../models/view/view'; + import { ConstantsService } from './constants.service'; import { ApiService } from '../abstractions/api.service'; @@ -68,7 +76,7 @@ export class CipherService implements CipherServiceAbstraction { return 0; } - decryptedCipherCache: any[]; + decryptedCipherCache: CipherView[]; constructor(private cryptoService: CryptoService, private userService: UserService, private settingsService: SettingsService, private apiService: ApiService, @@ -79,7 +87,7 @@ export class CipherService implements CipherServiceAbstraction { this.decryptedCipherCache = null; } - async encrypt(model: any): Promise { + async encrypt(model: CipherView): Promise { const cipher = new Cipher(); cipher.id = model.id; cipher.folderId = model.folderId; @@ -94,7 +102,7 @@ export class CipherService implements CipherServiceAbstraction { name: null, notes: null, }, key), - this.encryptCipherData(model, cipher, key), + this.encryptCipherData(cipher, model, key), this.encryptFields(model.fields, key).then((fields) => { cipher.fields = fields; }), @@ -103,7 +111,7 @@ export class CipherService implements CipherServiceAbstraction { return cipher; } - async encryptFields(fieldsModel: any[], key: SymmetricCryptoKey): Promise { + async encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise { if (!fieldsModel || !fieldsModel.length) { return null; } @@ -121,7 +129,7 @@ export class CipherService implements CipherServiceAbstraction { return encFields; } - async encryptField(fieldModel: any, key: SymmetricCryptoKey): Promise { + async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise { const field = new Field(); field.type = fieldModel.type; @@ -159,12 +167,12 @@ export class CipherService implements CipherServiceAbstraction { return response; } - async getAllDecrypted(): Promise { + async getAllDecrypted(): Promise { if (this.decryptedCipherCache != null) { return this.decryptedCipherCache; } - const decCiphers: any[] = []; + const decCiphers: CipherView[] = []; const key = await this.cryptoService.getKey(); if (key == null) { throw new Error('No key.'); @@ -173,9 +181,7 @@ export class CipherService implements CipherServiceAbstraction { const promises: any[] = []; const ciphers = await this.getAll(); ciphers.forEach((cipher) => { - promises.push(cipher.decrypt().then((c: any) => { - decCiphers.push(c); - })); + promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); }); await Promise.all(promises); @@ -183,22 +189,21 @@ export class CipherService implements CipherServiceAbstraction { return this.decryptedCipherCache; } - async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise { + async getAllDecryptedForGrouping(groupingId: string, folder: boolean = true): Promise { const ciphers = await this.getAllDecrypted(); - const ciphersToReturn: any[] = []; - ciphers.forEach((cipher) => { + return ciphers.filter((cipher) => { if (folder && cipher.folderId === groupingId) { - ciphersToReturn.push(cipher); + return true; } else if (!folder && cipher.collectionIds != null && cipher.collectionIds.indexOf(groupingId) > -1) { - ciphersToReturn.push(cipher); + return true; } - }); - return ciphersToReturn; + return false; + }); } - async getAllDecryptedForDomain(domain: string, includeOtherTypes?: any[]): Promise { + async getAllDecryptedForDomain(domain: string, includeOtherTypes?: any[]): Promise { if (domain == null && !includeOtherTypes) { return Promise.resolve([]); } @@ -222,21 +227,20 @@ export class CipherService implements CipherServiceAbstraction { const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); const matchingDomains = result[0]; const ciphers = result[1]; - const ciphersToReturn: any[] = []; - ciphers.forEach((cipher) => { + return ciphers.filter((cipher) => { if (domain && cipher.type === CipherType.Login && cipher.login.domain && matchingDomains.indexOf(cipher.login.domain) > -1) { - ciphersToReturn.push(cipher); + return true; } else if (includeOtherTypes && includeOtherTypes.indexOf(cipher.type) > -1) { - ciphersToReturn.push(cipher); + return true; } - }); - return ciphersToReturn; + return false; + }); } - async getLastUsedForDomain(domain: string): Promise { + async getLastUsedForDomain(domain: string): Promise { const ciphers = await this.getAllDecryptedForDomain(domain); if (ciphers.length === 0) { return null; @@ -437,7 +441,8 @@ export class CipherService implements CipherServiceAbstraction { // Helpers - private encryptObjProperty(model: any, obj: any, map: any, key: SymmetricCryptoKey): Promise { + private async encryptObjProperty(model: V, obj: D, + map: any, key: SymmetricCryptoKey): Promise { const promises = []; const self = this; @@ -449,39 +454,40 @@ export class CipherService implements CipherServiceAbstraction { // tslint:disable-next-line (function (theProp, theObj) { const p = Promise.resolve().then(() => { - const modelProp = model[(map[theProp] || theProp)]; + const modelProp = (model as any)[(map[theProp] || theProp)]; if (modelProp && modelProp !== '') { return self.cryptoService.encrypt(modelProp, key); } return null; }).then((val: CipherString) => { - theObj[theProp] = val; + (theObj as any)[theProp] = val; }); promises.push(p); })(prop, obj); } - return Promise.all(promises); + await Promise.all(promises); } - private encryptCipherData(cipher: Cipher, model: any, key: SymmetricCryptoKey): Promise { + private async encryptCipherData(cipher: Cipher, model: CipherView, key: SymmetricCryptoKey) { switch (cipher.type) { case CipherType.Login: - model.login = {}; - return this.encryptObjProperty(cipher.login, model.login, { + model.login = new LoginView(); + await this.encryptObjProperty(model.login, cipher.login, { uri: null, username: null, password: null, totp: null, }, key); + return; case CipherType.SecureNote: model.secureNote = { type: cipher.secureNote.type, }; - return Promise.resolve(); + return; case CipherType.Card: - model.card = {}; - return this.encryptObjProperty(cipher.card, model.card, { + model.card = new CardView(); + await this.encryptObjProperty(model.card, cipher.card, { cardholderName: null, brand: null, number: null, @@ -489,9 +495,10 @@ export class CipherService implements CipherServiceAbstraction { expYear: null, code: null, }, key); + return; case CipherType.Identity: - model.identity = {}; - return this.encryptObjProperty(cipher.identity, model.identity, { + model.identity = new IdentityView(); + await this.encryptObjProperty(model.identity, cipher.identity, { title: null, firstName: null, middleName: null, @@ -511,6 +518,7 @@ export class CipherService implements CipherServiceAbstraction { passportNumber: null, licenseNumber: null, }, key); + return; default: throw new Error('Unknown cipher type.'); }