diff --git a/package.json b/package.json index 4fd9fdd4a12..8a192f1729d 100644 --- a/package.json +++ b/package.json @@ -1,30 +1,17 @@ { - "name": "bitwarden-jslib", + "name": "@bitwarden/jslib", "version": "0.0.1", "scripts": { }, + "main": "src/index.ts", "devDependencies": { - "clean-webpack-plugin": "^0.1.17", - "copy-webpack-plugin": "^4.2.0", - "css-loader": "^0.28.7", - "extract-text-webpack-plugin": "^3.0.1", - "file-loader": "^1.1.5", - "html-loader": "^0.5.1", - "html-webpack-plugin": "^2.30.1", - "style-loader": "^0.19.0", - "ts-loader": "^3.0.5", "tslint": "^5.8.0", - "tslint-loader": "^3.5.3", - "typescript": "^2.5.3", - "webpack": "^3.8.1", - "webpack-merge": "^4.1.0" + "typescript": "^2.6.2" }, "dependencies": { - "@types/node-forge": "0.6.10", - "@types/tldjs": "1.7.1", + "@types/node-forge": "0.7.1", "@types/webcrypto": "0.0.28", - "node-forge": "0.7.1", - "tldjs": "2.0.0" + "node-forge": "0.7.1" } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ + diff --git a/src/models/data/attachmentData.ts b/src/models/data/attachmentData.ts deleted file mode 100644 index 2ff3ab423c8..00000000000 --- a/src/models/data/attachmentData.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AttachmentResponse } from '../response/attachmentResponse'; - -class AttachmentData { - id: string; - url: string; - fileName: string; - size: number; - sizeName: string; - - constructor(response: AttachmentResponse) { - this.id = response.id; - this.url = response.url; - this.fileName = response.fileName; - this.size = response.size; - this.sizeName = response.sizeName; - } -} - -export { AttachmentData }; -(window as any).AttachmentData = AttachmentData; diff --git a/src/models/data/cardData.ts b/src/models/data/cardData.ts deleted file mode 100644 index f0b9f63f2ec..00000000000 --- a/src/models/data/cardData.ts +++ /dev/null @@ -1,20 +0,0 @@ -class CardData { - cardholderName: string; - brand: string; - number: string; - expMonth: string; - expYear: string; - code: string; - - constructor(data: any) { - this.cardholderName = data.CardholderName; - this.brand = data.Brand; - this.number = data.Number; - this.expMonth = data.ExpMonth; - this.expYear = data.ExpYear; - this.code = data.Code; - } -} - -export { CardData }; -(window as any).CardData = CardData; diff --git a/src/models/data/cipherData.ts b/src/models/data/cipherData.ts deleted file mode 100644 index 413612122f4..00000000000 --- a/src/models/data/cipherData.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { CipherType } from '../../enums/cipherType.enum'; - -import { AttachmentData } from './attachmentData'; -import { CardData } from './cardData'; -import { FieldData } from './fieldData'; -import { IdentityData } from './identityData'; -import { LoginData } from './loginData'; -import { SecureNoteData } from './secureNoteData'; - -import { CipherResponse } from '../response/cipherResponse'; - -class CipherData { - id: string; - organizationId: string; - folderId: string; - userId: string; - edit: boolean; - organizationUseTotp: boolean; - favorite: boolean; - revisionDate: string; - type: CipherType; - sizeName: string; - name: string; - notes: string; - login?: LoginData; - secureNote?: SecureNoteData; - card?: CardData; - identity?: IdentityData; - fields?: FieldData[]; - attachments?: AttachmentData[]; - collectionIds?: string[]; - - constructor(response: CipherResponse, userId: string, collectionIds?: string[]) { - this.id = response.id; - this.organizationId = response.organizationId; - this.folderId = response.folderId; - this.userId = userId; - this.edit = response.edit; - this.organizationUseTotp = response.organizationUseTotp; - this.favorite = response.favorite; - this.revisionDate = response.revisionDate; - this.type = response.type; - - if (collectionIds != null) { - this.collectionIds = collectionIds; - } else { - this.collectionIds = response.collectionIds; - } - - this.name = response.data.Name; - this.notes = response.data.Notes; - - switch (this.type) { - case CipherType.Login: - this.login = new LoginData(response.data); - break; - case CipherType.SecureNote: - this.secureNote = new SecureNoteData(response.data); - break; - case CipherType.Card: - this.card = new CardData(response.data); - break; - case CipherType.Identity: - this.identity = new IdentityData(response.data); - break; - default: - break; - } - - if (response.data.Fields != null) { - this.fields = []; - response.data.Fields.forEach((field: any) => { - this.fields.push(new FieldData(field)); - }); - } - - if (response.attachments != null) { - this.attachments = []; - response.attachments.forEach((attachment) => { - this.attachments.push(new AttachmentData(attachment)); - }); - } - } -} - -export { CipherData }; -(window as any).CipherData = CipherData; diff --git a/src/models/data/collectionData.ts b/src/models/data/collectionData.ts deleted file mode 100644 index f2d5fc9f03c..00000000000 --- a/src/models/data/collectionData.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CollectionResponse } from '../response/collectionResponse'; - -class CollectionData { - id: string; - organizationId: string; - name: string; - - constructor(response: CollectionResponse) { - this.id = response.id; - this.organizationId = response.organizationId; - this.name = response.name; - } -} - -export { CollectionData }; -(window as any).CollectionData = CollectionData; diff --git a/src/models/data/fieldData.ts b/src/models/data/fieldData.ts deleted file mode 100644 index 4914bb6ef95..00000000000 --- a/src/models/data/fieldData.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { FieldType } from '../../enums/fieldType.enum'; - -class FieldData { - type: FieldType; - name: string; - value: string; - - constructor(response: any) { - this.type = response.Type; - this.name = response.Name; - this.value = response.Value; - } -} - -export { FieldData }; -(window as any).FieldData = FieldData; diff --git a/src/models/data/folderData.ts b/src/models/data/folderData.ts deleted file mode 100644 index 6f03781cc70..00000000000 --- a/src/models/data/folderData.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FolderResponse } from '../response/folderResponse'; - -class FolderData { - id: string; - userId: string; - name: string; - revisionDate: string; - - constructor(response: FolderResponse, userId: string) { - this.userId = userId; - this.name = response.name; - this.id = response.id; - this.revisionDate = response.revisionDate; - } -} - -export { FolderData }; -(window as any).FolderData = FolderData; diff --git a/src/models/data/identityData.ts b/src/models/data/identityData.ts deleted file mode 100644 index ee849166eec..00000000000 --- a/src/models/data/identityData.ts +++ /dev/null @@ -1,44 +0,0 @@ -class IdentityData { - 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(data: any) { - this.title = data.Title; - this.firstName = data.FirstName; - this.middleName = data.MiddleName; - this.lastName = data.LastName; - this.address1 = data.Address1; - this.address2 = data.Address2; - this.address3 = data.Address3; - this.city = data.City; - this.state = data.State; - this.postalCode = data.PostalCode; - this.country = data.Country; - this.company = data.Company; - this.email = data.Email; - this.phone = data.Phone; - this.ssn = data.SSN; - this.username = data.Username; - this.passportNumber = data.PassportNumber; - this.licenseNumber = data.LicenseNumber; - } -} - -export { IdentityData }; -(window as any).IdentityData = IdentityData; diff --git a/src/models/data/loginData.ts b/src/models/data/loginData.ts deleted file mode 100644 index de0aecc133f..00000000000 --- a/src/models/data/loginData.ts +++ /dev/null @@ -1,16 +0,0 @@ -class LoginData { - uri: string; - username: string; - password: string; - totp: string; - - constructor(data: any) { - this.uri = data.Uri; - this.username = data.Username; - this.password = data.Password; - this.totp = data.Totp; - } -} - -export { LoginData }; -(window as any).LoginData = LoginData; diff --git a/src/models/data/secureNoteData.ts b/src/models/data/secureNoteData.ts deleted file mode 100644 index ccfc9bd614c..00000000000 --- a/src/models/data/secureNoteData.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SecureNoteType } from '../../enums/secureNoteType.enum'; - -class SecureNoteData { - type: SecureNoteType; - - constructor(data: any) { - this.type = data.Type; - } -} - -export { SecureNoteData }; -(window as any).SecureNoteData = SecureNoteData; diff --git a/src/models/domain/attachment.ts b/src/models/domain/attachment.ts deleted file mode 100644 index d77152b0077..00000000000 --- a/src/models/domain/attachment.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { AttachmentData } from '../data/attachmentData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Attachment extends Domain { - id: string; - url: string; - size: number; - sizeName: string; - fileName: CipherString; - - constructor(obj?: AttachmentData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.size = obj.size; - this.buildDomainModel(this, obj, { - id: null, - url: null, - sizeName: null, - fileName: null, - }, 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, { - fileName: null, - }, orgId); - } -} - -export { Attachment }; -(window as any).Attachment = Attachment; diff --git a/src/models/domain/autofillField.ts b/src/models/domain/autofillField.ts deleted file mode 100644 index dfa6cdc7702..00000000000 --- a/src/models/domain/autofillField.ts +++ /dev/null @@ -1,22 +0,0 @@ -export default class AutofillField { - opid: string; - elementNumber: number; - visible: boolean; - viewable: boolean; - htmlID: string; - htmlName: string; - htmlClass: string; - 'label-left': string; - 'label-right': string; - 'label-top': string; - 'label-tag': string; - placeholder: string; - type: string; - value: string; - disabled: boolean; - readonly: boolean; - onePasswordFieldType: string; - form: string; - autoCompleteType: string; - selectInfo: any; -} diff --git a/src/models/domain/autofillForm.ts b/src/models/domain/autofillForm.ts deleted file mode 100644 index 2d7fc4800b2..00000000000 --- a/src/models/domain/autofillForm.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default class AutofillForm { - opid: string; - htmlName: string; - htmlID: string; - htmlAction: string; - htmlMethod: string; -} diff --git a/src/models/domain/autofillPageDetails.ts b/src/models/domain/autofillPageDetails.ts deleted file mode 100644 index 70f922bbe6d..00000000000 --- a/src/models/domain/autofillPageDetails.ts +++ /dev/null @@ -1,13 +0,0 @@ -import AutofillField from './autofillField'; -import AutofillForm from './autofillForm'; - -export default class AutofillPageDetails { - documentUUID: string; - title: string; - url: string; - documentUrl: string; - tabUrl: string; - forms: { [id: string]: AutofillForm; }; - fields: AutofillField[]; - collectedTimestamp: number; -} diff --git a/src/models/domain/autofillScript.ts b/src/models/domain/autofillScript.ts deleted file mode 100644 index 875e620ea8e..00000000000 --- a/src/models/domain/autofillScript.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default class AutofillScript { - script: string[][] = []; - documentUUID: any = {}; - properties: any = {}; - options: any = {}; - metadata: any = {}; - autosubmit: any = null; - - constructor(documentUUID: string) { - this.documentUUID = documentUUID; - } -} diff --git a/src/models/domain/card.ts b/src/models/domain/card.ts deleted file mode 100644 index 3e42f7affba..00000000000 --- a/src/models/domain/card.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { CardData } from '../data/cardData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Card extends Domain { - cardholderName: CipherString; - brand: CipherString; - number: CipherString; - expMonth: CipherString; - expYear: CipherString; - code: CipherString; - - constructor(obj?: CardData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - cardholderName: null, - brand: null, - number: null, - expMonth: null, - expYear: null, - code: null, - }, alreadyEncrypted, []); - } - - decrypt(orgId: string): Promise { - return this.decryptObj({}, { - cardholderName: null, - brand: null, - number: null, - expMonth: null, - expYear: null, - code: null, - }, orgId); - } -} - -export { Card }; -(window as any).Card = Card; diff --git a/src/models/domain/cipher.ts b/src/models/domain/cipher.ts deleted file mode 100644 index 5d51c828282..00000000000 --- a/src/models/domain/cipher.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { CipherType } from '../../enums/cipherType.enum'; - -import { CipherData } from '../data/cipherData'; - -import { Attachment } from './attachment'; -import { Card } from './card'; -import { CipherString } from './cipherString'; -import Domain from './domain'; -import { Field } from './field'; -import { Identity } from './identity'; -import { Login } from './login'; -import { SecureNote } from './secureNote'; - -import { UtilsService } from '../../services/abstractions/utils.service'; - -class Cipher extends Domain { - id: string; - organizationId: string; - folderId: string; - name: CipherString; - notes: CipherString; - type: CipherType; - favorite: boolean; - organizationUseTotp: boolean; - edit: boolean; - localData: any; - login: Login; - identity: Identity; - card: Card; - secureNote: SecureNote; - attachments: Attachment[]; - fields: Field[]; - collectionIds: string[]; - - private utilsService: UtilsService; - - constructor(obj?: CipherData, alreadyEncrypted: boolean = false, localData: any = null) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - id: null, - organizationId: null, - folderId: null, - name: null, - notes: null, - }, alreadyEncrypted, ['id', 'organizationId', 'folderId']); - - this.type = obj.type; - this.favorite = obj.favorite; - this.organizationUseTotp = obj.organizationUseTotp; - this.edit = obj.edit; - this.collectionIds = obj.collectionIds; - this.localData = localData; - - switch (this.type) { - case CipherType.Login: - this.login = new Login(obj.login, alreadyEncrypted); - break; - case CipherType.SecureNote: - this.secureNote = new SecureNote(obj.secureNote, alreadyEncrypted); - break; - case CipherType.Card: - this.card = new Card(obj.card, alreadyEncrypted); - break; - case CipherType.Identity: - this.identity = new Identity(obj.identity, alreadyEncrypted); - break; - default: - break; - } - - if (obj.attachments != null) { - this.attachments = []; - obj.attachments.forEach((attachment) => { - this.attachments.push(new Attachment(attachment, alreadyEncrypted)); - }); - } else { - this.attachments = null; - } - - if (obj.fields != null) { - this.fields = []; - obj.fields.forEach((field) => { - this.fields.push(new Field(field, alreadyEncrypted)); - }); - } else { - this.fields = null; - } - } - - 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, - }; - - await this.decryptObj(model, { - name: null, - notes: null, - }, this.organizationId); - - switch (this.type) { - case CipherType.Login: - model.login = await this.login.decrypt(this.organizationId); - model.subTitle = model.login.username; - if (model.login.uri) { - if (this.utilsService == null) { - this.utilsService = chrome.extension.getBackgroundPage() - .bitwardenMain.utilsService as UtilsService; - } - - model.login.domain = this.utilsService.getDomain(model.login.uri); - } - 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; - } - - const orgId = this.organizationId; - - if (this.attachments != null && this.attachments.length > 0) { - const attachments: any[] = []; - await this.attachments.reduce((promise, attachment) => { - return promise.then(() => { - return attachment.decrypt(orgId); - }).then((decAttachment) => { - attachments.push(decAttachment); - }); - }, Promise.resolve()); - model.attachments = attachments; - } - - if (this.fields != null && this.fields.length > 0) { - const fields: any[] = []; - await this.fields.reduce((promise, field) => { - return promise.then(() => { - return field.decrypt(orgId); - }).then((decField) => { - fields.push(decField); - }); - }, Promise.resolve()); - model.fields = fields; - } - - return model; - } -} - -export { Cipher }; -(window as any).Cipher = Cipher; diff --git a/src/models/domain/cipherString.ts b/src/models/domain/cipherString.ts deleted file mode 100644 index 61aec8ca6fb..00000000000 --- a/src/models/domain/cipherString.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { EncryptionType } from '../../enums/encryptionType.enum'; -import { CryptoService } from '../../services/abstractions/crypto.service'; - -class CipherString { - encryptedString?: string; - encryptionType?: EncryptionType; - decryptedValue?: string; - cipherText?: string; - initializationVector?: string; - mac?: string; - - private cryptoService: CryptoService; - - constructor(encryptedStringOrType: string | EncryptionType, ct?: string, iv?: string, mac?: string) { - if (ct != null) { - // ct and header - const encType = encryptedStringOrType as EncryptionType; - this.encryptedString = encType + '.' + ct; - - // iv - if (iv != null) { - this.encryptedString += ('|' + iv); - } - - // mac - if (mac != null) { - this.encryptedString += ('|' + mac); - } - - this.encryptionType = encType; - this.cipherText = ct; - this.initializationVector = iv; - this.mac = mac; - - return; - } - - this.encryptedString = encryptedStringOrType as string; - if (!this.encryptedString) { - return; - } - - const headerPieces = this.encryptedString.split('.'); - let encPieces: string[] = null; - - if (headerPieces.length === 2) { - try { - this.encryptionType = parseInt(headerPieces[0], null); - encPieces = headerPieces[1].split('|'); - } catch (e) { - return; - } - } else { - encPieces = this.encryptedString.split('|'); - this.encryptionType = encPieces.length === 3 ? EncryptionType.AesCbc128_HmacSha256_B64 : - EncryptionType.AesCbc256_B64; - } - - switch (this.encryptionType) { - case EncryptionType.AesCbc128_HmacSha256_B64: - case EncryptionType.AesCbc256_HmacSha256_B64: - if (encPieces.length !== 3) { - return; - } - - this.initializationVector = encPieces[0]; - this.cipherText = encPieces[1]; - this.mac = encPieces[2]; - break; - case EncryptionType.AesCbc256_B64: - if (encPieces.length !== 2) { - return; - } - - this.initializationVector = encPieces[0]; - this.cipherText = encPieces[1]; - break; - case EncryptionType.Rsa2048_OaepSha256_B64: - case EncryptionType.Rsa2048_OaepSha1_B64: - if (encPieces.length !== 1) { - return; - } - - this.cipherText = encPieces[0]; - break; - default: - return; - } - } - - decrypt(orgId: string) { - if (this.decryptedValue) { - return Promise.resolve(this.decryptedValue); - } - - const self = this; - if (this.cryptoService == null) { - this.cryptoService = chrome.extension.getBackgroundPage() - .bitwardenMain.cryptoService as CryptoService; - } - - return this.cryptoService.getOrgKey(orgId).then((orgKey: any) => { - return self.cryptoService.decrypt(self, orgKey); - }).then((decValue: any) => { - self.decryptedValue = decValue; - return self.decryptedValue; - }).catch(() => { - self.decryptedValue = '[error: cannot decrypt]'; - return self.decryptedValue; - }); - } -} - -export { CipherString }; -(window as any).CipherString = CipherString; diff --git a/src/models/domain/collection.ts b/src/models/domain/collection.ts deleted file mode 100644 index 0a5079e53a9..00000000000 --- a/src/models/domain/collection.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { CollectionData } from '../data/collectionData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Collection extends Domain { - id: string; - organizationId: string; - name: CipherString; - - constructor(obj?: CollectionData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - id: null, - organizationId: null, - name: null, - }, alreadyEncrypted, ['id', 'organizationId']); - } - - decrypt(): Promise { - const model = { - id: this.id, - organizationId: this.organizationId, - }; - - return this.decryptObj(model, { - name: null, - }, this.organizationId); - } -} - -export { Collection }; -(window as any).Collection = Collection; diff --git a/src/models/domain/domain.ts b/src/models/domain/domain.ts deleted file mode 100644 index cdb220776b2..00000000000 --- a/src/models/domain/domain.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CipherString } from '../domain/cipherString'; - -export default abstract class Domain { - protected buildDomainModel(model: any, obj: any, map: any, alreadyEncrypted: boolean, notEncList: any[] = []) { - for (const prop in map) { - if (!map.hasOwnProperty(prop)) { - continue; - } - - const objProp = obj[(map[prop] || prop)]; - if (alreadyEncrypted === true || notEncList.indexOf(prop) > -1) { - model[prop] = objProp ? objProp : null; - } else { - model[prop] = objProp ? new CipherString(objProp) : null; - } - } - } - - protected async decryptObj(model: any, map: any, orgId: string) { - const promises = []; - const self: any = this; - - for (const prop in map) { - if (!map.hasOwnProperty(prop)) { - continue; - } - - // tslint:disable-next-line - (function (theProp) { - const p = Promise.resolve().then(() => { - const mapProp = map[theProp] || theProp; - if (self[mapProp]) { - return self[mapProp].decrypt(orgId); - } - return null; - }).then((val: any) => { - model[theProp] = val; - }); - promises.push(p); - })(prop); - } - - await Promise.all(promises); - return model; - } -} diff --git a/src/models/domain/encryptedObject.ts b/src/models/domain/encryptedObject.ts deleted file mode 100644 index 668c30e2620..00000000000 --- a/src/models/domain/encryptedObject.ts +++ /dev/null @@ -1,8 +0,0 @@ -import SymmetricCryptoKey from './symmetricCryptoKey'; - -export default class EncryptedObject { - iv: Uint8Array; - ct: Uint8Array; - mac: Uint8Array; - key: SymmetricCryptoKey; -} diff --git a/src/models/domain/environmentUrls.ts b/src/models/domain/environmentUrls.ts deleted file mode 100644 index 0eec60a114e..00000000000 --- a/src/models/domain/environmentUrls.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default class EnvironmentUrls { - base: string; - api: string; - identity: string; -} diff --git a/src/models/domain/field.ts b/src/models/domain/field.ts deleted file mode 100644 index decc78664f0..00000000000 --- a/src/models/domain/field.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { FieldType } from '../../enums/fieldType.enum'; - -import { FieldData } from '../data/fieldData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Field extends Domain { - name: CipherString; - vault: CipherString; - type: FieldType; - - constructor(obj?: FieldData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.type = obj.type; - this.buildDomainModel(this, obj, { - name: null, - value: null, - }, alreadyEncrypted, []); - } - - decrypt(orgId: string): Promise { - const model = { - type: this.type, - }; - - return this.decryptObj(model, { - name: null, - value: null, - }, orgId); - } -} - -export { Field }; -(window as any).Field = Field; diff --git a/src/models/domain/folder.ts b/src/models/domain/folder.ts deleted file mode 100644 index 180cd44e4f3..00000000000 --- a/src/models/domain/folder.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { FolderData } from '../data/folderData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Folder extends Domain { - id: string; - name: CipherString; - - constructor(obj?: FolderData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - id: null, - name: null, - }, alreadyEncrypted, ['id']); - } - - decrypt(): Promise { - const model = { - id: this.id, - }; - - return this.decryptObj(model, { - name: null, - }, null); - } -} - -export { Folder }; -(window as any).Folder = Folder; diff --git a/src/models/domain/identity.ts b/src/models/domain/identity.ts deleted file mode 100644 index ede933ac309..00000000000 --- a/src/models/domain/identity.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { IdentityData } from '../data/identityData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Identity extends Domain { - title: CipherString; - firstName: CipherString; - middleName: CipherString; - lastName: CipherString; - address1: CipherString; - address2: CipherString; - address3: CipherString; - city: CipherString; - state: CipherString; - postalCode: CipherString; - country: CipherString; - company: CipherString; - email: CipherString; - phone: CipherString; - ssn: CipherString; - username: CipherString; - passportNumber: CipherString; - licenseNumber: CipherString; - - constructor(obj?: IdentityData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - 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, - }, alreadyEncrypted, []); - } - - decrypt(orgId: string): Promise { - return this.decryptObj({}, { - 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, - }, orgId); - } -} - -export { Identity }; -(window as any).Identity = Identity; diff --git a/src/models/domain/login.ts b/src/models/domain/login.ts deleted file mode 100644 index 8ed1e1f7c7c..00000000000 --- a/src/models/domain/login.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { LoginData } from '../data/loginData'; - -import { CipherString } from './cipherString'; -import Domain from './domain'; - -class Login extends Domain { - uri: CipherString; - username: CipherString; - password: CipherString; - totp: CipherString; - - constructor(obj?: LoginData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.buildDomainModel(this, obj, { - uri: null, - username: null, - password: null, - totp: null, - }, alreadyEncrypted, []); - } - - decrypt(orgId: string): Promise { - return this.decryptObj({}, { - uri: null, - username: null, - password: null, - totp: null, - }, orgId); - } -} - -export { Login }; -(window as any).Login = Login; diff --git a/src/models/domain/passwordHistory.ts b/src/models/domain/passwordHistory.ts deleted file mode 100644 index fc4eb56688f..00000000000 --- a/src/models/domain/passwordHistory.ts +++ /dev/null @@ -1,9 +0,0 @@ -export default class PasswordHistory { - password: string; - date: number; - - constructor(password: string, date: number) { - this.password = password; - this.date = date; - } -} diff --git a/src/models/domain/secureNote.ts b/src/models/domain/secureNote.ts deleted file mode 100644 index 2c742f8ea89..00000000000 --- a/src/models/domain/secureNote.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { SecureNoteType } from '../../enums/secureNoteType.enum'; - -import { SecureNoteData } from '../data/secureNoteData'; - -import Domain from './domain'; - -class SecureNote extends Domain { - type: SecureNoteType; - - constructor(obj?: SecureNoteData, alreadyEncrypted: boolean = false) { - super(); - if (obj == null) { - return; - } - - this.type = obj.type; - } - - decrypt(orgId: string): any { - return { - type: this.type, - }; - } -} - -export { SecureNote }; -(window as any).SecureNote = SecureNote; diff --git a/src/models/domain/symmetricCryptoKey.ts b/src/models/domain/symmetricCryptoKey.ts deleted file mode 100644 index bf50795a020..00000000000 --- a/src/models/domain/symmetricCryptoKey.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as forge from 'node-forge'; - -import { EncryptionType } from '../../enums/encryptionType.enum'; - -import SymmetricCryptoKeyBuffers from './symmetricCryptoKeyBuffers'; - -import UtilsService from '../../services/utils.service'; - -export default class SymmetricCryptoKey { - key: string; - keyB64: string; - encKey: string; - macKey: string; - encType: EncryptionType; - keyBuf: SymmetricCryptoKeyBuffers; - - constructor(keyBytes: string, b64KeyBytes?: boolean, encType?: EncryptionType) { - if (b64KeyBytes) { - keyBytes = forge.util.decode64(keyBytes); - } - - if (!keyBytes) { - throw new Error('Must provide keyBytes'); - } - - const buffer = (forge as any).util.createBuffer(keyBytes); - if (!buffer || buffer.length() === 0) { - throw new Error('Couldn\'t make buffer'); - } - - const bufferLength: number = buffer.length(); - - if (encType == null) { - if (bufferLength === 32) { - encType = EncryptionType.AesCbc256_B64; - } else if (bufferLength === 64) { - encType = EncryptionType.AesCbc256_HmacSha256_B64; - } else { - throw new Error('Unable to determine encType.'); - } - } - - this.key = keyBytes; - this.keyB64 = forge.util.encode64(keyBytes); - this.encType = encType; - - if (encType === EncryptionType.AesCbc256_B64 && bufferLength === 32) { - this.encKey = keyBytes; - this.macKey = null; - } else if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && bufferLength === 32) { - this.encKey = buffer.getBytes(16); // first half - this.macKey = buffer.getBytes(16); // second half - } else if (encType === EncryptionType.AesCbc256_HmacSha256_B64 && bufferLength === 64) { - this.encKey = buffer.getBytes(32); // first half - this.macKey = buffer.getBytes(32); // second half - } else { - throw new Error('Unsupported encType/key length.'); - } - } - - getBuffers() { - if (this.keyBuf) { - return this.keyBuf; - } - - const key = UtilsService.fromB64ToArray(this.keyB64); - const keys = new SymmetricCryptoKeyBuffers(key.buffer); - - if (this.macKey) { - keys.encKey = key.slice(0, key.length / 2).buffer; - keys.macKey = key.slice(key.length / 2).buffer; - } else { - keys.encKey = key.buffer; - keys.macKey = null; - } - - this.keyBuf = keys; - return this.keyBuf; - } -} diff --git a/src/models/domain/symmetricCryptoKeyBuffers.ts b/src/models/domain/symmetricCryptoKeyBuffers.ts deleted file mode 100644 index 5a378ad246b..00000000000 --- a/src/models/domain/symmetricCryptoKeyBuffers.ts +++ /dev/null @@ -1,9 +0,0 @@ -export default class SymmetricCryptoKeyBuffers { - key: ArrayBuffer; - encKey?: ArrayBuffer; - macKey?: ArrayBuffer; - - constructor(key: ArrayBuffer) { - this.key = key; - } -} diff --git a/src/models/request/cipherRequest.ts b/src/models/request/cipherRequest.ts deleted file mode 100644 index 659ee5be2d6..00000000000 --- a/src/models/request/cipherRequest.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { CipherType } from '../../enums/cipherType.enum'; - -class CipherRequest { - type: CipherType; - folderId: string; - organizationId: string; - name: string; - notes: string; - favorite: boolean; - login: any; - secureNote: any; - card: any; - identity: any; - fields: any[]; - - constructor(cipher: any) { - this.type = cipher.type; - this.folderId = cipher.folderId; - this.organizationId = cipher.organizationId; - this.name = cipher.name ? cipher.name.encryptedString : null; - this.notes = cipher.notes ? cipher.notes.encryptedString : null; - this.favorite = cipher.favorite; - - switch (this.type) { - case CipherType.Login: - this.login = { - uri: cipher.login.uri ? cipher.login.uri.encryptedString : null, - username: cipher.login.username ? cipher.login.username.encryptedString : null, - password: cipher.login.password ? cipher.login.password.encryptedString : null, - totp: cipher.login.totp ? cipher.login.totp.encryptedString : null, - }; - break; - case CipherType.SecureNote: - this.secureNote = { - type: cipher.secureNote.type, - }; - break; - case CipherType.Card: - this.card = { - cardholderName: cipher.card.cardholderName ? cipher.card.cardholderName.encryptedString : null, - brand: cipher.card.brand ? cipher.card.brand.encryptedString : null, - number: cipher.card.number ? cipher.card.number.encryptedString : null, - expMonth: cipher.card.expMonth ? cipher.card.expMonth.encryptedString : null, - expYear: cipher.card.expYear ? cipher.card.expYear.encryptedString : null, - code: cipher.card.code ? cipher.card.code.encryptedString : null, - }; - break; - case CipherType.Identity: - this.identity = { - title: cipher.identity.title ? cipher.identity.title.encryptedString : null, - firstName: cipher.identity.firstName ? cipher.identity.firstName.encryptedString : null, - middleName: cipher.identity.middleName ? cipher.identity.middleName.encryptedString : null, - lastName: cipher.identity.lastName ? cipher.identity.lastName.encryptedString : null, - address1: cipher.identity.address1 ? cipher.identity.address1.encryptedString : null, - address2: cipher.identity.address2 ? cipher.identity.address2.encryptedString : null, - address3: cipher.identity.address3 ? cipher.identity.address3.encryptedString : null, - city: cipher.identity.city ? cipher.identity.city.encryptedString : null, - state: cipher.identity.state ? cipher.identity.state.encryptedString : null, - postalCode: cipher.identity.postalCode ? cipher.identity.postalCode.encryptedString : null, - country: cipher.identity.country ? cipher.identity.country.encryptedString : null, - company: cipher.identity.company ? cipher.identity.company.encryptedString : null, - email: cipher.identity.email ? cipher.identity.email.encryptedString : null, - phone: cipher.identity.phone ? cipher.identity.phone.encryptedString : null, - ssn: cipher.identity.ssn ? cipher.identity.ssn.encryptedString : null, - username: cipher.identity.username ? cipher.identity.username.encryptedString : null, - passportNumber: cipher.identity.passportNumber ? - cipher.identity.passportNumber.encryptedString : null, - licenseNumber: cipher.identity.licenseNumber ? cipher.identity.licenseNumber.encryptedString : null, - }; - break; - default: - break; - } - - if (cipher.fields) { - this.fields = []; - cipher.fields.forEach((field: any) => { - this.fields.push({ - type: field.type, - name: field.name ? field.name.encryptedString : null, - value: field.value ? field.value.encryptedString : null, - }); - }); - } - } -} - -export { CipherRequest }; -(window as any).CipherRequest = CipherRequest; diff --git a/src/models/request/deviceRequest.ts b/src/models/request/deviceRequest.ts deleted file mode 100644 index 928a46557f3..00000000000 --- a/src/models/request/deviceRequest.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BrowserType } from '../../enums/browserType.enum'; -import { UtilsService } from '../../services/abstractions/utils.service'; - -class DeviceRequest { - type: BrowserType; - name: string; - identifier: string; - pushToken?: string; - - constructor(appId: string, utilsService: UtilsService) { - this.type = utilsService.getBrowser(); - this.name = utilsService.getBrowserString(); - this.identifier = appId; - this.pushToken = null; - } -} - -export { DeviceRequest }; -(window as any).DeviceRequest = DeviceRequest; diff --git a/src/models/request/deviceTokenRequest.ts b/src/models/request/deviceTokenRequest.ts deleted file mode 100644 index 69ef20bb9a7..00000000000 --- a/src/models/request/deviceTokenRequest.ts +++ /dev/null @@ -1,10 +0,0 @@ -class DeviceTokenRequest { - pushToken: string; - - constructor() { - this.pushToken = null; - } -} - -export { DeviceTokenRequest }; -(window as any).DeviceTokenRequest = DeviceTokenRequest; diff --git a/src/models/request/folderRequest.ts b/src/models/request/folderRequest.ts deleted file mode 100644 index 7ff9794b184..00000000000 --- a/src/models/request/folderRequest.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Folder } from '../domain/folder'; - -class FolderRequest { - name: string; - - constructor(folder: Folder) { - this.name = folder.name ? folder.name.encryptedString : null; - } -} - -export { FolderRequest }; -(window as any).FolderRequest = FolderRequest; diff --git a/src/models/request/passwordHintRequest.ts b/src/models/request/passwordHintRequest.ts deleted file mode 100644 index 4feb92944bf..00000000000 --- a/src/models/request/passwordHintRequest.ts +++ /dev/null @@ -1,10 +0,0 @@ -class PasswordHintRequest { - email: string; - - constructor(email: string) { - this.email = email; - } -} - -export { PasswordHintRequest }; -(window as any).PasswordHintRequest = PasswordHintRequest; diff --git a/src/models/request/registerRequest.ts b/src/models/request/registerRequest.ts deleted file mode 100644 index 4b80fb7884f..00000000000 --- a/src/models/request/registerRequest.ts +++ /dev/null @@ -1,18 +0,0 @@ -class RegisterRequest { - name: string; - email: string; - masterPasswordHash: string; - masterPasswordHint: string; - key: string; - - constructor(email: string, masterPasswordHash: string, masterPasswordHint: string, key: string) { - this.name = null; - this.email = email; - this.masterPasswordHash = masterPasswordHash; - this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null; - this.key = key; - } -} - -export { RegisterRequest }; -(window as any).RegisterRequest = RegisterRequest; diff --git a/src/models/request/tokenRequest.ts b/src/models/request/tokenRequest.ts deleted file mode 100644 index 4cf75646155..00000000000 --- a/src/models/request/tokenRequest.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DeviceRequest } from './deviceRequest'; - -class TokenRequest { - email: string; - masterPasswordHash: string; - token: string; - provider: number; - remember: boolean; - device?: DeviceRequest; - - constructor(email: string, masterPasswordHash: string, provider: number, - token: string, remember: boolean, device?: DeviceRequest) { - this.email = email; - this.masterPasswordHash = masterPasswordHash; - this.token = token; - this.provider = provider; - this.remember = remember; - this.device = device != null ? device : null; - } - - toIdentityToken() { - const obj: any = { - grant_type: 'password', - username: this.email, - password: this.masterPasswordHash, - scope: 'api offline_access', - client_id: 'browser', - }; - - if (this.device) { - obj.deviceType = this.device.type; - obj.deviceIdentifier = this.device.identifier; - obj.deviceName = this.device.name; - // no push tokens for browser apps yet - // obj.devicePushToken = this.device.pushToken; - } - - if (this.token && this.provider !== null && (typeof this.provider !== 'undefined')) { - obj.twoFactorToken = this.token; - obj.twoFactorProvider = this.provider; - obj.twoFactorRemember = this.remember ? '1' : '0'; - } - - return obj; - } -} - -export { TokenRequest }; -(window as any).TokenRequest = TokenRequest; diff --git a/src/models/request/twoFactorEmailRequest.ts b/src/models/request/twoFactorEmailRequest.ts deleted file mode 100644 index d540b08ecf1..00000000000 --- a/src/models/request/twoFactorEmailRequest.ts +++ /dev/null @@ -1,12 +0,0 @@ -class TwoFactorEmailRequest { - email: string; - masterPasswordHash: string; - - constructor(email: string, masterPasswordHash: string) { - this.email = email; - this.masterPasswordHash = masterPasswordHash; - } -} - -export { TwoFactorEmailRequest }; -(window as any).TwoFactorEmailRequest = TwoFactorEmailRequest; diff --git a/src/models/response/attachmentResponse.ts b/src/models/response/attachmentResponse.ts deleted file mode 100644 index df5138650cf..00000000000 --- a/src/models/response/attachmentResponse.ts +++ /dev/null @@ -1,18 +0,0 @@ -class AttachmentResponse { - id: string; - url: string; - fileName: string; - size: number; - sizeName: string; - - constructor(response: any) { - this.id = response.Id; - this.url = response.Url; - this.fileName = response.FileName; - this.size = response.Size; - this.sizeName = response.SizeName; - } -} - -export { AttachmentResponse }; -(window as any).AttachmentResponse = AttachmentResponse; diff --git a/src/models/response/cipherResponse.ts b/src/models/response/cipherResponse.ts deleted file mode 100644 index b2e597e23a3..00000000000 --- a/src/models/response/cipherResponse.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AttachmentResponse } from './attachmentResponse'; - -class CipherResponse { - id: string; - organizationId: string; - folderId: string; - type: number; - favorite: boolean; - edit: boolean; - organizationUseTotp: boolean; - data: any; - revisionDate: string; - attachments: AttachmentResponse[]; - collectionIds: string[]; - - constructor(response: any) { - this.id = response.Id; - this.organizationId = response.OrganizationId; - this.folderId = response.FolderId; - this.type = response.Type; - this.favorite = response.Favorite; - this.edit = response.Edit; - this.organizationUseTotp = response.OrganizationUseTotp; - this.data = response.Data; - this.revisionDate = response.RevisionDate; - - if (response.Attachments != null) { - this.attachments = []; - response.Attachments.forEach((attachment: any) => { - this.attachments.push(new AttachmentResponse(attachment)); - }); - } - - if (response.CollectionIds) { - this.collectionIds = []; - response.CollectionIds.forEach((id: string) => { - this.collectionIds.push(id); - }); - } - } -} - -export { CipherResponse }; -(window as any).CipherResponse = CipherResponse; diff --git a/src/models/response/collectionResponse.ts b/src/models/response/collectionResponse.ts deleted file mode 100644 index 8b0247720db..00000000000 --- a/src/models/response/collectionResponse.ts +++ /dev/null @@ -1,14 +0,0 @@ -class CollectionResponse { - id: string; - organizationId: string; - name: string; - - constructor(response: any) { - this.id = response.Id; - this.organizationId = response.OrganizationId; - this.name = response.Name; - } -} - -export { CollectionResponse }; -(window as any).CollectionResponse = CollectionResponse; diff --git a/src/models/response/deviceResponse.ts b/src/models/response/deviceResponse.ts deleted file mode 100644 index 63f69fe3b38..00000000000 --- a/src/models/response/deviceResponse.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BrowserType } from '../../enums/browserType.enum'; - -class DeviceResponse { - id: string; - name: number; - identifier: string; - type: BrowserType; - creationDate: string; - - constructor(response: any) { - this.id = response.Id; - this.name = response.Name; - this.identifier = response.Identifier; - this.type = response.Type; - this.creationDate = response.CreationDate; - } -} - -export { DeviceResponse }; -(window as any).DeviceResponse = DeviceResponse; diff --git a/src/models/response/domainsResponse.ts b/src/models/response/domainsResponse.ts deleted file mode 100644 index d65077147bc..00000000000 --- a/src/models/response/domainsResponse.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { GlobalDomainResponse } from './globalDomainResponse'; - -class DomainsResponse { - equivalentDomains: string[][]; - globalEquivalentDomains: GlobalDomainResponse[] = []; - - constructor(response: any) { - this.equivalentDomains = response.EquivalentDomains; - - this.globalEquivalentDomains = []; - if (response.GlobalEquivalentDomains) { - response.GlobalEquivalentDomains.forEach((domain: any) => { - this.globalEquivalentDomains.push(new GlobalDomainResponse(domain)); - }); - } - } -} - -export { DomainsResponse }; -(window as any).DomainsResponse = DomainsResponse; diff --git a/src/models/response/errorResponse.ts b/src/models/response/errorResponse.ts deleted file mode 100644 index 4d976932cd2..00000000000 --- a/src/models/response/errorResponse.ts +++ /dev/null @@ -1,37 +0,0 @@ -class ErrorResponse { - message: string; - validationErrors: { [key: string]: string[]; }; - statusCode: number; - - constructor(response: any, status: number, identityResponse?: boolean) { - let errorModel = null; - if (identityResponse && response && response.ErrorModel) { - errorModel = response.ErrorModel; - } else if (response) { - errorModel = response; - } - - if (errorModel) { - this.message = errorModel.Message; - this.validationErrors = errorModel.ValidationErrors; - } - this.statusCode = status; - } - - getSingleMessage(): string { - if (this.validationErrors) { - for (const key in this.validationErrors) { - if (!this.validationErrors.hasOwnProperty(key)) { - continue; - } - if (this.validationErrors[key].length) { - return this.validationErrors[key][0]; - } - } - } - return this.message; - } -} - -export { ErrorResponse }; -(window as any).ErrorResponse = ErrorResponse; diff --git a/src/models/response/folderResponse.ts b/src/models/response/folderResponse.ts deleted file mode 100644 index c5ff0ada70a..00000000000 --- a/src/models/response/folderResponse.ts +++ /dev/null @@ -1,14 +0,0 @@ -class FolderResponse { - id: string; - name: string; - revisionDate: string; - - constructor(response: any) { - this.id = response.Id; - this.name = response.Name; - this.revisionDate = response.RevisionDate; - } -} - -export { FolderResponse }; -(window as any).FolderResponse = FolderResponse; diff --git a/src/models/response/globalDomainResponse.ts b/src/models/response/globalDomainResponse.ts deleted file mode 100644 index 8e9a45df11e..00000000000 --- a/src/models/response/globalDomainResponse.ts +++ /dev/null @@ -1,14 +0,0 @@ -class GlobalDomainResponse { - type: number; - domains: string[]; - excluded: number[]; - - constructor(response: any) { - this.type = response.Type; - this.domains = response.Domains; - this.excluded = response.Excluded; - } -} - -export { GlobalDomainResponse }; -(window as any).GlobalDomainResponse = GlobalDomainResponse; diff --git a/src/models/response/identityTokenResponse.ts b/src/models/response/identityTokenResponse.ts deleted file mode 100644 index 2d188707c05..00000000000 --- a/src/models/response/identityTokenResponse.ts +++ /dev/null @@ -1,24 +0,0 @@ -class IdentityTokenResponse { - accessToken: string; - expiresIn: number; - refreshToken: string; - tokenType: string; - - privateKey: string; - key: string; - twoFactorToken: string; - - constructor(response: any) { - this.accessToken = response.access_token; - this.expiresIn = response.expires_in; - this.refreshToken = response.refresh_token; - this.tokenType = response.token_type; - - this.privateKey = response.PrivateKey; - this.key = response.Key; - this.twoFactorToken = response.TwoFactorToken; - } -} - -export { IdentityTokenResponse }; -(window as any).IdentityTokenResponse = IdentityTokenResponse; diff --git a/src/models/response/keysResponse.ts b/src/models/response/keysResponse.ts deleted file mode 100644 index cb96dad51e4..00000000000 --- a/src/models/response/keysResponse.ts +++ /dev/null @@ -1,12 +0,0 @@ -class KeysResponse { - privateKey: string; - publicKey: string; - - constructor(response: any) { - this.privateKey = response.PrivateKey; - this.publicKey = response.PublicKey; - } -} - -export { KeysResponse }; -(window as any).KeysResponse = KeysResponse; diff --git a/src/models/response/listResponse.ts b/src/models/response/listResponse.ts deleted file mode 100644 index 9cf5455ada8..00000000000 --- a/src/models/response/listResponse.ts +++ /dev/null @@ -1,10 +0,0 @@ -class ListResponse { - data: any; - - constructor(data: any) { - this.data = data; - } -} - -export { ListResponse }; -(window as any).ListResponse = ListResponse; diff --git a/src/models/response/profileOrganizationResponse.ts b/src/models/response/profileOrganizationResponse.ts deleted file mode 100644 index 484857745a0..00000000000 --- a/src/models/response/profileOrganizationResponse.ts +++ /dev/null @@ -1,30 +0,0 @@ -class ProfileOrganizationResponse { - id: string; - name: string; - useGroups: boolean; - useDirectory: boolean; - useTotp: boolean; - seats: number; - maxCollections: number; - maxStorageGb?: number; - key: string; - status: number; // TODO: map to enum - type: number; // TODO: map to enum - - constructor(response: any) { - this.id = response.Id; - this.name = response.Name; - this.useGroups = response.UseGroups; - this.useDirectory = response.UseDirectory; - this.useTotp = response.UseTotp; - this.seats = response.Seats; - this.maxCollections = response.MaxCollections; - this.maxStorageGb = response.MaxStorageGb; - this.key = response.Key; - this.status = response.Status; - this.type = response.Type; - } -} - -export { ProfileOrganizationResponse }; -(window as any).ProfileOrganizationResponse = ProfileOrganizationResponse; diff --git a/src/models/response/profileResponse.ts b/src/models/response/profileResponse.ts deleted file mode 100644 index 69e4f3e9cb8..00000000000 --- a/src/models/response/profileResponse.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ProfileOrganizationResponse } from './profileOrganizationResponse'; - -class ProfileResponse { - id: string; - name: string; - email: string; - emailVerified: boolean; - masterPasswordHint: string; - premium: boolean; - culture: string; - twoFactorEnabled: boolean; - key: string; - privateKey: string; - securityStamp: string; - organizations: ProfileOrganizationResponse[] = []; - - constructor(response: any) { - this.id = response.Id; - this.name = response.Name; - this.email = response.Email; - this.emailVerified = response.EmailVerified; - this.masterPasswordHint = response.MasterPasswordHint; - this.premium = response.Premium; - this.culture = response.Culture; - this.twoFactorEnabled = response.TwoFactorEnabled; - this.key = response.Key; - this.privateKey = response.PrivateKey; - this.securityStamp = response.SecurityStamp; - - if (response.Organizations) { - response.Organizations.forEach((org: any) => { - this.organizations.push(new ProfileOrganizationResponse(org)); - }); - } - } -} - -export { ProfileResponse }; -(window as any).ProfileResponse = ProfileResponse; diff --git a/src/models/response/syncResponse.ts b/src/models/response/syncResponse.ts deleted file mode 100644 index 8acdc5202dc..00000000000 --- a/src/models/response/syncResponse.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { CipherResponse } from './cipherResponse'; -import { CollectionResponse } from './collectionResponse'; -import { DomainsResponse } from './domainsResponse'; -import { FolderResponse } from './folderResponse'; -import { ProfileResponse } from './profileResponse'; - -class SyncResponse { - profile?: ProfileResponse; - folders: FolderResponse[] = []; - collections: CollectionResponse[] = []; - ciphers: CipherResponse[] = []; - domains?: DomainsResponse; - - constructor(response: any) { - if (response.Profile) { - this.profile = new ProfileResponse(response.Profile); - } - - if (response.Folders) { - response.Folders.forEach((folder: any) => { - this.folders.push(new FolderResponse(folder)); - }); - } - - if (response.Collections) { - response.Collections.forEach((collection: any) => { - this.collections.push(new CollectionResponse(collection)); - }); - } - - if (response.Ciphers) { - response.Ciphers.forEach((cipher: any) => { - this.ciphers.push(new CipherResponse(cipher)); - }); - } - - if (response.Domains) { - this.domains = new DomainsResponse(response.Domains); - } - } -} - -export { SyncResponse }; -(window as any).SyncResponse = SyncResponse; diff --git a/src/services/abstractions/crypto.service.ts b/src/services/abstractions/crypto.service.ts deleted file mode 100644 index 1fd413d34f1..00000000000 --- a/src/services/abstractions/crypto.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CipherString } from '../../models/domain/cipherString'; -import SymmetricCryptoKey from '../../models/domain/symmetricCryptoKey'; - -import { ProfileOrganizationResponse } from '../../models/response/profileOrganizationResponse'; - -export interface CryptoService { - setKey(key: SymmetricCryptoKey): Promise; - setKeyHash(keyHash: string): Promise<{}>; - setEncKey(encKey: string): Promise<{}>; - setEncPrivateKey(encPrivateKey: string): Promise<{}>; - setOrgKeys(orgs: ProfileOrganizationResponse[]): Promise<{}>; - getKey(): Promise; - getKeyHash(): Promise; - getEncKey(): Promise; - getPrivateKey(): Promise; - getOrgKeys(): Promise>; - getOrgKey(orgId: string): Promise; - clearKeys(): Promise; - toggleKey(): Promise; - makeKey(password: string, salt: string): SymmetricCryptoKey; - hashPassword(password: string, key: SymmetricCryptoKey): Promise; - makeEncKey(key: SymmetricCryptoKey): Promise; - encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey, plainValueEncoding?: string): Promise; - encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise; - decrypt(cipherString: CipherString, key?: SymmetricCryptoKey, outputEncoding?: string): Promise; - decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise; - rsaDecrypt(encValue: string): Promise; -} diff --git a/src/services/abstractions/utils.service.ts b/src/services/abstractions/utils.service.ts deleted file mode 100644 index e52eb44fec2..00000000000 --- a/src/services/abstractions/utils.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { BrowserType } from '../../enums/browserType.enum'; - -export interface UtilsService { - getBrowser(): BrowserType; - getBrowserString(): string; - isFirefox(): boolean; - isChrome(): boolean; - isEdge(): boolean; - isOpera(): boolean; - analyticsId(): string; - initListSectionItemListeners(doc: Document, angular: any): void; - copyToClipboard(text: string, doc?: Document): void; - getDomain(uriString: string): string; - getHostname(uriString: string): string; - inSidebar(theWindow: Window): boolean; - inTab(theWindow: Window): boolean; - inPopout(theWindow: Window): boolean; - inPopup(theWindow: Window): boolean; - saveObjToStorage(key: string, obj: any): Promise; - removeFromStorage(key: string): Promise; - getObjFromStorage(key: string): Promise; -} diff --git a/src/services/api.service.ts b/src/services/api.service.ts deleted file mode 100644 index d97ec6c47aa..00000000000 --- a/src/services/api.service.ts +++ /dev/null @@ -1,454 +0,0 @@ -import AppIdService from './appId.service'; -import ConstantsService from './constants.service'; -import TokenService from './token.service'; -import UtilsService from './utils.service'; - -import EnvironmentUrls from '../models/domain/environmentUrls'; - -import { CipherRequest } from '../models/request/cipherRequest'; -import { DeviceRequest } from '../models/request/deviceRequest'; -import { DeviceTokenRequest } from '../models/request/deviceTokenRequest'; -import { FolderRequest } from '../models/request/folderRequest'; -import { PasswordHintRequest } from '../models/request/passwordHintRequest'; -import { RegisterRequest } from '../models/request/registerRequest'; -import { TokenRequest } from '../models/request/tokenRequest'; -import { TwoFactorEmailRequest } from '../models/request/twoFactorEmailRequest'; - -import { AttachmentResponse } from '../models/response/attachmentResponse'; -import { CipherResponse } from '../models/response/cipherResponse'; -import { DeviceResponse } from '../models/response/deviceResponse'; -import { DomainsResponse } from '../models/response/domainsResponse'; -import { ErrorResponse } from '../models/response/errorResponse'; -import { FolderResponse } from '../models/response/folderResponse'; -import { GlobalDomainResponse } from '../models/response/globalDomainResponse'; -import { IdentityTokenResponse } from '../models/response/identityTokenResponse'; -import { KeysResponse } from '../models/response/keysResponse'; -import { ListResponse } from '../models/response/listResponse'; -import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; -import { ProfileResponse } from '../models/response/profileResponse'; -import { SyncResponse } from '../models/response/syncResponse'; - -export default class ApiService { - urlsSet: boolean = false; - baseUrl: string; - identityBaseUrl: string; - logoutCallback: Function; - - constructor(private tokenService: TokenService, private utilsService: UtilsService, - logoutCallback: Function) { - this.logoutCallback = logoutCallback; - } - - setUrls(urls: EnvironmentUrls) { - this.urlsSet = true; - - if (urls.base != null) { - this.baseUrl = urls.base + '/api'; - this.identityBaseUrl = urls.base + '/identity'; - return; - } - - if (urls.api != null && urls.identity != null) { - this.baseUrl = urls.api; - this.identityBaseUrl = urls.identity; - return; - } - - /* tslint:disable */ - // Desktop - //this.baseUrl = 'http://localhost:4000'; - //this.identityBaseUrl = 'http://localhost:33656'; - - // Desktop HTTPS - //this.baseUrl = 'https://localhost:44377'; - //this.identityBaseUrl = 'https://localhost:44392'; - - // Desktop external - //this.baseUrl = 'http://192.168.1.3:4000'; - //this.identityBaseUrl = 'http://192.168.1.3:33656'; - - // Preview - //this.baseUrl = 'https://preview-api.bitwarden.com'; - //this.identityBaseUrl = 'https://preview-identity.bitwarden.com'; - - // Production - this.baseUrl = 'https://api.bitwarden.com'; - this.identityBaseUrl = 'https://identity.bitwarden.com'; - /* tslint:enable */ - } - - // Auth APIs - - async postIdentityToken(request: TokenRequest): Promise { - const response = await fetch(new Request(this.identityBaseUrl + '/connect/token', { - body: this.qsStringify(request.toIdentityToken()), - cache: 'no-cache', - headers: new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', - 'Accept': 'application/json', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - let responseJson: any = null; - const typeHeader = response.headers.get('content-type'); - if (typeHeader != null && typeHeader.indexOf('application/json') > -1) { - responseJson = await response.json(); - } - - if (responseJson != null) { - if (response.status === 200) { - return new IdentityTokenResponse(responseJson); - } else if (response.status === 400 && responseJson.TwoFactorProviders2 && - Object.keys(responseJson.TwoFactorProviders2).length) { - await this.tokenService.clearTwoFactorToken(request.email); - return responseJson.TwoFactorProviders2; - } - } - - return Promise.reject(new ErrorResponse(responseJson, response.status, true)); - } - - async refreshIdentityToken(): Promise { - try { - await this.doRefreshToken(); - } catch (e) { - return Promise.reject(null); - } - } - - // Two Factor APIs - - async postTwoFactorEmail(request: TwoFactorEmailRequest): Promise { - const response = await fetch(new Request(this.baseUrl + '/two-factor/send-email-login', { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Account APIs - - async getAccountRevisionDate(): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/accounts/revision-date', { - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - })); - - if (response.status === 200) { - return (await response.json() as number); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async postPasswordHint(request: PasswordHintRequest): Promise { - const response = await fetch(new Request(this.baseUrl + '/accounts/password-hint', { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async postRegister(request: RegisterRequest): Promise { - const response = await fetch(new Request(this.baseUrl + '/accounts/register', { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Folder APIs - - async postFolder(request: FolderRequest): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/folders', { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new FolderResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async putFolder(id: string, request: FolderRequest): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/folders/' + id, { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'PUT', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new FolderResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async deleteFolder(id: string): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/folders/' + id, { - cache: 'no-cache', - headers: new Headers({ - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'DELETE', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Cipher APIs - - async postCipher(request: CipherRequest): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/ciphers', { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new CipherResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async putCipher(id: string, request: CipherRequest): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id, { - body: JSON.stringify(request), - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Content-Type': 'application/json; charset=utf-8', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'PUT', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new CipherResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async deleteCipher(id: string): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id, { - cache: 'no-cache', - headers: new Headers({ - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'DELETE', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Attachments APIs - - async postCipherAttachment(id: string, data: FormData): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id + '/attachment', { - body: data, - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new CipherResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - async deleteCipherAttachment(id: string, attachmentId: string): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/ciphers/' + id + '/attachment/' + attachmentId, { - cache: 'no-cache', - headers: new Headers({ - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'DELETE', - })); - - if (response.status !== 200) { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Sync APIs - - async getSync(): Promise { - const authHeader = await this.handleTokenState(); - const response = await fetch(new Request(this.baseUrl + '/sync', { - cache: 'no-cache', - headers: new Headers({ - 'Accept': 'application/json', - 'Authorization': authHeader, - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - })); - - if (response.status === 200) { - const responseJson = await response.json(); - return new SyncResponse(responseJson); - } else { - const error = await this.handleError(response, false); - return Promise.reject(error); - } - } - - // Helpers - - private async handleError(response: Response, tokenError: boolean): Promise { - if ((tokenError && response.status === 400) || response.status === 401 || response.status === 403) { - this.logoutCallback(true); - return null; - } - - let responseJson: any = null; - const typeHeader = response.headers.get('content-type'); - if (typeHeader != null && typeHeader.indexOf('application/json') > -1) { - responseJson = await response.json(); - } - - return new ErrorResponse(responseJson, response.status, tokenError); - } - - private async handleTokenState(): Promise { - let accessToken: string; - if (this.tokenService.tokenNeedsRefresh()) { - const tokenResponse = await this.doRefreshToken(); - accessToken = tokenResponse.accessToken; - } else { - accessToken = await this.tokenService.getToken(); - } - - return 'Bearer ' + accessToken; - } - - private async doRefreshToken(): Promise { - const refreshToken = await this.tokenService.getRefreshToken(); - if (refreshToken == null || refreshToken === '') { - throw new Error(); - } - - const response = await fetch(new Request(this.identityBaseUrl + '/connect/token', { - body: this.qsStringify({ - grant_type: 'refresh_token', - client_id: 'browser', - refresh_token: refreshToken, - }), - cache: 'no-cache', - headers: new Headers({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', - 'Accept': 'application/json', - 'Device-Type': this.utilsService.getBrowser().toString(), - }), - method: 'POST', - })); - - if (response.status === 200) { - const responseJson = await response.json(); - const tokenResponse = new IdentityTokenResponse(responseJson); - await this.tokenService.setTokens(tokenResponse.accessToken, tokenResponse.refreshToken); - return tokenResponse; - } else { - const error = await this.handleError(response, true); - return Promise.reject(error); - } - } - - private qsStringify(params: any): string { - return Object.keys(params).map((key) => { - return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); - }).join('&'); - } -} diff --git a/src/services/appId.service.ts b/src/services/appId.service.ts deleted file mode 100644 index d4e0dce5cb8..00000000000 --- a/src/services/appId.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import UtilsService from './utils.service'; - -export default class AppIdService { - static getAppId(): Promise { - return AppIdService.makeAndGetAppId('appId'); - } - - static getAnonymousAppId(): Promise { - return AppIdService.makeAndGetAppId('anonymousAppId'); - } - - private static async makeAndGetAppId(key: string) { - const existingId = await UtilsService.getObjFromStorage(key); - if (existingId != null) { - return existingId; - } - - const guid = UtilsService.newGuid(); - await UtilsService.saveObjToStorage(key, guid); - return guid; - } - - // TODO: remove these in favor of static methods - getAppId(): Promise { - return AppIdService.getAppId(); - } - - getAnonymousAppId(): Promise { - return AppIdService.getAnonymousAppId(); - } -} diff --git a/src/services/autofill.service.ts b/src/services/autofill.service.ts deleted file mode 100644 index 2b2593bffe9..00000000000 --- a/src/services/autofill.service.ts +++ /dev/null @@ -1,872 +0,0 @@ -import { CipherType } from '../enums/cipherType.enum'; -import { FieldType } from '../enums/fieldType.enum'; - -import AutofillField from '../models/domain/autofillField'; -import AutofillPageDetails from '../models/domain/autofillPageDetails'; -import AutofillScript from '../models/domain/autofillScript'; - -import CipherService from './cipher.service'; -import TokenService from './token.service'; -import TotpService from './totp.service'; -import UtilsService from './utils.service'; - -const CardAttributes: string[] = ['autoCompleteType', 'data-stripe', 'htmlName', 'htmlID', 'label-tag', - 'placeholder', 'label-left', 'label-top']; - -const IdentityAttributes: string[] = ['autoCompleteType', 'data-stripe', 'htmlName', 'htmlID', 'label-tag', - 'placeholder', 'label-left', 'label-top']; - -const UsernameFieldNames: string[] = [ - // English - 'username', 'user name', 'email', 'email address', 'e-mail', 'e-mail address', 'userid', 'user id', - // German - 'benutzername', 'benutzer name', 'email adresse', 'e-mail adresse', 'benutzerid', 'benutzer id']; - -/* tslint:disable */ -const IsoCountries: { [id: string]: string; } = { - afghanistan: "AF", "aland islands": "AX", albania: "AL", algeria: "DZ", "american samoa": "AS", andorra: "AD", - angola: "AO", anguilla: "AI", antarctica: "AQ", "antigua and barbuda": "AG", argentina: "AR", armenia: "AM", - aruba: "AW", australia: "AU", austria: "AT", azerbaijan: "AZ", bahamas: "BS", bahrain: "BH", bangladesh: "BD", - barbados: "BB", belarus: "BY", belgium: "BE", belize: "BZ", benin: "BJ", bermuda: "BM", bhutan: "BT", bolivia: "BO", - "bosnia and herzegovina": "BA", botswana: "BW", "bouvet island": "BV", brazil: "BR", - "british indian ocean territory": "IO", "brunei darussalam": "BN", bulgaria: "BG", "burkina faso": "BF", burundi: "BI", - cambodia: "KH", cameroon: "CM", canada: "CA", "cape verde": "CV", "cayman islands": "KY", - "central african republic": "CF", chad: "TD", chile: "CL", china: "CN", "christmas island": "CX", - "cocos (keeling) islands": "CC", colombia: "CO", comoros: "KM", congo: "CG", "congo, democratic republic": "CD", - "cook islands": "CK", "costa rica": "CR", "cote d'ivoire": "CI", croatia: "HR", cuba: "CU", cyprus: "CY", - "czech republic": "CZ", denmark: "DK", djibouti: "DJ", dominica: "DM", "dominican republic": "DO", ecuador: "EC", - egypt: "EG", "el salvador": "SV", "equatorial guinea": "GQ", eritrea: "ER", estonia: "EE", ethiopia: "ET", - "falkland islands": "FK", "faroe islands": "FO", fiji: "FJ", finland: "FI", france: "FR", "french guiana": "GF", - "french polynesia": "PF", "french southern territories": "TF", gabon: "GA", gambia: "GM", georgia: "GE", germany: "DE", - ghana: "GH", gibraltar: "GI", greece: "GR", greenland: "GL", grenada: "GD", guadeloupe: "GP", guam: "GU", - guatemala: "GT", guernsey: "GG", guinea: "GN", "guinea-bissau": "GW", guyana: "GY", haiti: "HT", - "heard island & mcdonald islands": "HM", "holy see (vatican city state)": "VA", honduras: "HN", "hong kong": "HK", - hungary: "HU", iceland: "IS", india: "IN", indonesia: "ID", "iran, islamic republic of": "IR", iraq: "IQ", - ireland: "IE", "isle of man": "IM", israel: "IL", italy: "IT", jamaica: "JM", japan: "JP", jersey: "JE", - jordan: "JO", kazakhstan: "KZ", kenya: "KE", kiribati: "KI", "republic of korea": "KR", "south korea": "KR", - "democratic people's republic of korea": "KP", "north korea": "KP", kuwait: "KW", kyrgyzstan: "KG", - "lao people's democratic republic": "LA", latvia: "LV", lebanon: "LB", lesotho: "LS", liberia: "LR", - "libyan arab jamahiriya": "LY", liechtenstein: "LI", lithuania: "LT", luxembourg: "LU", macao: "MO", macedonia: "MK", - madagascar: "MG", malawi: "MW", malaysia: "MY", maldives: "MV", mali: "ML", malta: "MT", "marshall islands": "MH", - martinique: "MQ", mauritania: "MR", mauritius: "MU", mayotte: "YT", mexico: "MX", - "micronesia, federated states of": "FM", moldova: "MD", monaco: "MC", mongolia: "MN", montenegro: "ME", montserrat: "MS", - morocco: "MA", mozambique: "MZ", myanmar: "MM", namibia: "NA", nauru: "NR", nepal: "NP", netherlands: "NL", - "netherlands antilles": "AN", "new caledonia": "NC", "new zealand": "NZ", nicaragua: "NI", niger: "NE", nigeria: "NG", - niue: "NU", "norfolk island": "NF", "northern mariana islands": "MP", norway: "NO", oman: "OM", pakistan: "PK", - palau: "PW", "palestinian territory, occupied": "PS", panama: "PA", "papua new guinea": "PG", paraguay: "PY", peru: "PE", - philippines: "PH", pitcairn: "PN", poland: "PL", portugal: "PT", "puerto rico": "PR", qatar: "QA", reunion: "RE", - romania: "RO", "russian federation": "RU", rwanda: "RW", "saint barthelemy": "BL", "saint helena": "SH", - "saint kitts and nevis": "KN", "saint lucia": "LC", "saint martin": "MF", "saint pierre and miquelon": "PM", - "saint vincent and grenadines": "VC", samoa: "WS", "san marino": "SM", "sao tome and principe": "ST", - "saudi arabia": "SA", senegal: "SN", serbia: "RS", seychelles: "SC", "sierra leone": "SL", singapore: "SG", - slovakia: "SK", slovenia: "SI", "solomon islands": "SB", somalia: "SO", "south africa": "ZA", - "south georgia and sandwich isl.": "GS", spain: "ES", "sri lanka": "LK", sudan: "SD", suriname: "SR", - "svalbard and jan mayen": "SJ", swaziland: "SZ", sweden: "SE", switzerland: "CH", "syrian arab republic": "SY", - taiwan: "TW", tajikistan: "TJ", tanzania: "TZ", thailand: "TH", "timor-leste": "TL", togo: "TG", tokelau: "TK", - tonga: "TO", "trinidad and tobago": "TT", tunisia: "TN", turkey: "TR", turkmenistan: "TM", - "turks and caicos islands": "TC", tuvalu: "TV", uganda: "UG", ukraine: "UA", "united arab emirates": "AE", - "united kingdom": "GB", "united states": "US", "united states outlying islands": "UM", uruguay: "UY", - uzbekistan: "UZ", vanuatu: "VU", venezuela: "VE", vietnam: "VN", "virgin islands, british": "VG", - "virgin islands, u.s.": "VI", "wallis and futuna": "WF", "western sahara": "EH", yemen: "YE", zambia: "ZM", - zimbabwe: "ZW", -}; - -const IsoStates: { [id: string]: string; } = { - alabama: 'AL', alaska: 'AK', 'american samoa': 'AS', arizona: 'AZ', arkansas: 'AR', california: 'CA', colorado: 'CO', - connecticut: 'CT', delaware: 'DE', 'district of columbia': 'DC', 'federated states of micronesia': 'FM', florida: 'FL', - georgia: 'GA', guam: 'GU', hawaii: 'HI', idaho: 'ID', illinois: 'IL', indiana: 'IN', iowa: 'IA', kansas: 'KS', - kentucky: 'KY', louisiana: 'LA', maine: 'ME', 'marshall islands': 'MH', maryland: 'MD', massachusetts: 'MA', - michigan: 'MI', minnesota: 'MN', mississippi: 'MS', missouri: 'MO', montana: 'MT', nebraska: 'NE', nevada: 'NV', - 'new hampshire': 'NH', 'new jersey': 'NJ', 'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', - 'north dakota': 'ND', 'northern mariana islands': 'MP', ohio: 'OH', oklahoma: 'OK', oregon: 'OR', palau: 'PW', - pennsylvania: 'PA', 'puerto rico': 'PR', 'rhode island': 'RI', 'south carolina': 'SC', 'south dakota': 'SD', - tennessee: 'TN', texas: 'TX', utah: 'UT', vermont: 'VT', 'virgin islands': 'VI', virginia: 'VA', washington: 'WA', - 'west virginia': 'WV', wisconsin: 'WI', wyoming: 'WY', -}; - -var IsoProvinces: { [id: string]: string; } = { - alberta: 'AB', 'british columbia': 'BC', manitoba: 'MB', 'new brunswick': 'NB', 'newfoundland and labrador': 'NL', - 'nova scotia': 'NS', ontario: 'ON', 'prince edward island': 'PE', quebec: 'QC', saskatchewan: 'SK', -}; -/* tslint:enable */ - -export default class AutofillService { - constructor(public cipherService: CipherService, public tokenService: TokenService, - public totpService: TotpService, public utilsService: UtilsService) { - } - - getFormsWithPasswordFields(pageDetails: AutofillPageDetails): any[] { - const formData: any[] = []; - - const passwordFields = this.loadPasswordFields(pageDetails, true); - if (passwordFields.length === 0) { - return formData; - } - - for (const formKey in pageDetails.forms) { - if (!pageDetails.forms.hasOwnProperty(formKey)) { - continue; - } - - for (let i = 0; i < passwordFields.length; i++) { - const pf = passwordFields[i]; - if (formKey !== pf.form) { - continue; - } - - let uf = this.findUsernameField(pageDetails, pf, false, false); - if (uf == null) { - // not able to find any viewable username fields. maybe there are some "hidden" ones? - uf = this.findUsernameField(pageDetails, pf, true, false); - } - - formData.push({ - form: pageDetails.forms[formKey], - password: pf, - username: uf, - }); - break; - } - } - - return formData; - } - - async doAutoFill(options: any) { - let totpPromise: Promise = null; - const tab = await this.getActiveTab(); - if (!tab || !options.cipher || !options.pageDetails || !options.pageDetails.length) { - throw new Error('Nothing to auto-fill.'); - } - - let didAutofill = false; - options.pageDetails.forEach((pd: any) => { - // make sure we're still on correct tab - if (pd.tab.id !== tab.id || pd.tab.url !== tab.url) { - return; - } - - const fillScript = this.generateFillScript(pd.details, { - skipUsernameOnlyFill: options.skipUsernameOnlyFill || false, - cipher: options.cipher, - }); - - if (!fillScript || !fillScript.script || !fillScript.script.length) { - return; - } - - didAutofill = true; - if (!options.skipLastUsed) { - this.cipherService.updateLastUsedDate(options.cipher.id); - } - - chrome.tabs.sendMessage(tab.id, { - command: 'fillForm', - // tslint:disable-next-line - fillScript: fillScript, - }, { frameId: pd.frameId }); - - if (options.cipher.type !== CipherType.Login || totpPromise || - (options.fromBackground && this.utilsService.isFirefox()) || options.skipTotp || - !options.cipher.login.totp || !this.tokenService.getPremium()) { - return; - } - - totpPromise = this.totpService.isAutoCopyEnabled().then((enabled) => { - if (enabled) { - return this.totpService.getCode(options.cipher.login.totp); - } - - return null; - }).then((code: string) => { - if (code) { - UtilsService.copyToClipboard(code); - } - - return code; - }); - }); - - if (didAutofill) { - if (totpPromise != null) { - const totpCode = await totpPromise; - return totpCode; - } else { - return null; - } - } else { - throw new Error('Did not auto-fill.'); - } - } - - async doAutoFillForLastUsedLogin(pageDetails: any, fromCommand: boolean) { - const tab = await this.getActiveTab(); - if (!tab || !tab.url) { - return; - } - - const tabDomain = UtilsService.getDomain(tab.url); - if (tabDomain == null) { - return; - } - - const lastUsedCipher = await this.cipherService.getLastUsedForDomain(tabDomain); - if (!lastUsedCipher) { - return; - } - - await this.doAutoFill({ - cipher: lastUsedCipher, - // tslint:disable-next-line - pageDetails: pageDetails, - fromBackground: true, - skipTotp: !fromCommand, - skipLastUsed: true, - skipUsernameOnlyFill: !fromCommand, - }); - } - - // Helpers - - private getActiveTab(): Promise { - return new Promise((resolve, reject) => { - chrome.tabs.query({ active: true, currentWindow: true }, (tabs: any[]) => { - if (tabs.length === 0) { - reject('No tab found.'); - } else { - resolve(tabs[0]); - } - }); - }); - } - - private generateFillScript(pageDetails: AutofillPageDetails, options: any): AutofillScript { - if (!pageDetails || !options.cipher) { - return null; - } - - let fillScript = new AutofillScript(pageDetails.documentUUID); - const filledFields: { [id: string]: AutofillField; } = {}; - const fields = options.cipher.fields; - - if (fields && fields.length) { - const fieldNames: string[] = []; - - fields.forEach((f: any) => { - if (this.hasValue(f.name)) { - fieldNames.push(f.name.toLowerCase()); - } else { - fieldNames.push(null); - } - }); - - pageDetails.fields.forEach((field: any) => { - if (filledFields.hasOwnProperty(field.opid) || !field.viewable) { - return; - } - - const matchingIndex = this.findMatchingFieldIndex(field, fieldNames); - if (matchingIndex > -1) { - let val = fields[matchingIndex].value; - if (val == null && fields[matchingIndex].type === FieldType.Boolean) { - val = 'false'; - } - - filledFields[field.opid] = field; - fillScript.script.push(['click_on_opid', field.opid]); - fillScript.script.push(['fill_by_opid', field.opid, val]); - } - }); - } - - switch (options.cipher.type) { - case CipherType.Login: - fillScript = this.generateLoginFillScript(fillScript, pageDetails, filledFields, options); - break; - case CipherType.Card: - fillScript = this.generateCardFillScript(fillScript, pageDetails, filledFields, options); - break; - case CipherType.Identity: - fillScript = this.generateIdentityFillScript(fillScript, pageDetails, filledFields, options); - break; - default: - return null; - } - - return fillScript; - } - - private generateLoginFillScript(fillScript: AutofillScript, pageDetails: any, - filledFields: { [id: string]: AutofillField; }, options: any): AutofillScript { - if (!options.cipher.login) { - return null; - } - - const passwords: AutofillField[] = []; - const usernames: AutofillField[] = []; - let pf: AutofillField = null; - let username: AutofillField = null; - const login = options.cipher.login; - - if (!login.password || login.password === '') { - // No password for this login. Maybe they just wanted to auto-fill some custom fields? - fillScript = this.setFillScriptForFocus(filledFields, fillScript); - return fillScript; - } - - let passwordFields = this.loadPasswordFields(pageDetails, false); - if (!passwordFields.length) { - // not able to find any viewable password fields. maybe there are some "hidden" ones? - passwordFields = this.loadPasswordFields(pageDetails, true); - } - - for (const formKey in pageDetails.forms) { - if (!pageDetails.forms.hasOwnProperty(formKey)) { - continue; - } - - const passwordFieldsForForm: AutofillField[] = []; - passwordFields.forEach((passField) => { - if (formKey === passField.form) { - passwordFieldsForForm.push(passField); - } - }); - - passwordFields.forEach((passField) => { - pf = passField; - passwords.push(pf); - - if (login.username) { - username = this.findUsernameField(pageDetails, pf, false, false); - - if (!username) { - // not able to find any viewable username fields. maybe there are some "hidden" ones? - username = this.findUsernameField(pageDetails, pf, true, false); - } - - if (username) { - usernames.push(username); - } - } - }); - } - - if (passwordFields.length && !passwords.length) { - // The page does not have any forms with password fields. Use the first password field on the page and the - // input field just before it as the username. - - pf = passwordFields[0]; - passwords.push(pf); - - if (login.username && pf.elementNumber > 0) { - username = this.findUsernameField(pageDetails, pf, false, true); - - if (!username) { - // not able to find any viewable username fields. maybe there are some "hidden" ones? - username = this.findUsernameField(pageDetails, pf, true, true); - } - - if (username) { - usernames.push(username); - } - } - } - - if (!passwordFields.length && !options.skipUsernameOnlyFill) { - // No password fields on this page. Let's try to just fuzzy fill the username. - pageDetails.fields.forEach((f: any) => { - if (f.viewable && (f.type === 'text' || f.type === 'email' || f.type === 'tel') && - this.fieldIsFuzzyMatch(f, UsernameFieldNames)) { - usernames.push(f); - } - }); - } - - usernames.forEach((u) => { - if (filledFields.hasOwnProperty(u.opid)) { - return; - } - - filledFields[u.opid] = u; - fillScript.script.push(['click_on_opid', u.opid]); - fillScript.script.push(['fill_by_opid', u.opid, login.username]); - }); - - passwords.forEach((p) => { - if (filledFields.hasOwnProperty(p.opid)) { - return; - } - - filledFields[p.opid] = p; - fillScript.script.push(['click_on_opid', p.opid]); - fillScript.script.push(['fill_by_opid', p.opid, login.password]); - }); - - fillScript = this.setFillScriptForFocus(filledFields, fillScript); - return fillScript; - } - - private generateCardFillScript(fillScript: AutofillScript, pageDetails: any, - filledFields: { [id: string]: AutofillField; }, options: any): AutofillScript { - if (!options.cipher.card) { - return null; - } - - const fillFields: { [id: string]: AutofillField; } = {}; - - pageDetails.fields.forEach((f: any) => { - CardAttributes.forEach((attr) => { - if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) { - return; - } - - // ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill - // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ - if (!fillFields.cardholderName && this.isFieldMatch(f[attr], - ['cc-name', 'card-name', 'cardholder-name', 'cardholder', 'name'], - ['cc-name', 'card-name', 'cardholder-name', 'cardholder'])) { - fillFields.cardholderName = f; - } else if (!fillFields.number && this.isFieldMatch(f[attr], - ['cc-number', 'cc-num', 'card-number', 'card-num', 'number'], - ['cc-number', 'cc-num', 'card-number', 'card-num'])) { - fillFields.number = f; - } else if (!fillFields.exp && this.isFieldMatch(f[attr], - ['cc-exp', 'card-exp', 'cc-expiration', 'card-expiration', 'cc-ex', 'card-ex'], - [])) { - fillFields.exp = f; - } else if (!fillFields.expMonth && this.isFieldMatch(f[attr], - ['exp-month', 'cc-exp-month', 'cc-month', 'card-month', 'cc-mo', 'card-mo', 'exp-mo', - 'card-exp-mo', 'cc-exp-mo', 'card-expiration-month', 'expiration-month', - 'cc-mm', 'card-mm', 'card-exp-mm', 'cc-exp-mm', 'exp-mm'])) { - fillFields.expMonth = f; - } else if (!fillFields.expYear && this.isFieldMatch(f[attr], - ['exp-year', 'cc-exp-year', 'cc-year', 'card-year', 'cc-yr', 'card-yr', 'exp-yr', - 'card-exp-yr', 'cc-exp-yr', 'card-expiration-year', 'expiration-year', - 'cc-yy', 'card-yy', 'card-exp-yy', 'cc-exp-yy', 'exp-yy', - 'cc-yyyy', 'card-yyyy', 'card-exp-yyyy', 'cc-exp-yyyy'])) { - fillFields.expYear = f; - } else if (!fillFields.code && this.isFieldMatch(f[attr], - ['cvv', 'cvc', 'cvv2', 'cc-csc', 'cc-cvv', 'card-csc', 'card-cvv', 'cvd', - 'cid', 'cvc2', 'cnv', 'cvn2', 'cc-code', 'card-code'])) { - fillFields.code = f; - } else if (!fillFields.brand && this.isFieldMatch(f[attr], - ['cc-type', 'card-type', 'card-brand', 'cc-brand'])) { - fillFields.brand = f; - } - }); - }); - - const card = options.cipher.card; - this.makeScriptAction(fillScript, card, fillFields, filledFields, 'cardholderName'); - this.makeScriptAction(fillScript, card, fillFields, filledFields, 'number'); - this.makeScriptAction(fillScript, card, fillFields, filledFields, 'expYear'); - this.makeScriptAction(fillScript, card, fillFields, filledFields, 'code'); - this.makeScriptAction(fillScript, card, fillFields, filledFields, 'brand'); - - if (fillFields.expMonth && this.hasValue(card.expMonth)) { - let expMonth = card.expMonth; - - if (fillFields.expMonth.selectInfo && fillFields.expMonth.selectInfo.options) { - let index: number = null; - if (fillFields.expMonth.selectInfo.options.length === 12) { - index = parseInt(card.expMonth, null) - 1; - } else if (fillFields.expMonth.selectInfo.options.length === 13) { - index = parseInt(card.expMonth, null); - } - - if (index != null) { - const option = fillFields.expMonth.selectInfo.options[index]; - if (option.length > 1) { - expMonth = option[1]; - } - } - } - - filledFields[fillFields.expMonth.opid] = fillFields.expMonth; - fillScript.script.push(['click_on_opid', fillFields.expMonth.opid]); - fillScript.script.push(['fill_by_opid', fillFields.expMonth.opid, expMonth]); - } - - if (fillFields.exp && this.hasValue(card.expMonth) && this.hasValue(card.expYear)) { - let year = card.expYear; - if (year.length === 2) { - year = '20' + year; - } - - const exp = year + '-' + ('0' + card.expMonth).slice(-2); - this.makeScriptActionWithValue(fillScript, exp, fillFields.exp, filledFields); - } - - return fillScript; - } - - private generateIdentityFillScript(fillScript: AutofillScript, pageDetails: any, - filledFields: { [id: string]: AutofillField; }, options: any): AutofillScript { - if (!options.cipher.identity) { - return null; - } - - const fillFields: { [id: string]: AutofillField; } = {}; - - pageDetails.fields.forEach((f: any) => { - IdentityAttributes.forEach((attr) => { - if (!f.hasOwnProperty(attr) || !f[attr] || !f.viewable) { - return; - } - - // ref https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill - // ref https://developers.google.com/web/fundamentals/design-and-ux/input/forms/ - if (!fillFields.name && this.isFieldMatch(f[attr], - ['name', 'full-name', 'your-name'], ['full-name', 'your-name'])) { - fillFields.name = f; - } else if (!fillFields.firstName && this.isFieldMatch(f[attr], - ['f-name', 'first-name', 'given-name', 'first-n'])) { - fillFields.firstName = f; - } else if (!fillFields.middleName && this.isFieldMatch(f[attr], - ['m-name', 'middle-name', 'additional-name', 'middle-initial', 'middle-n', 'middle-i'])) { - fillFields.middleName = f; - } else if (!fillFields.lastName && this.isFieldMatch(f[attr], - ['l-name', 'last-name', 's-name', 'surname', 'family-name', 'family-n', 'last-n'])) { - fillFields.lastName = f; - } else if (!fillFields.title && this.isFieldMatch(f[attr], - ['honorific-prefix', 'prefix', 'title'])) { - fillFields.title = f; - } else if (!fillFields.email && this.isFieldMatch(f[attr], - ['e-mail', 'email-address'])) { - fillFields.email = f; - } else if (!fillFields.address && this.isFieldMatch(f[attr], - ['address', 'street-address', 'addr'], [])) { - fillFields.address = f; - } else if (!fillFields.address1 && this.isFieldMatch(f[attr], - ['address-1', 'address-line-1', 'addr-1'])) { - fillFields.address1 = f; - } else if (!fillFields.address2 && this.isFieldMatch(f[attr], - ['address-2', 'address-line-2', 'addr-2'])) { - fillFields.address2 = f; - } else if (!fillFields.address3 && this.isFieldMatch(f[attr], - ['address-3', 'address-line-3', 'addr-3'])) { - fillFields.address3 = f; - } else if (!fillFields.postalCode && this.isFieldMatch(f[attr], - ['postal', 'zip', 'zip2', 'zip-code', 'postal-code', 'post-code', 'address-zip', - 'address-postal', 'address-code', 'address-postal-code', 'address-zip-code'])) { - fillFields.postalCode = f; - } else if (!fillFields.city && this.isFieldMatch(f[attr], - ['city', 'town', 'address-level-2', 'address-city', 'address-town'])) { - fillFields.city = f; - } else if (!fillFields.state && this.isFieldMatch(f[attr], - ['state', 'province', 'provence', 'address-level-1', 'address-state', - 'address-province'])) { - fillFields.state = f; - } else if (!fillFields.country && this.isFieldMatch(f[attr], - ['country', 'country-code', 'country-name', 'address-country', 'address-country-name', - 'address-country-code'])) { - fillFields.country = f; - } else if (!fillFields.phone && this.isFieldMatch(f[attr], - ['phone', 'mobile', 'mobile-phone', 'tel', 'telephone', 'phone-number'])) { - fillFields.phone = f; - } else if (!fillFields.username && this.isFieldMatch(f[attr], - ['user-name', 'user-id', 'screen-name'])) { - fillFields.username = f; - } else if (!fillFields.company && this.isFieldMatch(f[attr], - ['company', 'company-name', 'organization', 'organization-name'])) { - fillFields.company = f; - } - }); - }); - - const identity = options.cipher.identity; - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'title'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'firstName'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'middleName'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'lastName'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'address1'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'address2'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'address3'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'city'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'postalCode'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'company'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'email'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'phone'); - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'username'); - - let filledState = false; - if (fillFields.state && identity.state && identity.state.length > 2) { - const stateLower = identity.state.toLowerCase(); - const isoState = IsoStates[stateLower] || IsoProvinces[stateLower]; - if (isoState) { - filledState = true; - this.makeScriptActionWithValue(fillScript, isoState, fillFields.state, filledFields); - } - } - - if (!filledState) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'state'); - } - - let filledCountry = false; - if (fillFields.country && identity.country && identity.country.length > 2) { - const countryLower = identity.country.toLowerCase(); - const isoCountry = IsoCountries[countryLower]; - if (isoCountry) { - filledCountry = true; - this.makeScriptActionWithValue(fillScript, isoCountry, fillFields.country, filledFields); - } - } - - if (!filledCountry) { - this.makeScriptAction(fillScript, identity, fillFields, filledFields, 'country'); - } - - if (fillFields.name && (identity.firstName || identity.lastName)) { - let fullName = ''; - if (this.hasValue(identity.firstName)) { - fullName = identity.firstName; - } - if (this.hasValue(identity.middleName)) { - if (fullName !== '') { - fullName += ' '; - } - fullName += identity.middleName; - } - if (this.hasValue(identity.lastName)) { - if (fullName !== '') { - fullName += ' '; - } - fullName += identity.lastName; - } - - this.makeScriptActionWithValue(fillScript, fullName, fillFields.name, filledFields); - } - - if (fillFields.address && this.hasValue(identity.address1)) { - let address = ''; - if (this.hasValue(identity.address1)) { - address = identity.address1; - } - if (this.hasValue(identity.address2)) { - if (address !== '') { - address += ', '; - } - address += identity.address2; - } - if (this.hasValue(identity.address3)) { - if (address !== '') { - address += ', '; - } - address += identity.address3; - } - - this.makeScriptActionWithValue(fillScript, address, fillFields.address, filledFields); - } - - return fillScript; - } - - private isFieldMatch(value: string, options: string[], containsOptions?: string[]): boolean { - value = value.trim().toLowerCase().replace(/[^a-zA-Z]+/g, ''); - for (let i = 0; i < options.length; i++) { - let option = options[i]; - const checkValueContains = containsOptions == null || containsOptions.indexOf(option) > -1; - option = option.replace(/-/g, ''); - if (value === option || (checkValueContains && value.indexOf(option) > -1)) { - return true; - } - } - - return false; - } - - private makeScriptAction(fillScript: AutofillScript, cipherData: any, fillFields: { [id: string]: AutofillField; }, - filledFields: { [id: string]: AutofillField; }, dataProp: string, fieldProp?: string) { - fieldProp = fieldProp || dataProp; - this.makeScriptActionWithValue(fillScript, cipherData[dataProp], fillFields[fieldProp], filledFields); - } - - private makeScriptActionWithValue(fillScript: AutofillScript, dataValue: any, field: AutofillField, - filledFields: { [id: string]: AutofillField; }) { - - let doFill = false; - if (this.hasValue(dataValue) && field) { - if (field.type === 'select-one' && field.selectInfo && field.selectInfo.options) { - for (let i = 0; i < field.selectInfo.options.length; i++) { - const option = field.selectInfo.options[i]; - for (let j = 0; j < option.length; j++) { - if (option[j].toLowerCase() === dataValue.toLowerCase()) { - doFill = true; - if (option.length > 1) { - dataValue = option[1]; - } - break; - } - } - - if (doFill) { - break; - } - } - } else { - doFill = true; - } - } - - if (doFill) { - filledFields[field.opid] = field; - fillScript.script.push(['click_on_opid', field.opid]); - fillScript.script.push(['fill_by_opid', field.opid, dataValue]); - } - } - - private loadPasswordFields(pageDetails: AutofillPageDetails, canBeHidden: boolean) { - const arr: AutofillField[] = []; - pageDetails.fields.forEach((f) => { - if (!f.disabled && !f.readonly && f.type === 'password' && (canBeHidden || f.viewable)) { - arr.push(f); - } - }); - - return arr; - } - - private findUsernameField(pageDetails: AutofillPageDetails, passwordField: AutofillField, canBeHidden: boolean, - withoutForm: boolean) { - let usernameField: AutofillField = null; - for (let i = 0; i < pageDetails.fields.length; i++) { - const f = pageDetails.fields[i]; - if (f.elementNumber >= passwordField.elementNumber) { - break; - } - - if (!f.disabled && !f.readonly && - (withoutForm || f.form === passwordField.form) && (canBeHidden || f.viewable) && - (f.type === 'text' || f.type === 'email' || f.type === 'tel')) { - usernameField = f; - - if (this.findMatchingFieldIndex(f, UsernameFieldNames) > -1) { - // We found an exact match. No need to keep looking. - break; - } - } - } - - return usernameField; - } - - private findMatchingFieldIndex(field: AutofillField, names: string[]): number { - for (let i = 0; i < names.length; i++) { - if (this.fieldPropertyIsMatch(field, 'htmlID', names[i])) { - return i; - } - if (this.fieldPropertyIsMatch(field, 'htmlName', names[i])) { - return i; - } - if (this.fieldPropertyIsMatch(field, 'label-tag', names[i])) { - return i; - } - if (this.fieldPropertyIsMatch(field, 'placeholder', names[i])) { - return i; - } - } - - return -1; - } - - private fieldPropertyIsMatch(field: any, property: string, name: string): boolean { - let fieldVal = field[property] as string; - if (!this.hasValue(fieldVal)) { - return false; - } - - fieldVal = fieldVal.trim().replace(/(?:\r\n|\r|\n)/g, ''); - if (name.startsWith('regex=')) { - try { - const regexParts = name.split('=', 2); - if (regexParts.length === 2) { - const regex = new RegExp(regexParts[1], 'i'); - return regex.test(fieldVal); - } - } catch (e) { } - } else if (name.startsWith('csv=')) { - const csvParts = name.split('=', 2); - if (csvParts.length === 2) { - const csvVals = csvParts[1].split(','); - for (let i = 0; i < csvVals.length; i++) { - const val = csvVals[i]; - if (val != null && val.trim().toLowerCase() === fieldVal.toLowerCase()) { - return true; - } - } - return false; - } - } - - return fieldVal.toLowerCase() === name; - } - - private fieldIsFuzzyMatch(field: AutofillField, names: string[]): boolean { - if (this.hasValue(field.htmlID) && this.fuzzyMatch(names, field.htmlID)) { - return true; - } - if (this.hasValue(field.htmlName) && this.fuzzyMatch(names, field.htmlName)) { - return true; - } - if (this.hasValue(field['label-tag']) && this.fuzzyMatch(names, field['label-tag'])) { - return true; - } - if (this.hasValue(field.placeholder) && this.fuzzyMatch(names, field.placeholder)) { - return true; - } - if (this.hasValue(field['label-left']) && this.fuzzyMatch(names, field['label-left'])) { - return true; - } - if (this.hasValue(field['label-top']) && this.fuzzyMatch(names, field['label-top'])) { - return true; - } - - return false; - } - - private fuzzyMatch(options: string[], value: string): boolean { - if (options == null || options.length === 0 || value == null || value === '') { - return false; - } - - value = value.replace(/(?:\r\n|\r|\n)/g, '').trim().toLowerCase(); - - for (let i = 0; i < options.length; i++) { - if (value.indexOf(options[i]) > -1) { - return true; - } - } - - return false; - } - - private hasValue(str: string): boolean { - return str && str !== ''; - } - - private setFillScriptForFocus(filledFields: { [id: string]: AutofillField; }, - fillScript: AutofillScript): AutofillScript { - let lastField: AutofillField = null; - let lastPasswordField: AutofillField = null; - - for (const opid in filledFields) { - if (filledFields.hasOwnProperty(opid) && filledFields[opid].viewable) { - lastField = filledFields[opid]; - - if (filledFields[opid].type === 'password') { - lastPasswordField = filledFields[opid]; - } - } - } - - // Prioritize password field over others. - if (lastPasswordField) { - fillScript.script.push(['focus_by_opid', lastPasswordField.opid]); - } else if (lastField) { - fillScript.script.push(['focus_by_opid', lastField.opid]); - } - - return fillScript; - } -} diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts deleted file mode 100644 index 7b0d5d08b2f..00000000000 --- a/src/services/cipher.service.ts +++ /dev/null @@ -1,514 +0,0 @@ -import { CipherType } from '../enums/cipherType.enum'; - -import { Cipher } from '../models/domain/cipher'; -import { CipherString } from '../models/domain/cipherString'; -import { Field } from '../models/domain/field'; -import SymmetricCryptoKey from '../models/domain/symmetricCryptoKey'; - -import { CipherData } from '../models/data/cipherData'; - -import { CipherRequest } from '../models/request/cipherRequest'; -import { CipherResponse } from '../models/response/cipherResponse'; -import { ErrorResponse } from '../models/response/errorResponse'; - -import ApiService from './api.service'; -import ConstantsService from './constants.service'; -import CryptoService from './crypto.service'; -import SettingsService from './settings.service'; -import UserService from './user.service'; -import UtilsService from './utils.service'; - -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) { - } - - 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 UtilsService.getObjFromStorage(Keys.localData); - const ciphers = await UtilsService.getObjFromStorage<{ [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 UtilsService.getObjFromStorage(Keys.localData); - const ciphers = await UtilsService.getObjFromStorage<{ [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 UtilsService.getObjFromStorage(Keys.localData); - if (!ciphersLocalData) { - ciphersLocalData = {}; - } - - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastUsedDate = new Date().getTime(); - } else { - ciphersLocalData[id] = { - lastUsedDate: new Date().getTime(), - }; - } - - await UtilsService.saveObjToStorage(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 UtilsService.getObjFromStorage<{ [id: string]: any; }>(Keys.neverDomains); - if (!domains) { - domains = {}; - } - domains[domain] = null; - await UtilsService.saveObjToStorage(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 UtilsService.getObjFromStorage<{ [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 UtilsService.saveObjToStorage(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async replace(ciphers: { [id: string]: CipherData; }): Promise { - const userId = await this.userService.getUserId(); - await UtilsService.saveObjToStorage(Keys.ciphersPrefix + userId, ciphers); - this.decryptedCipherCache = null; - } - - async clear(userId: string): Promise { - await UtilsService.removeFromStorage(Keys.ciphersPrefix + userId); - this.decryptedCipherCache = null; - } - - async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const ciphers = await UtilsService.getObjFromStorage<{ [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 UtilsService.saveObjToStorage(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 UtilsService.getObjFromStorage<{ [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 UtilsService.saveObjToStorage(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/collection.service.ts b/src/services/collection.service.ts deleted file mode 100644 index 14e23118bef..00000000000 --- a/src/services/collection.service.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { CipherString } from '../models/domain/cipherString'; -import { Collection } from '../models/domain/collection'; - -import { CollectionData } from '../models/data/collectionData'; - -import CryptoService from './crypto.service'; -import UserService from './user.service'; -import UtilsService from './utils.service'; - -const Keys = { - collectionsPrefix: 'collections_', -}; - -export default class CollectionService { - decryptedCollectionCache: any[]; - - constructor(private cryptoService: CryptoService, private userService: UserService) { - } - - clearCache(): void { - this.decryptedCollectionCache = null; - } - - async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const collections = await UtilsService.getObjFromStorage<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); - if (collections == null || !collections.hasOwnProperty(id)) { - return null; - } - - return new Collection(collections[id]); - } - - async getAll(): Promise { - const userId = await this.userService.getUserId(); - const collections = await UtilsService.getObjFromStorage<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); - const response: Collection[] = []; - for (const id in collections) { - if (collections.hasOwnProperty(id)) { - response.push(new Collection(collections[id])); - } - } - return response; - } - - async getAllDecrypted(): Promise { - if (this.decryptedCollectionCache != null) { - return this.decryptedCollectionCache; - } - - const key = await this.cryptoService.getKey(); - if (key == null) { - throw new Error('No key.'); - } - - const decFolders: any[] = []; - const promises: Array> = []; - const folders = await this.getAll(); - folders.forEach((folder) => { - promises.push(folder.decrypt().then((f: any) => { - decFolders.push(f); - })); - }); - - await Promise.all(promises); - this.decryptedCollectionCache = decFolders; - return this.decryptedCollectionCache; - } - - async upsert(collection: CollectionData | CollectionData[]): Promise { - const userId = await this.userService.getUserId(); - let collections = await UtilsService.getObjFromStorage<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); - if (collections == null) { - collections = {}; - } - - if (collection instanceof CollectionData) { - const c = collection as CollectionData; - collections[c.id] = c; - } else { - (collection as CollectionData[]).forEach((c) => { - collections[c.id] = c; - }); - } - - await UtilsService.saveObjToStorage(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; - } - - async replace(collections: { [id: string]: CollectionData; }): Promise { - const userId = await this.userService.getUserId(); - await UtilsService.saveObjToStorage(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; - } - - async clear(userId: string): Promise { - await UtilsService.removeFromStorage(Keys.collectionsPrefix + userId); - this.decryptedCollectionCache = null; - } - - async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const collections = await UtilsService.getObjFromStorage<{ [id: string]: CollectionData; }>( - Keys.collectionsPrefix + userId); - if (collections == null) { - return; - } - - if (typeof id === 'string') { - const i = id as string; - delete collections[id]; - } else { - (id as string[]).forEach((i) => { - delete collections[i]; - }); - } - - await UtilsService.saveObjToStorage(Keys.collectionsPrefix + userId, collections); - this.decryptedCollectionCache = null; - } -} diff --git a/src/services/constants.service.ts b/src/services/constants.service.ts deleted file mode 100644 index 337c6174a46..00000000000 --- a/src/services/constants.service.ts +++ /dev/null @@ -1,116 +0,0 @@ -import UtilsService from './utils.service'; - -export default class ConstantsService { - static readonly environmentUrlsKey: string = 'environmentUrls'; - static readonly disableGaKey: string = 'disableGa'; - static readonly disableAddLoginNotificationKey: string = 'disableAddLoginNotification'; - static readonly disableContextMenuItemKey: string = 'disableContextMenuItem'; - static readonly disableFaviconKey: string = 'disableFavicon'; - static readonly disableAutoTotpCopyKey: string = 'disableAutoTotpCopy'; - static readonly enableAutoFillOnPageLoadKey: string = 'enableAutoFillOnPageLoad'; - static readonly lockOptionKey: string = 'lockOption'; - static readonly lastActiveKey: string = 'lastActive'; - - // TODO: remove these instance properties once all references are reading from the static properties - readonly environmentUrlsKey: string = 'environmentUrls'; - readonly disableGaKey: string = 'disableGa'; - readonly disableAddLoginNotificationKey: string = 'disableAddLoginNotification'; - readonly disableContextMenuItemKey: string = 'disableContextMenuItem'; - readonly disableFaviconKey: string = 'disableFavicon'; - readonly disableAutoTotpCopyKey: string = 'disableAutoTotpCopy'; - readonly enableAutoFillOnPageLoadKey: string = 'enableAutoFillOnPageLoad'; - readonly lockOptionKey: string = 'lockOption'; - readonly lastActiveKey: string = 'lastActive'; - - // TODO: Convert these objects to enums - readonly encType: any = { - AesCbc256_B64: 0, - AesCbc128_HmacSha256_B64: 1, - AesCbc256_HmacSha256_B64: 2, - Rsa2048_OaepSha256_B64: 3, - Rsa2048_OaepSha1_B64: 4, - Rsa2048_OaepSha256_HmacSha256_B64: 5, - Rsa2048_OaepSha1_HmacSha256_B64: 6, - }; - - readonly cipherType: any = { - login: 1, - secureNote: 2, - card: 3, - identity: 4, - }; - - readonly fieldType: any = { - text: 0, - hidden: 1, - boolean: 2, - }; - - readonly twoFactorProvider: any = { - u2f: 4, - yubikey: 3, - duo: 2, - authenticator: 0, - email: 1, - remember: 5, - }; - - twoFactorProviderInfo: any[]; - - constructor(i18nService: any, utilsService: UtilsService) { - if (utilsService.isEdge()) { - // delay for i18n fetch - setTimeout(() => { - this.bootstrap(i18nService); - }, 1000); - } else { - this.bootstrap(i18nService); - } - } - - private bootstrap(i18nService: any) { - this.twoFactorProviderInfo = [ - { - type: 0, - name: i18nService.authenticatorAppTitle, - description: i18nService.authenticatorAppDesc, - active: true, - free: true, - displayOrder: 0, - priority: 1, - }, - { - type: 3, - name: i18nService.yubiKeyTitle, - description: i18nService.yubiKeyDesc, - active: true, - displayOrder: 1, - priority: 3, - }, - { - type: 2, - name: 'Duo', - description: i18nService.duoDesc, - active: true, - displayOrder: 2, - priority: 2, - }, - { - type: 4, - name: i18nService.u2fTitle, - description: i18nService.u2fDesc, - active: true, - displayOrder: 3, - priority: 4, - }, - { - type: 1, - name: i18nService.emailTitle, - description: i18nService.emailDesc, - active: true, - displayOrder: 4, - priority: 0, - }, - ]; - } -} diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts deleted file mode 100644 index 16c3c7497b6..00000000000 --- a/src/services/crypto.service.ts +++ /dev/null @@ -1,597 +0,0 @@ -import * as forge from 'node-forge'; - -import { EncryptionType } from '../enums/encryptionType.enum'; - -import { CipherString } from '../models/domain/cipherString'; -import EncryptedObject from '../models/domain/encryptedObject'; -import SymmetricCryptoKey from '../models/domain/symmetricCryptoKey'; -import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse'; - -import ConstantsService from './constants.service'; -import UtilsService from './utils.service'; - -import { CryptoService as CryptoServiceInterface } from './abstractions/crypto.service'; - -const Keys = { - key: 'key', - encOrgKeys: 'encOrgKeys', - encPrivateKey: 'encPrivateKey', - encKey: 'encKey', - keyHash: 'keyHash', -}; - -const SigningAlgorithm = { - name: 'HMAC', - hash: { name: 'SHA-256' }, -}; - -const AesAlgorithm = { - name: 'AES-CBC', -}; - -const Crypto = window.crypto; -const Subtle = Crypto.subtle; - -export default class CryptoService implements CryptoServiceInterface { - private key: SymmetricCryptoKey; - private encKey: SymmetricCryptoKey; - private legacyEtmKey: SymmetricCryptoKey; - private keyHash: string; - private privateKey: ArrayBuffer; - private orgKeys: Map; - - async setKey(key: SymmetricCryptoKey): Promise { - this.key = key; - - const option = await UtilsService.getObjFromStorage(ConstantsService.lockOptionKey); - if (option != null) { - // if we have a lock option set, we do not store the key - return; - } - - return UtilsService.saveObjToStorage(Keys.key, key.keyB64); - } - - setKeyHash(keyHash: string): Promise<{}> { - this.keyHash = keyHash; - return UtilsService.saveObjToStorage(Keys.keyHash, keyHash); - } - - async setEncKey(encKey: string): Promise<{}> { - if (encKey == null) { - return; - } - await UtilsService.saveObjToStorage(Keys.encKey, encKey); - this.encKey = null; - } - - async setEncPrivateKey(encPrivateKey: string): Promise<{}> { - if (encPrivateKey == null) { - return; - } - - await UtilsService.saveObjToStorage(Keys.encPrivateKey, encPrivateKey); - this.privateKey = null; - } - - setOrgKeys(orgs: ProfileOrganizationResponse[]): Promise<{}> { - const orgKeys: any = {}; - orgs.forEach((org) => { - orgKeys[org.id] = org.key; - }); - - return UtilsService.saveObjToStorage(Keys.encOrgKeys, orgKeys); - } - - async getKey(): Promise { - if (this.key != null) { - return this.key; - } - - const option = await UtilsService.getObjFromStorage(ConstantsService.lockOptionKey); - if (option != null) { - return null; - } - - const key = await UtilsService.getObjFromStorage(Keys.key); - if (key) { - this.key = new SymmetricCryptoKey(key, true); - } - - return key == null ? null : this.key; - } - - getKeyHash(): Promise { - if (this.keyHash != null) { - return Promise.resolve(this.keyHash); - } - - return UtilsService.getObjFromStorage(Keys.keyHash); - } - - async getEncKey(): Promise { - if (this.encKey != null) { - return this.encKey; - } - - const encKey = await UtilsService.getObjFromStorage(Keys.encKey); - if (encKey == null) { - return null; - } - - const key = await this.getKey(); - if (key == null) { - return null; - } - - const decEncKey = await this.decrypt(new CipherString(encKey), key, 'raw'); - if (decEncKey == null) { - return null; - } - - this.encKey = new SymmetricCryptoKey(decEncKey); - return this.encKey; - } - - async getPrivateKey(): Promise { - if (this.privateKey != null) { - return this.privateKey; - } - - const encPrivateKey = await UtilsService.getObjFromStorage(Keys.encPrivateKey); - if (encPrivateKey == null) { - return null; - } - - const privateKey = await this.decrypt(new CipherString(encPrivateKey), null, 'raw'); - const privateKeyB64 = forge.util.encode64(privateKey); - this.privateKey = UtilsService.fromB64ToArray(privateKeyB64).buffer; - return this.privateKey; - } - - async getOrgKeys(): Promise> { - if (this.orgKeys != null && this.orgKeys.size > 0) { - return this.orgKeys; - } - - const self = this; - const encOrgKeys = await UtilsService.getObjFromStorage(Keys.encOrgKeys); - if (!encOrgKeys) { - return null; - } - - const orgKeys: Map = new Map(); - let setKey = false; - - for (const orgId in encOrgKeys) { - if (!encOrgKeys.hasOwnProperty(orgId)) { - continue; - } - - const decValueB64 = await this.rsaDecrypt(encOrgKeys[orgId]); - orgKeys.set(orgId, new SymmetricCryptoKey(decValueB64, true)); - setKey = true; - } - - if (setKey) { - this.orgKeys = orgKeys; - } - - return this.orgKeys; - } - - async getOrgKey(orgId: string): Promise { - if (orgId == null) { - return null; - } - - const orgKeys = await this.getOrgKeys(); - if (orgKeys == null || !orgKeys.has(orgId)) { - return null; - } - - return orgKeys.get(orgId); - } - - clearKey(): Promise { - this.key = this.legacyEtmKey = null; - return UtilsService.removeFromStorage(Keys.key); - } - - clearKeyHash(): Promise { - this.keyHash = null; - return UtilsService.removeFromStorage(Keys.keyHash); - } - - clearEncKey(memoryOnly?: boolean): Promise { - this.encKey = null; - if (memoryOnly) { - return Promise.resolve(); - } - return UtilsService.removeFromStorage(Keys.encKey); - } - - clearPrivateKey(memoryOnly?: boolean): Promise { - this.privateKey = null; - if (memoryOnly) { - return Promise.resolve(); - } - return UtilsService.removeFromStorage(Keys.encPrivateKey); - } - - clearOrgKeys(memoryOnly?: boolean): Promise { - this.orgKeys = null; - if (memoryOnly) { - return Promise.resolve(); - } - return UtilsService.removeFromStorage(Keys.encOrgKeys); - } - - clearKeys(): Promise { - return Promise.all([ - this.clearKey(), - this.clearKeyHash(), - this.clearOrgKeys(), - this.clearEncKey(), - this.clearPrivateKey(), - ]); - } - - async toggleKey(): Promise { - const key = await this.getKey(); - const option = await UtilsService.getObjFromStorage(ConstantsService.lockOptionKey); - if (option != null || option === 0) { - // if we have a lock option set, clear the key - await this.clearKey(); - this.key = key; - return; - } - - await this.setKey(key); - } - - makeKey(password: string, salt: string): SymmetricCryptoKey { - const keyBytes: string = (forge as any).pbkdf2(forge.util.encodeUtf8(password), forge.util.encodeUtf8(salt), - 5000, 256 / 8, 'sha256'); - return new SymmetricCryptoKey(keyBytes); - } - - async hashPassword(password: string, key: SymmetricCryptoKey): Promise { - const storedKey = await this.getKey(); - key = key || storedKey; - if (!password || !key) { - throw new Error('Invalid parameters.'); - } - - const hashBits = (forge as any).pbkdf2(key.key, forge.util.encodeUtf8(password), 1, 256 / 8, 'sha256'); - return forge.util.encode64(hashBits); - } - - makeEncKey(key: SymmetricCryptoKey): Promise { - const bytes = new Uint8Array(512 / 8); - Crypto.getRandomValues(bytes); - return this.encrypt(bytes, key, 'raw'); - } - - async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey, - plainValueEncoding: string = 'utf8'): Promise { - if (!plainValue) { - return Promise.resolve(null); - } - - let plainValueArr: Uint8Array; - if (plainValueEncoding === 'utf8') { - plainValueArr = UtilsService.fromUtf8ToArray(plainValue as string); - } else { - plainValueArr = plainValue as Uint8Array; - } - - const encValue = await this.aesEncrypt(plainValueArr.buffer, key); - const iv = UtilsService.fromBufferToB64(encValue.iv.buffer); - const ct = UtilsService.fromBufferToB64(encValue.ct.buffer); - const mac = encValue.mac ? UtilsService.fromBufferToB64(encValue.mac.buffer) : null; - return new CipherString(encValue.key.encType, iv, ct, mac); - } - - async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise { - const encValue = await this.aesEncrypt(plainValue, key); - let macLen = 0; - if (encValue.mac) { - macLen = encValue.mac.length; - } - - const encBytes = new Uint8Array(1 + encValue.iv.length + macLen + encValue.ct.length); - encBytes.set([encValue.key.encType]); - encBytes.set(encValue.iv, 1); - if (encValue.mac) { - encBytes.set(encValue.mac, 1 + encValue.iv.length); - } - - encBytes.set(encValue.ct, 1 + encValue.iv.length + macLen); - return encBytes.buffer; - } - - async decrypt(cipherString: CipherString, key?: SymmetricCryptoKey, - outputEncoding: string = 'utf8'): Promise { - const ivBytes: string = forge.util.decode64(cipherString.initializationVector); - const ctBytes: string = forge.util.decode64(cipherString.cipherText); - const macBytes: string = cipherString.mac ? forge.util.decode64(cipherString.mac) : null; - const decipher = await this.aesDecrypt(cipherString.encryptionType, ctBytes, ivBytes, macBytes, key); - if (!decipher) { - return null; - } - - if (outputEncoding === 'utf8') { - return decipher.output.toString('utf8'); - } else { - return decipher.output.getBytes(); - } - } - - async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise { - if (!encBuf) { - throw new Error('no encBuf.'); - } - - const encBytes = new Uint8Array(encBuf); - const encType = encBytes[0]; - let ctBytes: Uint8Array = null; - let ivBytes: Uint8Array = null; - let macBytes: Uint8Array = null; - - switch (encType) { - case EncryptionType.AesCbc128_HmacSha256_B64: - case EncryptionType.AesCbc256_HmacSha256_B64: - if (encBytes.length <= 49) { // 1 + 16 + 32 + ctLength - return null; - } - - ivBytes = encBytes.slice(1, 17); - macBytes = encBytes.slice(17, 49); - ctBytes = encBytes.slice(49); - break; - case EncryptionType.AesCbc256_B64: - if (encBytes.length <= 17) { // 1 + 16 + ctLength - return null; - } - - ivBytes = encBytes.slice(1, 17); - ctBytes = encBytes.slice(17); - break; - default: - return null; - } - - return await this.aesDecryptWC(encType, ctBytes.buffer, ivBytes.buffer, macBytes ? macBytes.buffer : null, key); - } - - async rsaDecrypt(encValue: string): Promise { - const headerPieces = encValue.split('.'); - let encType: EncryptionType = null; - let encPieces: string[]; - - if (headerPieces.length === 1) { - encType = EncryptionType.Rsa2048_OaepSha256_B64; - encPieces = [headerPieces[0]]; - } else if (headerPieces.length === 2) { - try { - encType = parseInt(headerPieces[0], null); - encPieces = headerPieces[1].split('|'); - } catch (e) { } - } - - switch (encType) { - case EncryptionType.Rsa2048_OaepSha256_B64: - case EncryptionType.Rsa2048_OaepSha1_B64: - if (encPieces.length !== 1) { - throw new Error('Invalid cipher format.'); - } - break; - case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: - case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: - if (encPieces.length !== 2) { - throw new Error('Invalid cipher format.'); - } - break; - default: - throw new Error('encType unavailable.'); - } - - if (encPieces == null || encPieces.length <= 0) { - throw new Error('encPieces unavailable.'); - } - - const key = await this.getEncKey(); - if (key != null && key.macKey != null && encPieces.length > 1) { - const ctBytes: string = forge.util.decode64(encPieces[0]); - const macBytes: string = forge.util.decode64(encPieces[1]); - const computedMacBytes = await this.computeMac(ctBytes, key.macKey, false); - const macsEqual = await this.macsEqual(key.macKey, macBytes, computedMacBytes); - if (!macsEqual) { - throw new Error('MAC failed.'); - } - } - - const privateKeyBytes = await this.getPrivateKey(); - if (!privateKeyBytes) { - throw new Error('No private key.'); - } - - let rsaAlgorithm: any = null; - switch (encType) { - case EncryptionType.Rsa2048_OaepSha256_B64: - case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: - rsaAlgorithm = { - name: 'RSA-OAEP', - hash: { name: 'SHA-256' }, - }; - break; - case EncryptionType.Rsa2048_OaepSha1_B64: - case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64: - rsaAlgorithm = { - name: 'RSA-OAEP', - hash: { name: 'SHA-1' }, - }; - break; - default: - throw new Error('encType unavailable.'); - } - - const privateKey = await Subtle.importKey('pkcs8', privateKeyBytes, rsaAlgorithm, false, ['decrypt']); - const ctArr = UtilsService.fromB64ToArray(encPieces[0]); - const decBytes = await Subtle.decrypt(rsaAlgorithm, privateKey, ctArr.buffer); - const b64DecValue = UtilsService.fromBufferToB64(decBytes); - return b64DecValue; - } - - // Helpers - - private async aesEncrypt(plainValue: ArrayBuffer, key: SymmetricCryptoKey): Promise { - const obj = new EncryptedObject(); - obj.key = await this.getKeyForEncryption(key); - const keyBuf = obj.key.getBuffers(); - - obj.iv = new Uint8Array(16); - Crypto.getRandomValues(obj.iv); - - const encKey = await Subtle.importKey('raw', keyBuf.encKey, AesAlgorithm, false, ['encrypt']); - const encValue = await Subtle.encrypt({ name: 'AES-CBC', iv: obj.iv }, encKey, plainValue); - obj.ct = new Uint8Array(encValue); - - if (keyBuf.macKey) { - const data = new Uint8Array(obj.iv.length + obj.ct.length); - data.set(obj.iv, 0); - data.set(obj.ct, obj.iv.length); - const mac = await this.computeMacWC(data.buffer, keyBuf.macKey); - obj.mac = new Uint8Array(mac); - } - - return obj; - } - - private async aesDecrypt(encType: EncryptionType, ctBytes: string, ivBytes: string, macBytes: string, - key: SymmetricCryptoKey): Promise { - const keyForEnc = await this.getKeyForEncryption(key); - const theKey = this.resolveLegacyKey(encType, keyForEnc); - - if (encType !== theKey.encType) { - // tslint:disable-next-line - console.error('encType unavailable.'); - return null; - } - - if (theKey.macKey != null && macBytes != null) { - const computedMacBytes = this.computeMac(ivBytes + ctBytes, theKey.macKey, false); - if (!this.macsEqual(theKey.macKey, computedMacBytes, macBytes)) { - // tslint:disable-next-line - console.error('MAC failed.'); - return null; - } - } - - const ctBuffer = (forge as any).util.createBuffer(ctBytes); - const decipher = (forge as any).cipher.createDecipher('AES-CBC', theKey.encKey); - decipher.start({ iv: ivBytes }); - decipher.update(ctBuffer); - decipher.finish(); - - return decipher; - } - - private async aesDecryptWC(encType: EncryptionType, ctBuf: ArrayBuffer, ivBuf: ArrayBuffer, - macBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise { - const theKey = await this.getKeyForEncryption(key); - const keyBuf = theKey.getBuffers(); - const encKey = await Subtle.importKey('raw', keyBuf.encKey, AesAlgorithm, false, ['decrypt']); - if (!keyBuf.macKey || !macBuf) { - return null; - } - - const data = new Uint8Array(ivBuf.byteLength + ctBuf.byteLength); - data.set(new Uint8Array(ivBuf), 0); - data.set(new Uint8Array(ctBuf), ivBuf.byteLength); - const computedMacBuf = await this.computeMacWC(data.buffer, keyBuf.macKey); - if (computedMacBuf === null) { - return null; - } - - const macsMatch = await this.macsEqualWC(keyBuf.macKey, macBuf, computedMacBuf); - if (macsMatch === false) { - // tslint:disable-next-line - console.error('MAC failed.'); - return null; - } - - return await Subtle.decrypt({ name: 'AES-CBC', iv: ivBuf }, encKey, ctBuf); - } - - private computeMac(dataBytes: string, macKey: string, b64Output: boolean): string { - const hmac = (forge as any).hmac.create(); - hmac.start('sha256', macKey); - hmac.update(dataBytes); - const mac = hmac.digest(); - return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes(); - } - - private async computeMacWC(dataBuf: ArrayBuffer, macKeyBuf: ArrayBuffer): Promise { - const key = await Subtle.importKey('raw', macKeyBuf, SigningAlgorithm, false, ['sign']); - return await Subtle.sign(SigningAlgorithm, key, dataBuf); - } - - // Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification). - // ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/ - private macsEqual(macKey: string, mac1: string, mac2: string): boolean { - const hmac = (forge as any).hmac.create(); - - hmac.start('sha256', macKey); - hmac.update(mac1); - const mac1Bytes = hmac.digest().getBytes(); - - hmac.start(null, null); - hmac.update(mac2); - const mac2Bytes = hmac.digest().getBytes(); - - return mac1Bytes === mac2Bytes; - } - - private async macsEqualWC(macKeyBuf: ArrayBuffer, mac1Buf: ArrayBuffer, mac2Buf: ArrayBuffer): Promise { - const macKey = await Subtle.importKey('raw', macKeyBuf, SigningAlgorithm, false, ['sign']); - const mac1 = await Subtle.sign(SigningAlgorithm, macKey, mac1Buf); - const mac2 = await Subtle.sign(SigningAlgorithm, macKey, mac2Buf); - - if (mac1.byteLength !== mac2.byteLength) { - return false; - } - - const arr1 = new Uint8Array(mac1); - const arr2 = new Uint8Array(mac2); - - for (let i = 0; i < arr2.length; i++) { - if (arr1[i] !== arr2[i]) { - return false; - } - } - - return true; - } - - private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise { - if (key) { - return key; - } - - const encKey = await this.getEncKey(); - return encKey || (await this.getKey()); - } - - private resolveLegacyKey(encType: EncryptionType, key: SymmetricCryptoKey): SymmetricCryptoKey { - if (encType === EncryptionType.AesCbc128_HmacSha256_B64 && key.encType === EncryptionType.AesCbc256_B64) { - // Old encrypt-then-mac scheme, make a new key - this.legacyEtmKey = this.legacyEtmKey || - new SymmetricCryptoKey(key.key, false, EncryptionType.AesCbc128_HmacSha256_B64); - return this.legacyEtmKey; - } - - return key; - } -} diff --git a/src/services/environment.service.ts b/src/services/environment.service.ts deleted file mode 100644 index 098a67f7498..00000000000 --- a/src/services/environment.service.ts +++ /dev/null @@ -1,87 +0,0 @@ -import ApiService from './api.service'; -import ConstantsService from './constants.service'; -import UtilsService from './utils.service'; - -import EnvironmentUrls from '../models/domain/environmentUrls'; - -export default class EnvironmentService { - baseUrl: string; - webVaultUrl: string; - apiUrl: string; - identityUrl: string; - iconsUrl: string; - - constructor(private apiService: ApiService) { - } - - async setUrlsFromStorage(): Promise { - const urlsObj: any = await UtilsService.getObjFromStorage(ConstantsService.environmentUrlsKey); - const urls = urlsObj || { - base: null, - api: null, - identity: null, - icons: null, - webVault: null, - }; - - const envUrls = new EnvironmentUrls(); - - if (urls.base) { - this.baseUrl = envUrls.base = urls.base; - await this.apiService.setUrls(envUrls); - return; - } - - this.webVaultUrl = urls.webVault; - this.apiUrl = envUrls.api = urls.api; - this.identityUrl = envUrls.identity = urls.identity; - this.iconsUrl = urls.icons; - await this.apiService.setUrls(envUrls); - } - - async setUrls(urls: any): Promise { - urls.base = this.formatUrl(urls.base); - urls.webVault = this.formatUrl(urls.webVault); - urls.api = this.formatUrl(urls.api); - urls.identity = this.formatUrl(urls.identity); - urls.icons = this.formatUrl(urls.icons); - - await UtilsService.saveObjToStorage(ConstantsService.environmentUrlsKey, { - base: urls.base, - api: urls.api, - identity: urls.identity, - webVault: urls.webVault, - icons: urls.icons, - }); - - this.baseUrl = urls.base; - this.webVaultUrl = urls.webVault; - this.apiUrl = urls.api; - this.identityUrl = urls.identity; - this.iconsUrl = urls.icons; - - const envUrls = new EnvironmentUrls(); - if (this.baseUrl) { - envUrls.base = this.baseUrl; - } else { - envUrls.api = this.apiUrl; - envUrls.identity = this.identityUrl; - } - - await this.apiService.setUrls(envUrls); - return urls; - } - - private formatUrl(url: string): string { - if (url == null || url === '') { - return null; - } - - url = url.replace(/\/+$/g, ''); - if (!url.startsWith('http://') && !url.startsWith('https://')) { - url = 'https://' + url; - } - - return url; - } -} diff --git a/src/services/folder.service.ts b/src/services/folder.service.ts deleted file mode 100644 index 3337c1b0bc8..00000000000 --- a/src/services/folder.service.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { CipherString } from '../models/domain/cipherString'; -import { Folder } from '../models/domain/folder'; - -import { FolderData } from '../models/data/folderData'; - -import { FolderRequest } from '../models/request/folderRequest'; -import { FolderResponse } from '../models/response/folderResponse'; - -import ApiService from './api.service'; -import CryptoService from './crypto.service'; -import UserService from './user.service'; -import UtilsService from './utils.service'; - -const Keys = { - foldersPrefix: 'folders_', -}; - -export default class FolderService { - decryptedFolderCache: any[]; - - constructor(private cryptoService: CryptoService, private userService: UserService, - private i18nService: any, private apiService: ApiService) { - } - - clearCache(): void { - this.decryptedFolderCache = null; - } - - async encrypt(model: any): Promise { - const folder = new Folder(); - folder.id = model.id; - folder.name = await this.cryptoService.encrypt(model.name); - return folder; - } - - async get(id: string): Promise { - const userId = await this.userService.getUserId(); - const folders = await UtilsService.getObjFromStorage<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); - if (folders == null || !folders.hasOwnProperty(id)) { - return null; - } - - return new Folder(folders[id]); - } - - async getAll(): Promise { - const userId = await this.userService.getUserId(); - const folders = await UtilsService.getObjFromStorage<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); - const response: Folder[] = []; - for (const id in folders) { - if (folders.hasOwnProperty(id)) { - response.push(new Folder(folders[id])); - } - } - return response; - } - - async getAllDecrypted(): Promise { - if (this.decryptedFolderCache != null) { - return this.decryptedFolderCache; - } - - const decFolders: any[] = [{ - id: null, - name: this.i18nService.noneFolder, - }]; - - const key = await this.cryptoService.getKey(); - if (key == null) { - throw new Error('No key.'); - } - - const promises: Array> = []; - const folders = await this.getAll(); - folders.forEach((folder) => { - promises.push(folder.decrypt().then((f: any) => { - decFolders.push(f); - })); - }); - - await Promise.all(promises); - this.decryptedFolderCache = decFolders; - return this.decryptedFolderCache; - } - - async saveWithServer(folder: Folder): Promise { - const request = new FolderRequest(folder); - - let response: FolderResponse; - if (folder.id == null) { - response = await this.apiService.postFolder(request); - folder.id = response.id; - } else { - response = await this.apiService.putFolder(folder.id, request); - } - - const userId = await this.userService.getUserId(); - const data = new FolderData(response, userId); - await this.upsert(data); - } - - async upsert(folder: FolderData | FolderData[]): Promise { - const userId = await this.userService.getUserId(); - let folders = await UtilsService.getObjFromStorage<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); - if (folders == null) { - folders = {}; - } - - if (folder instanceof FolderData) { - const f = folder as FolderData; - folders[f.id] = f; - } else { - (folder as FolderData[]).forEach((f) => { - folders[f.id] = f; - }); - } - - await UtilsService.saveObjToStorage(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; - } - - async replace(folders: { [id: string]: FolderData; }): Promise { - const userId = await this.userService.getUserId(); - await UtilsService.saveObjToStorage(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; - } - - async clear(userId: string): Promise { - await UtilsService.removeFromStorage(Keys.foldersPrefix + userId); - this.decryptedFolderCache = null; - } - - async delete(id: string | string[]): Promise { - const userId = await this.userService.getUserId(); - const folders = await UtilsService.getObjFromStorage<{ [id: string]: FolderData; }>( - Keys.foldersPrefix + userId); - if (folders == null) { - return; - } - - if (typeof id === 'string') { - const i = id as string; - delete folders[id]; - } else { - (id as string[]).forEach((i) => { - delete folders[i]; - }); - } - - await UtilsService.saveObjToStorage(Keys.foldersPrefix + userId, folders); - this.decryptedFolderCache = null; - } - - async deleteWithServer(id: string): Promise { - await this.apiService.deleteFolder(id); - await this.delete(id); - } -} diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts deleted file mode 100644 index a294015f3c1..00000000000 --- a/src/services/i18n.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import UtilsService from '../services/utils.service'; - -export default function i18nService(utilsService: UtilsService) { - const edgeMessages: any = {}; - - if (utilsService.isEdge()) { - fetch('../_locales/en/messages.json').then((file) => { - return file.json(); - }).then((locales) => { - for (const prop in locales) { - if (locales.hasOwnProperty(prop)) { - edgeMessages[prop] = chrome.i18n.getMessage(prop); - } - } - }); - - return edgeMessages; - } - - return new Proxy({}, { - get: (target, name) => { - return chrome.i18n.getMessage(name); - }, - set: (target, name, value) => { - return false; - }, - }); -} diff --git a/src/services/lock.service.ts b/src/services/lock.service.ts deleted file mode 100644 index bc58aacbabd..00000000000 --- a/src/services/lock.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -import CipherService from './cipher.service'; -import CollectionService from './collection.service'; -import ConstantsService from './constants.service'; -import CryptoService from './crypto.service'; -import FolderService from './folder.service'; -import UtilsService from './utils.service'; - -export default class LockService { - constructor(private cipherService: CipherService, private folderService: FolderService, - private collectionService: CollectionService, private cryptoService: CryptoService, - private utilsService: UtilsService, private setIcon: Function, private refreshBadgeAndMenu: Function) { - this.checkLock(); - setInterval(() => this.checkLock(), 10 * 1000); // check every 10 seconds - - const self = this; - if ((window as any).chrome.idle && (window as any).chrome.idle.onStateChanged) { - (window as any).chrome.idle.onStateChanged.addListener(async (newState: string) => { - if (newState === 'locked') { - const lockOption = await UtilsService.getObjFromStorage(ConstantsService.lockOptionKey); - if (lockOption === -2) { - self.lock(); - } - } - }); - } - } - - async checkLock(): Promise { - const popupOpen = chrome.extension.getViews({ type: 'popup' }).length > 0; - const tabOpen = chrome.extension.getViews({ type: 'tab' }).length > 0; - const sidebarView = this.sidebarViewName(); - const sidebarOpen = sidebarView != null && chrome.extension.getViews({ type: sidebarView }).length > 0; - - if (popupOpen || tabOpen || sidebarOpen) { - // Do not lock - return; - } - - const key = await this.cryptoService.getKey(); - if (key == null) { - // no key so no need to lock - return; - } - - const lockOption = await UtilsService.getObjFromStorage(ConstantsService.lockOptionKey); - if (lockOption == null || lockOption < 0) { - return; - } - - const lastActive = await UtilsService.getObjFromStorage(ConstantsService.lastActiveKey); - if (lastActive == null) { - return; - } - - const lockOptionSeconds = lockOption * 60; - const diffSeconds = ((new Date()).getTime() - lastActive) / 1000; - if (diffSeconds >= lockOptionSeconds) { - // need to lock now - await this.lock(); - } - } - - async lock(): Promise { - await Promise.all([ - this.cryptoService.clearKey(), - this.cryptoService.clearOrgKeys(true), - this.cryptoService.clearPrivateKey(true), - this.cryptoService.clearEncKey(true), - this.setIcon(), - this.refreshBadgeAndMenu(), - ]); - - this.folderService.clearCache(); - this.cipherService.clearCache(); - this.collectionService.clearCache(); - } - - // Helpers - - private sidebarViewName(): string { - if ((window as any).chrome.sidebarAction && this.utilsService.isFirefox()) { - return 'sidebar'; - } else if (this.utilsService.isOpera() && (typeof opr !== 'undefined') && opr.sidebarAction) { - return 'sidebar_panel'; - } - - return null; - } -} diff --git a/src/services/passwordGeneration.service.ts b/src/services/passwordGeneration.service.ts deleted file mode 100644 index 4e52438d46a..00000000000 --- a/src/services/passwordGeneration.service.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { CipherString } from '../models/domain/cipherString'; -import PasswordHistory from '../models/domain/passwordHistory'; - -import CryptoService from './crypto.service'; -import UtilsService from './utils.service'; - -const DefaultOptions = { - length: 14, - ambiguous: false, - number: true, - minNumber: 1, - uppercase: true, - minUppercase: 1, - lowercase: true, - minLowercase: 1, - special: false, - minSpecial: 1, -}; - -const Keys = { - options: 'passwordGenerationOptions', - history: 'generatedPasswordHistory', -}; - -const MaxPasswordsInHistory = 100; - -export default class PasswordGenerationService { - static generatePassword(options: any): string { - // overload defaults with given options - const o = Object.assign({}, DefaultOptions, options); - - // sanitize - if (o.uppercase && o.minUppercase < 0) { - o.minUppercase = 1; - } - if (o.lowercase && o.minLowercase < 0) { - o.minLowercase = 1; - } - if (o.number && o.minNumber < 0) { - o.minNumber = 1; - } - if (o.special && o.minSpecial < 0) { - o.minSpecial = 1; - } - - if (!o.length || o.length < 1) { - o.length = 10; - } - - const minLength: number = o.minUppercase + o.minLowercase + o.minNumber + o.minSpecial; - if (o.length < minLength) { - o.length = minLength; - } - - const positions: string[] = []; - if (o.lowercase && o.minLowercase > 0) { - for (let i = 0; i < o.minLowercase; i++) { - positions.push('l'); - } - } - if (o.uppercase && o.minUppercase > 0) { - for (let i = 0; i < o.minUppercase; i++) { - positions.push('u'); - } - } - if (o.number && o.minNumber > 0) { - for (let i = 0; i < o.minNumber; i++) { - positions.push('n'); - } - } - if (o.special && o.minSpecial > 0) { - for (let i = 0; i < o.minSpecial; i++) { - positions.push('s'); - } - } - while (positions.length < o.length) { - positions.push('a'); - } - - // shuffle - positions.sort(() => { - return UtilsService.secureRandomNumber(0, 1) * 2 - 1; - }); - - // build out the char sets - let allCharSet = ''; - - let lowercaseCharSet = 'abcdefghijkmnopqrstuvwxyz'; - if (o.ambiguous) { - lowercaseCharSet += 'l'; - } - if (o.lowercase) { - allCharSet += lowercaseCharSet; - } - - let uppercaseCharSet = 'ABCDEFGHIJKLMNPQRSTUVWXYZ'; - if (o.ambiguous) { - uppercaseCharSet += 'O'; - } - if (o.uppercase) { - allCharSet += uppercaseCharSet; - } - - let numberCharSet = '23456789'; - if (o.ambiguous) { - numberCharSet += '01'; - } - if (o.number) { - allCharSet += numberCharSet; - } - - const specialCharSet = '!@#$%^&*'; - if (o.special) { - allCharSet += specialCharSet; - } - - let password = ''; - for (let i = 0; i < o.length; i++) { - let positionChars: string; - switch (positions[i]) { - case 'l': - positionChars = lowercaseCharSet; - break; - case 'u': - positionChars = uppercaseCharSet; - break; - case 'n': - positionChars = numberCharSet; - break; - case 's': - positionChars = specialCharSet; - break; - case 'a': - positionChars = allCharSet; - break; - } - - const randomCharIndex = UtilsService.secureRandomNumber(0, positionChars.length - 1); - password += positionChars.charAt(randomCharIndex); - } - - return password; - } - - optionsCache: any; - history: PasswordHistory[] = []; - - constructor(private cryptoService: CryptoService) { - UtilsService.getObjFromStorage(Keys.history).then((encrypted) => { - return this.decryptHistory(encrypted); - }).then((history) => { - this.history = history; - }); - } - - generatePassword(options: any) { - return PasswordGenerationService.generatePassword(options); - } - - async getOptions() { - if (this.optionsCache == null) { - const options = await UtilsService.getObjFromStorage(Keys.options); - if (options == null) { - this.optionsCache = DefaultOptions; - } else { - this.optionsCache = options; - } - } - - return this.optionsCache; - } - - async saveOptions(options: any) { - await UtilsService.saveObjToStorage(Keys.options, options); - this.optionsCache = options; - } - - getHistory() { - return this.history || new Array(); - } - - async addHistory(password: string): Promise { - // Prevent duplicates - if (this.matchesPrevious(password)) { - return; - } - - this.history.push(new PasswordHistory(password, Date.now())); - - // Remove old items. - if (this.history.length > MaxPasswordsInHistory) { - this.history.shift(); - } - - const newHistory = await this.encryptHistory(); - return await UtilsService.saveObjToStorage(Keys.history, newHistory); - } - - async clear(): Promise { - this.history = []; - return await UtilsService.removeFromStorage(Keys.history); - } - - private async encryptHistory(): Promise { - if (this.history == null || this.history.length === 0) { - return Promise.resolve([]); - } - - const promises = this.history.map(async (item) => { - const encrypted = await this.cryptoService.encrypt(item.password); - return new PasswordHistory(encrypted.encryptedString, item.date); - }); - - return await Promise.all(promises); - } - - private async decryptHistory(history: PasswordHistory[]): Promise { - if (history == null || history.length === 0) { - return Promise.resolve([]); - } - - const promises = history.map(async (item) => { - const decrypted = await this.cryptoService.decrypt(new CipherString(item.password)); - return new PasswordHistory(decrypted, item.date); - }); - - return await Promise.all(promises); - } - - private matchesPrevious(password: string): boolean { - if (this.history == null || this.history.length === 0) { - return false; - } - - return this.history[this.history.length - 1].password === password; - } -} diff --git a/src/services/settings.service.ts b/src/services/settings.service.ts deleted file mode 100644 index 25d598f83c5..00000000000 --- a/src/services/settings.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import UserService from './user.service'; -import UtilsService from './utils.service'; - -const Keys = { - settingsPrefix: 'settings_', - equivalentDomains: 'equivalentDomains', -}; - -export default class SettingsService { - private settingsCache: any; - - constructor(private userService: UserService) { - } - - clearCache(): void { - this.settingsCache = null; - } - - getEquivalentDomains(): Promise { - return this.getSettingsKey(Keys.equivalentDomains); - } - - async setEquivalentDomains(equivalentDomains: string[][]) { - await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); - } - - async clear(userId: string): Promise { - await UtilsService.removeFromStorage(Keys.settingsPrefix + userId); - this.settingsCache = null; - } - - // Helpers - - private async getSettings(): Promise { - if (this.settingsCache == null) { - const userId = await this.userService.getUserId(); - this.settingsCache = UtilsService.getObjFromStorage(Keys.settingsPrefix + userId); - } - return this.settingsCache; - } - - private async getSettingsKey(key: string): Promise { - const settings = await this.getSettings(); - if (settings != null && settings[key]) { - return settings[key]; - } - return null; - } - - private async setSettingsKey(key: string, value: any): Promise { - const userId = await this.userService.getUserId(); - let settings = await this.getSettings(); - if (!settings) { - settings = {}; - } - - settings[key] = value; - await UtilsService.saveObjToStorage(Keys.settingsPrefix + userId, settings); - this.settingsCache = settings; - } -} diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts deleted file mode 100644 index a28c401f196..00000000000 --- a/src/services/sync.service.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { CipherData } from '../models/data/cipherData'; -import { CollectionData } from '../models/data/collectionData'; -import { FolderData } from '../models/data/folderData'; - -import { CipherResponse } from '../models/response/cipherResponse'; -import { CollectionResponse } from '../models/response/collectionResponse'; -import { DomainsResponse } from '../models/response/domainsResponse'; -import { FolderResponse } from '../models/response/folderResponse'; -import { ProfileResponse } from '../models/response/profileResponse'; -import { SyncResponse } from '../models/response/syncResponse'; - -import ApiService from './api.service'; -import CipherService from './cipher.service'; -import CollectionService from './collection.service'; -import CryptoService from './crypto.service'; -import FolderService from './folder.service'; -import SettingsService from './settings.service'; -import UserService from './user.service'; -import UtilsService from './utils.service'; - -const Keys = { - lastSyncPrefix: 'lastSync_', -}; - -export default class SyncService { - syncInProgress: boolean = false; - - constructor(private userService: UserService, private apiService: ApiService, - private settingsService: SettingsService, private folderService: FolderService, - private cipherService: CipherService, private cryptoService: CryptoService, - private collectionService: CollectionService, private logoutCallback: Function) { - } - - async getLastSync() { - const userId = await this.userService.getUserId(); - const lastSync = await UtilsService.getObjFromStorage(Keys.lastSyncPrefix + userId); - if (lastSync) { - return new Date(lastSync); - } - - return null; - } - - async setLastSync(date: Date) { - const userId = await this.userService.getUserId(); - await UtilsService.saveObjToStorage(Keys.lastSyncPrefix + userId, date.toJSON()); - } - - syncStarted() { - this.syncInProgress = true; - chrome.runtime.sendMessage({ command: 'syncStarted' }); - } - - syncCompleted(successfully: boolean) { - this.syncInProgress = false; - // tslint:disable-next-line - chrome.runtime.sendMessage({ command: 'syncCompleted', successfully: successfully }); - } - - async fullSync(forceSync: boolean) { - this.syncStarted(); - const isAuthenticated = await this.userService.isAuthenticated(); - if (!isAuthenticated) { - this.syncCompleted(false); - return false; - } - - const now = new Date(); - const needsSyncResult = await this.needsSyncing(forceSync); - const needsSync = needsSyncResult[0]; - const skipped = needsSyncResult[1]; - - if (skipped) { - this.syncCompleted(false); - return false; - } - - if (!needsSync) { - await this.setLastSync(now); - this.syncCompleted(false); - return false; - } - - const userId = await this.userService.getUserId(); - try { - const response = await this.apiService.getSync(); - - await this.syncProfile(response.profile); - await this.syncFolders(userId, response.folders); - await this.syncCollections(response.collections); - await this.syncCiphers(userId, response.ciphers); - await this.syncSettings(userId, response.domains); - - await this.setLastSync(now); - this.syncCompleted(true); - return true; - } catch (e) { - this.syncCompleted(false); - return false; - } - } - - // Helpers - - private async needsSyncing(forceSync: boolean) { - if (forceSync) { - return [true, false]; - } - - try { - const response = await this.apiService.getAccountRevisionDate(); - const accountRevisionDate = new Date(response); - const lastSync = await this.getLastSync(); - if (lastSync != null && accountRevisionDate <= lastSync) { - return [false, false]; - } - - return [true, false]; - } catch (e) { - return [false, true]; - } - } - - private async syncProfile(response: ProfileResponse) { - const stamp = await this.userService.getSecurityStamp(); - if (stamp != null && stamp !== response.securityStamp) { - if (this.logoutCallback != null) { - this.logoutCallback(true); - } - - throw new Error('Stamp has changed'); - } - - await this.cryptoService.setEncKey(response.key); - await this.cryptoService.setEncPrivateKey(response.privateKey); - await this.cryptoService.setOrgKeys(response.organizations); - await this.userService.setSecurityStamp(response.securityStamp); - } - - private async syncFolders(userId: string, response: FolderResponse[]) { - const folders: { [id: string]: FolderData; } = {}; - response.forEach((f) => { - folders[f.id] = new FolderData(f, userId); - }); - return await this.folderService.replace(folders); - } - - private async syncCollections(response: CollectionResponse[]) { - const collections: { [id: string]: CollectionData; } = {}; - response.forEach((c) => { - collections[c.id] = new CollectionData(c); - }); - return await this.collectionService.replace(collections); - } - - private async syncCiphers(userId: string, response: CipherResponse[]) { - const ciphers: { [id: string]: CipherData; } = {}; - response.forEach((c) => { - ciphers[c.id] = new CipherData(c, userId); - }); - return await this.cipherService.replace(ciphers); - } - - private async syncSettings(userId: string, response: DomainsResponse) { - let eqDomains: string[][] = []; - if (response != null && response.equivalentDomains != null) { - eqDomains = eqDomains.concat(response.equivalentDomains); - } - - if (response != null && response.globalEquivalentDomains != null) { - response.globalEquivalentDomains.forEach((global) => { - if (global.domains.length > 0) { - eqDomains.push(global.domains); - } - }); - } - - return this.settingsService.setEquivalentDomains(eqDomains); - } -} diff --git a/src/services/token.service.ts b/src/services/token.service.ts deleted file mode 100644 index 3ebaa239be5..00000000000 --- a/src/services/token.service.ts +++ /dev/null @@ -1,170 +0,0 @@ -import ConstantsService from './constants.service'; -import UtilsService from './utils.service'; - -const Keys = { - accessToken: 'accessToken', - refreshToken: 'refreshToken', - twoFactorTokenPrefix: 'twoFactorToken_', -}; - -export default class TokenService { - token: string; - decodedToken: any; - refreshToken: string; - - setTokens(accessToken: string, refreshToken: string): Promise { - return Promise.all([ - this.setToken(accessToken), - this.setRefreshToken(refreshToken), - ]); - } - - setToken(token: string): Promise { - this.token = token; - this.decodedToken = null; - return UtilsService.saveObjToStorage(Keys.accessToken, token); - } - - async getToken(): Promise { - if (this.token != null) { - return this.token; - } - - this.token = await UtilsService.getObjFromStorage(Keys.accessToken); - return this.token; - } - - setRefreshToken(refreshToken: string): Promise { - this.refreshToken = refreshToken; - return UtilsService.saveObjToStorage(Keys.refreshToken, refreshToken); - } - - async getRefreshToken(): Promise { - if (this.refreshToken != null) { - return this.refreshToken; - } - - this.refreshToken = await UtilsService.getObjFromStorage(Keys.refreshToken); - return this.refreshToken; - } - - setTwoFactorToken(token: string, email: string): Promise { - return UtilsService.saveObjToStorage(Keys.twoFactorTokenPrefix + email, token); - } - - getTwoFactorToken(email: string): Promise { - return UtilsService.getObjFromStorage(Keys.twoFactorTokenPrefix + email); - } - - clearTwoFactorToken(email: string): Promise { - return UtilsService.removeFromStorage(Keys.twoFactorTokenPrefix + email); - } - - clearToken(): Promise { - this.token = null; - this.decodedToken = null; - this.refreshToken = null; - - return Promise.all([ - UtilsService.removeFromStorage(Keys.accessToken), - UtilsService.removeFromStorage(Keys.refreshToken), - ]); - } - - // jwthelper methods - // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js - - decodeToken(): any { - if (this.decodedToken) { - return this.decodedToken; - } - - if (this.token == null) { - throw new Error('Token not found.'); - } - - const parts = this.token.split('.'); - if (parts.length !== 3) { - throw new Error('JWT must have 3 parts'); - } - - const decoded = UtilsService.urlBase64Decode(parts[1]); - if (decoded == null) { - throw new Error('Cannot decode the token'); - } - - this.decodedToken = JSON.parse(decoded); - return this.decodedToken; - } - - getTokenExpirationDate(): Date { - const decoded = this.decodeToken(); - if (typeof decoded.exp === 'undefined') { - return null; - } - - const d = new Date(0); // The 0 here is the key, which sets the date to the epoch - d.setUTCSeconds(decoded.exp); - return d; - } - - tokenSecondsRemaining(offsetSeconds: number = 0): number { - const d = this.getTokenExpirationDate(); - if (d == null) { - return 0; - } - - const msRemaining = d.valueOf() - (new Date().valueOf() + (offsetSeconds * 1000)); - return Math.round(msRemaining / 1000); - } - - tokenNeedsRefresh(minutes: number = 5): boolean { - const sRemaining = this.tokenSecondsRemaining(); - return sRemaining < (60 * minutes); - } - - getUserId(): string { - const decoded = this.decodeToken(); - if (typeof decoded.sub === 'undefined') { - throw new Error('No user id found'); - } - - return decoded.sub as string; - } - - getEmail(): string { - const decoded = this.decodeToken(); - if (typeof decoded.email === 'undefined') { - throw new Error('No email found'); - } - - return decoded.email as string; - } - - getName(): string { - const decoded = this.decodeToken(); - if (typeof decoded.name === 'undefined') { - throw new Error('No name found'); - } - - return decoded.name as string; - } - - getPremium(): boolean { - const decoded = this.decodeToken(); - if (typeof decoded.premium === 'undefined') { - return false; - } - - return decoded.premium as boolean; - } - - getIssuer(): string { - const decoded = this.decodeToken(); - if (typeof decoded.iss === 'undefined') { - throw new Error('No issuer found'); - } - - return decoded.iss as string; - } -} diff --git a/src/services/totp.service.ts b/src/services/totp.service.ts deleted file mode 100644 index ae4d4ec57a0..00000000000 --- a/src/services/totp.service.ts +++ /dev/null @@ -1,113 +0,0 @@ -import ConstantsService from './constants.service'; -import UtilsService from './utils.service'; - -const b32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - -const TotpAlgorithm = { - name: 'HMAC', - hash: { name: 'SHA-1' }, -}; - -export default class TotpService { - async getCode(keyb32: string): Promise { - const epoch = Math.round(new Date().getTime() / 1000.0); - const timeHex = this.leftpad(this.dec2hex(Math.floor(epoch / 30)), 16, '0'); - const timeBytes = this.hex2bytes(timeHex); - const keyBytes = this.b32tobytes(keyb32); - - if (!keyBytes.length || !timeBytes.length) { - return null; - } - - const hashHex = await this.sign(keyBytes, timeBytes); - if (!hashHex) { - return null; - } - - const offset = this.hex2dec(hashHex.substring(hashHex.length - 1)); - // tslint:disable-next-line - let otp = (this.hex2dec(hashHex.substr(offset * 2, 8)) & this.hex2dec('7fffffff')) + ''; - otp = (otp).substr(otp.length - 6, 6); - return otp; - } - - async isAutoCopyEnabled(): Promise { - return !(await UtilsService.getObjFromStorage(ConstantsService.disableAutoTotpCopyKey)); - } - - // Helpers - - private leftpad(s: string, l: number, p: string): string { - if (l + 1 >= s.length) { - s = Array(l + 1 - s.length).join(p) + s; - } - return s; - } - - private dec2hex(d: number): string { - return (d < 15.5 ? '0' : '') + Math.round(d).toString(16); - } - - private hex2dec(s: string): number { - return parseInt(s, 16); - } - - private hex2bytes(s: string): Uint8Array { - const bytes = new Uint8Array(s.length / 2); - for (let i = 0; i < s.length; i += 2) { - bytes[i / 2] = parseInt(s.substr(i, 2), 16); - } - return bytes; - } - - private buff2hex(buff: ArrayBuffer): string { - const bytes = new Uint8Array(buff); - const hex: string[] = []; - bytes.forEach((b) => { - // tslint:disable-next-line - hex.push((b >>> 4).toString(16)); - // tslint:disable-next-line - hex.push((b & 0xF).toString(16)); - }); - return hex.join(''); - } - - private b32tohex(s: string): string { - s = s.toUpperCase(); - let cleanedInput = ''; - - for (let i = 0; i < s.length; i++) { - if (b32Chars.indexOf(s[i]) < 0) { - continue; - } - - cleanedInput += s[i]; - } - s = cleanedInput; - - let bits = ''; - let hex = ''; - for (let i = 0; i < s.length; i++) { - const byteIndex = b32Chars.indexOf(s.charAt(i)); - if (byteIndex < 0) { - continue; - } - bits += this.leftpad(byteIndex.toString(2), 5, '0'); - } - for (let i = 0; i + 4 <= bits.length; i += 4) { - const chunk = bits.substr(i, 4); - hex = hex + parseInt(chunk, 2).toString(16); - } - return hex; - } - - private b32tobytes(s: string): Uint8Array { - return this.hex2bytes(this.b32tohex(s)); - } - - private async sign(keyBytes: Uint8Array, timeBytes: Uint8Array) { - const key = await window.crypto.subtle.importKey('raw', keyBytes, TotpAlgorithm, false, ['sign']); - const signature = await window.crypto.subtle.sign(TotpAlgorithm, key, timeBytes); - return this.buff2hex(signature); - } -} diff --git a/src/services/user.service.ts b/src/services/user.service.ts deleted file mode 100644 index caa56c0d6f2..00000000000 --- a/src/services/user.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import TokenService from './token.service'; -import UtilsService from './utils.service'; - -const Keys = { - userId: 'userId', - userEmail: 'userEmail', - stamp: 'securityStamp', -}; - -export default class UserService { - userId: string; - email: string; - stamp: string; - - constructor(private tokenService: TokenService) { - } - - setUserIdAndEmail(userId: string, email: string): Promise { - this.email = email; - this.userId = userId; - - return Promise.all([ - UtilsService.saveObjToStorage(Keys.userEmail, email), - UtilsService.saveObjToStorage(Keys.userId, userId), - ]); - } - - setSecurityStamp(stamp: string): Promise { - this.stamp = stamp; - return UtilsService.saveObjToStorage(Keys.stamp, stamp); - } - - async getUserId(): Promise { - if (this.userId != null) { - return this.userId; - } - - this.userId = await UtilsService.getObjFromStorage(Keys.userId); - return this.userId; - } - - async getEmail(): Promise { - if (this.email != null) { - return this.email; - } - - this.email = await UtilsService.getObjFromStorage(Keys.userEmail); - return this.email; - } - - async getSecurityStamp(): Promise { - if (this.stamp != null) { - return this.stamp; - } - - this.stamp = await UtilsService.getObjFromStorage(Keys.stamp); - return this.stamp; - } - - async clear(): Promise { - await Promise.all([ - UtilsService.removeFromStorage(Keys.userId), - UtilsService.removeFromStorage(Keys.userEmail), - UtilsService.removeFromStorage(Keys.stamp), - ]); - - this.userId = this.email = this.stamp = null; - } - - async isAuthenticated(): Promise { - const token = await this.tokenService.getToken(); - if (token == null) { - return false; - } - - const userId = await this.getUserId(); - return userId != null; - } -} diff --git a/src/services/utils.service.spec.ts b/src/services/utils.service.spec.ts deleted file mode 100644 index c976888de8e..00000000000 --- a/src/services/utils.service.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import UtilsService from './utils.service'; -import { BrowserType } from '../enums/browserType.enum'; - -describe('Utils Service', () => { - describe('getDomain', () => { - it('should fail for invalid urls', () => { - expect(UtilsService.getDomain(null)).toBeNull(); - expect(UtilsService.getDomain(undefined)).toBeNull(); - expect(UtilsService.getDomain(' ')).toBeNull(); - expect(UtilsService.getDomain('https://bit!:"_&ward.com')).toBeNull(); - expect(UtilsService.getDomain('bitwarden')).toBeNull(); - }); - - it('should handle urls without protocol', () => { - expect(UtilsService.getDomain('bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getDomain('wrong://bitwarden.com')).toBe('bitwarden.com'); - }); - - it('should handle valid urls', () => { - expect(UtilsService.getDomain('https://bitwarden')).toBe('bitwarden'); - expect(UtilsService.getDomain('https://bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getDomain('http://bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getDomain('http://vault.bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getDomain('https://user:password@bitwarden.com:8080/password/sites?and&query#hash')).toBe('bitwarden.com'); - expect(UtilsService.getDomain('https://bitwarden.unknown')).toBe('bitwarden.unknown'); - }); - - it('should support localhost and IP', () => { - expect(UtilsService.getDomain('https://localhost')).toBe('localhost'); - expect(UtilsService.getDomain('https://192.168.1.1')).toBe('192.168.1.1'); - }); - }); - - describe('getHostname', () => { - it('should fail for invalid urls', () => { - expect(UtilsService.getHostname(null)).toBeNull(); - expect(UtilsService.getHostname(undefined)).toBeNull(); - expect(UtilsService.getHostname(' ')).toBeNull(); - expect(UtilsService.getHostname('https://bit!:"_&ward.com')).toBeNull(); - expect(UtilsService.getHostname('bitwarden')).toBeNull(); - }); - - it('should handle valid urls', () => { - expect(UtilsService.getHostname('https://bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getHostname('http://bitwarden.com')).toBe('bitwarden.com'); - expect(UtilsService.getHostname('http://vault.bitwarden.com')).toBe('vault.bitwarden.com'); - expect(UtilsService.getHostname('https://user:password@bitwarden.com:8080/password/sites?and&query#hash')).toBe('bitwarden.com'); - }); - - it('should support localhost and IP', () => { - expect(UtilsService.getHostname('https://localhost')).toBe('localhost'); - expect(UtilsService.getHostname('https://192.168.1.1')).toBe('192.168.1.1'); - }); - }); - - describe('newGuid', () => { - it('should create a valid guid', () => { - const validGuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - expect(UtilsService.newGuid()).toMatch(validGuid); - }); - }); - - describe('getBrowser', () => { - const original = navigator.userAgent; - - // Reset the userAgent. - afterAll(() => { - Object.defineProperty(navigator, 'userAgent', { - value: original - }); - }); - - it('should detect chrome', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' - }); - - const utilsService = new UtilsService(); - expect(utilsService.getBrowser()).toBe(BrowserType.Chrome); - }); - - it('should detect firefox', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0' - }); - - const utilsService = new UtilsService(); - expect(utilsService.getBrowser()).toBe(BrowserType.Firefox); - }); - - it('should detect opera', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3175.3 Safari/537.36 OPR/49.0.2695.0 (Edition developer)' - }); - - const utilsService = new UtilsService(); - expect(utilsService.getBrowser()).toBe(BrowserType.Opera); - }); - - it('should detect edge', () => { - Object.defineProperty(navigator, 'userAgent', { - configurable: true, - value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; ServiceUI 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063' - }); - - const utilsService = new UtilsService(); - expect(utilsService.getBrowser()).toBe(BrowserType.Edge); - }); - }); -}); diff --git a/src/services/utils.service.ts b/src/services/utils.service.ts deleted file mode 100644 index b2230de2b9a..00000000000 --- a/src/services/utils.service.ts +++ /dev/null @@ -1,407 +0,0 @@ -import * as tldjs from 'tldjs'; -import { BrowserType } from '../enums/browserType.enum'; -import { UtilsService as UtilsServiceInterface } from './abstractions/utils.service'; - -const AnalyticsIds = { - [BrowserType.Chrome]: 'UA-81915606-6', - [BrowserType.Firefox]: 'UA-81915606-7', - [BrowserType.Opera]: 'UA-81915606-8', - [BrowserType.Edge]: 'UA-81915606-9', - [BrowserType.Vivaldi]: 'UA-81915606-15', - [BrowserType.Safari]: 'UA-81915606-16', -}; - -export default class UtilsService implements UtilsServiceInterface { - static copyToClipboard(text: string, doc?: Document): void { - doc = doc || document; - if ((window as any).clipboardData && (window as any).clipboardData.setData) { - // IE specific code path to prevent textarea being shown while dialog is visible. - (window as any).clipboardData.setData('Text', text); - } else if (doc.queryCommandSupported && doc.queryCommandSupported('copy')) { - const textarea = doc.createElement('textarea'); - textarea.textContent = text; - // Prevent scrolling to bottom of page in MS Edge. - textarea.style.position = 'fixed'; - doc.body.appendChild(textarea); - textarea.select(); - - try { - // Security exception may be thrown by some browsers. - doc.execCommand('copy'); - } catch (e) { - // tslint:disable-next-line - console.warn('Copy to clipboard failed.', e); - } finally { - doc.body.removeChild(textarea); - } - } - } - - static urlBase64Decode(str: string): string { - let output = str.replace(/-/g, '+').replace(/_/g, '/'); - switch (output.length % 4) { - case 0: - break; - case 2: - output += '=='; - break; - case 3: - output += '='; - break; - default: - throw new Error('Illegal base64url string!'); - } - - return decodeURIComponent(escape(window.atob(output))); - } - - // ref: http://stackoverflow.com/a/2117523/1090359 - static newGuid(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - // tslint:disable-next-line - const r = Math.random() * 16 | 0; - // tslint:disable-next-line - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - // EFForg/OpenWireless - // ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js - static secureRandomNumber(min: number, max: number): number { - let rval = 0; - const range = max - min + 1; - const bitsNeeded = Math.ceil(Math.log2(range)); - if (bitsNeeded > 53) { - throw new Error('We cannot generate numbers larger than 53 bits.'); - } - - const bytesNeeded = Math.ceil(bitsNeeded / 8); - const mask = Math.pow(2, bitsNeeded) - 1; - // 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111 - - // Create byte array and fill with N random numbers - const byteArray = new Uint8Array(bytesNeeded); - window.crypto.getRandomValues(byteArray); - - let p = (bytesNeeded - 1) * 8; - for (let i = 0; i < bytesNeeded; i++) { - rval += byteArray[i] * Math.pow(2, p); - p -= 8; - } - - // Use & to apply the mask and reduce the number of recursive lookups - // tslint:disable-next-line - rval = rval & mask; - - if (rval >= range) { - // Integer out of acceptable range - return UtilsService.secureRandomNumber(min, max); - } - - // Return an integer that falls within the range - return min + rval; - } - - static fromB64ToArray(str: string): Uint8Array { - const binaryString = window.atob(str); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; - } - - static fromUtf8ToArray(str: string): Uint8Array { - const strUtf8 = unescape(encodeURIComponent(str)); - const arr = new Uint8Array(strUtf8.length); - for (let i = 0; i < strUtf8.length; i++) { - arr[i] = strUtf8.charCodeAt(i); - } - return arr; - } - - static fromBufferToB64(buffer: ArrayBuffer): string { - let binary = ''; - const bytes = new Uint8Array(buffer); - for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); - } - return window.btoa(binary); - } - - static fromBufferToUtf8(buffer: ArrayBuffer): string { - const bytes = new Uint8Array(buffer); - const encodedString = String.fromCharCode.apply(null, bytes); - return decodeURIComponent(escape(encodedString)); - } - - static saveObjToStorage(key: string, obj: any) { - return new Promise((resolve) => { - chrome.storage.local.set({ [key]: obj }, () => { - resolve(); - }); - }); - } - - static removeFromStorage(key: string) { - return new Promise((resolve) => { - chrome.storage.local.remove(key, () => { - resolve(); - }); - }); - } - - static getObjFromStorage(key: string): Promise { - return new Promise((resolve) => { - chrome.storage.local.get(key, (obj: any) => { - if (obj && (typeof obj[key] !== 'undefined') && obj[key] !== null) { - resolve(obj[key] as T); - } else { - resolve(null); - } - }); - }); - } - - static getDomain(uriString: string): string { - if (uriString == null) { - return null; - } - - uriString = uriString.trim(); - if (uriString === '') { - return null; - } - - if (uriString.startsWith('http://') || uriString.startsWith('https://')) { - try { - const url = new URL(uriString); - - if (url.hostname === 'localhost' || UtilsService.validIpAddress(url.hostname)) { - return url.hostname; - } - - const urlDomain = tldjs.getDomain(url.hostname); - return urlDomain != null ? urlDomain : url.hostname; - } catch (e) { } - } - - const domain = tldjs.getDomain(uriString); - if (domain != null) { - return domain; - } - - return null; - } - - static getHostname(uriString: string): string { - if (uriString == null) { - return null; - } - - uriString = uriString.trim(); - if (uriString === '') { - return null; - } - - if (uriString.startsWith('http://') || uriString.startsWith('https://')) { - try { - const url = new URL(uriString); - return url.hostname; - } catch (e) { } - } - - return null; - } - - private static validIpAddress(ipString: string): boolean { - // tslint:disable-next-line - const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - return ipRegex.test(ipString); - } - - private browserCache: BrowserType = null; - private analyticsIdCache: string = null; - - getBrowser(): BrowserType { - if (this.browserCache) { - return this.browserCache; - } - - if (navigator.userAgent.indexOf('Firefox') !== -1 || navigator.userAgent.indexOf('Gecko/') !== -1) { - this.browserCache = BrowserType.Firefox; - } else if ((!!(window as any).opr && !!opr.addons) || !!(window as any).opera || - navigator.userAgent.indexOf(' OPR/') >= 0) { - this.browserCache = BrowserType.Opera; - } else if (navigator.userAgent.indexOf(' Edge/') !== -1) { - this.browserCache = BrowserType.Edge; - } else if (navigator.userAgent.indexOf(' Vivaldi/') !== -1) { - this.browserCache = BrowserType.Vivaldi; - } else if ((window as any).chrome) { - this.browserCache = BrowserType.Chrome; - } - - return this.browserCache; - } - - getBrowserString(): string { - return BrowserType[this.getBrowser()].toLowerCase(); - } - - isFirefox(): boolean { - return this.getBrowser() === BrowserType.Firefox; - } - - isChrome(): boolean { - return this.getBrowser() === BrowserType.Chrome; - } - - isEdge(): boolean { - return this.getBrowser() === BrowserType.Edge; - } - - isOpera(): boolean { - return this.getBrowser() === BrowserType.Opera; - } - - isVivaldi(): boolean { - return this.getBrowser() === BrowserType.Vivaldi; - } - - isSafari(): boolean { - return this.getBrowser() === BrowserType.Safari; - } - - analyticsId(): string { - if (this.analyticsIdCache) { - return this.analyticsIdCache; - } - - this.analyticsIdCache = AnalyticsIds[this.getBrowser()]; - return this.analyticsIdCache; - } - - initListSectionItemListeners(doc: Document, angular: any): void { - if (!doc) { - throw new Error('doc parameter required'); - } - - const sectionItems = doc.querySelectorAll( - '.list-section-item:not([data-bw-events="1"])'); - const sectionFormItems = doc.querySelectorAll( - '.list-section-item:not([data-bw-events="1"]) input, ' + - '.list-section-item:not([data-bw-events="1"]) select, ' + - '.list-section-item:not([data-bw-events="1"]) textarea'); - - sectionItems.forEach((item) => { - (item as HTMLElement).dataset.bwEvents = '1'; - - item.addEventListener('click', (e) => { - if (e.defaultPrevented) { - return; - } - - const el = e.target as HTMLElement; - - // Some elements will already focus properly - if (el.tagName != null) { - switch (el.tagName.toLowerCase()) { - case 'label': case 'input': case 'textarea': case 'select': - return; - default: - break; - } - } - - const cell = el.closest('.list-section-item'); - if (!cell) { - return; - } - - const textFilter = 'input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"])'; - const text = cell.querySelectorAll(textFilter + ', textarea'); - const checkbox = cell.querySelectorAll('input[type="checkbox"]'); - const select = cell.querySelectorAll('select'); - - if (text.length > 0) { - (text[0] as HTMLElement).focus(); - } else if (select.length > 0) { - (select[0] as HTMLElement).focus(); - } else if (checkbox.length > 0) { - const cb = checkbox[0] as HTMLInputElement; - cb.checked = !cb.checked; - if (angular) { - angular.element(checkbox[0]).triggerHandler('click'); - } - } - }, false); - }); - - sectionFormItems.forEach((item) => { - const itemCell = item.closest('.list-section-item'); - (itemCell as HTMLElement).dataset.bwEvents = '1'; - - item.addEventListener('focus', (e: Event) => { - const el = e.target as HTMLElement; - const cell = el.closest('.list-section-item'); - if (!cell) { - return; - } - - cell.classList.add('active'); - }, false); - - item.addEventListener('blur', (e: Event) => { - const el = e.target as HTMLElement; - const cell = el.closest('.list-section-item'); - if (!cell) { - return; - } - - cell.classList.remove('active'); - }, false); - }); - } - - getDomain(uriString: string): string { - return UtilsService.getDomain(uriString); - } - - getHostname(uriString: string): string { - return UtilsService.getHostname(uriString); - } - - copyToClipboard(text: string, doc?: Document) { - UtilsService.copyToClipboard(text, doc); - } - - inSidebar(theWindow: Window): boolean { - return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=sidebar') > -1; - } - - inTab(theWindow: Window): boolean { - return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=tab') > -1; - } - - inPopout(theWindow: Window): boolean { - return theWindow.location.search !== '' && theWindow.location.search.indexOf('uilocation=popout') > -1; - } - - inPopup(theWindow: Window): boolean { - return theWindow.location.search === '' || theWindow.location.search.indexOf('uilocation=') === -1 || - theWindow.location.search.indexOf('uilocation=popup') > -1; - } - - saveObjToStorage(key: string, obj: any): Promise { - return UtilsService.saveObjToStorage(key, obj); - } - - removeFromStorage(key: string): Promise { - return UtilsService.removeFromStorage(key); - } - - getObjFromStorage(key: string): Promise { - return UtilsService.getObjFromStorage(key); - } -}