From 835bb6ffa713f3facd367abed248bdac94aa0730 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 21 Sep 2017 22:45:24 -0400 Subject: [PATCH] login custom fields view/edit --- src/_locales/en/messages.json | 12 ++ src/background.js | 10 +- src/models/api/requestModels.js | 11 ++ src/models/api/responseModels.js | 5 +- src/models/dataModels.js | 16 ++- src/models/domainModels.js | 106 +++++++++++++----- .../app/vault/vaultEditLoginController.js | 3 +- .../app/vault/vaultViewLoginController.js | 27 +++-- src/popup/app/vault/views/vaultEditLogin.html | 18 +++ src/popup/app/vault/views/vaultViewLogin.html | 32 +++++- src/services/apiService.js | 8 +- src/services/constantsService.js | 10 ++ src/services/loginService.js | 47 +++++++- 13 files changed, 255 insertions(+), 50 deletions(-) diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index fd049357..a07b1fd5 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -753,5 +753,17 @@ }, "privateModeMessage": { "message": "Unfortunately this window is not available in private mode for this browser." + }, + "customFields": { + "message": "Custom Fields" + }, + "copyValue": { + "message": "Copy Value" + }, + "toggleValue": { + "message": "Toggle Value" + }, + "value": { + "message": "Value" } } diff --git a/src/background.js b/src/background.js index 3573a309..fcd8b5e5 100644 --- a/src/background.js +++ b/src/background.js @@ -289,12 +289,12 @@ var bg_isBackground = true, chrome.webRequest.onCompleted.addListener(completeAuthRequest, { urls: ['http://*/*'] }); chrome.webRequest.onErrorOccurred.addListener(completeAuthRequest, { urls: ['http://*/*'] }); + } - function completeAuthRequest(details) { - var i = pendingAuthRequests.indexOf(details.requestId); - if (i > -1) { - pendingAuthRequests.splice(i, 1); - } + function completeAuthRequest(details) { + var i = pendingAuthRequests.indexOf(details.requestId); + if (i > -1) { + pendingAuthRequests.splice(i, 1); } } diff --git a/src/models/api/requestModels.js b/src/models/api/requestModels.js index 4e63292c..684cdcbb 100644 --- a/src/models/api/requestModels.js +++ b/src/models/api/requestModels.js @@ -18,6 +18,17 @@ default: break; } + + if (cipher.fields) { + this.fields = []; + for (var i = 0; i < cipher.fields.length; i++) { + this.fields.push({ + type: cipher.fields[i].type, + name: cipher.fields[i].name ? cipher.fields[i].name.encryptedString : null, + value: cipher.fields[i].value ? cipher.fields[i].value.encryptedString : null, + }); + } + } }; var FolderRequest = function (folder) { diff --git a/src/models/api/responseModels.js b/src/models/api/responseModels.js index 6c186005..d7636dd9 100644 --- a/src/models/api/responseModels.js +++ b/src/models/api/responseModels.js @@ -145,16 +145,17 @@ var SyncResponse = function (response) { this.profile = new ProfileResponse(response.Profile); } + var i; this.folders = []; if (response.Folders) { - for (var i = 0; i < response.Folders.length; i++) { + for (i = 0; i < response.Folders.length; i++) { this.folders.push(new FolderResponse(response.Folders[i])); } } this.ciphers = []; if (response.Ciphers) { - for (var i = 0; i < response.Ciphers.length; i++) { + for (i = 0; i < response.Ciphers.length; i++) { this.ciphers.push(new CipherResponse(response.Ciphers[i])); } } diff --git a/src/models/dataModels.js b/src/models/dataModels.js index 3ae7acd6..03ea444e 100644 --- a/src/models/dataModels.js +++ b/src/models/dataModels.js @@ -31,9 +31,17 @@ var LoginData = function (response, userId) { this.favorite = response.favorite; this.revisionDate = response.revisionDate; + var i; + if (response.data.Fields) { + this.fields = []; + for (i = 0; i < response.data.Fields.length; i++) { + this.fields.push(new FieldData(response.data.Fields[i])); + } + } + if (response.attachments) { this.attachments = []; - for (var i = 0; i < response.attachments.length; i++) { + for (i = 0; i < response.attachments.length; i++) { this.attachments.push(new AttachmentData(response.attachments[i])); } } @@ -46,3 +54,9 @@ var AttachmentData = function (response) { this.size = response.size; this.sizeName = response.sizeName; }; + +var FieldData = function (response) { + this.type = response.Type; + this.name = response.Name; + this.value = response.Value; +}; diff --git a/src/models/domainModels.js b/src/models/domainModels.js index 10b1815e..a17f9fa4 100644 --- a/src/models/domainModels.js +++ b/src/models/domainModels.js @@ -114,15 +114,39 @@ var Login = function (obj, alreadyEncrypted, localData) { this.totp = obj.totp ? new CipherString(obj.totp) : null; } + var i; if (obj.attachments) { this.attachments = []; - for (var i = 0; i < obj.attachments.length; i++) { + for (i = 0; i < obj.attachments.length; i++) { this.attachments.push(new Attachment(obj.attachments[i], alreadyEncrypted)); } } else { this.attachments = null; } + + if (obj.fields) { + this.fields = []; + for (i = 0; i < obj.fields.length; i++) { + this.fields.push(new Field(obj.fields[i], alreadyEncrypted)); + } + } + else { + this.fields = null; + } +}; + +var Field = function (obj, alreadyEncrypted) { + this.type = obj.type; + + if (alreadyEncrypted === true) { + this.name = obj.name ? obj.name : null; + this.value = obj.value ? obj.value : null; + } + else { + this.name = obj.name ? new CipherString(obj.name) : null; + this.value = obj.value ? new CipherString(obj.value) : null; + } }; var Attachment = function (obj, alreadyEncrypted) { @@ -182,9 +206,9 @@ var Folder = function (obj, alreadyEncrypted) { }; var attachments = []; - var deferred = Q.defer(); + var fields = []; - self.name.decrypt(self.organizationId).then(function (val) { + return self.name.decrypt(self.organizationId).then(function (val) { model.name = val; if (self.uri) { return self.uri.decrypt(self.organizationId); @@ -222,24 +246,58 @@ var Folder = function (obj, alreadyEncrypted) { model.totp = val; if (self.attachments) { - var attachmentPromises = []; - for (var i = 0; i < self.attachments.length; i++) { - (function (attachment) { - var promise = attachment.decrypt(self.organizationId).then(function (decAttachment) { - attachments.push(decAttachment); - }); - attachmentPromises.push(promise); - })(self.attachments[i]); - } - return Q.all(attachmentPromises); + return self.attachments.reduce(function (promise, attachment) { + return promise.then(function () { + return attachment.decrypt(self.organizationId); + }).then(function (decAttachment) { + attachments.push(decAttachment); + }); + }, Q()); } return; }).then(function () { model.attachments = attachments.length ? attachments : null; - deferred.resolve(model); - }); - return deferred.promise; + if (self.fields) { + return self.fields.reduce(function (promise, field) { + return promise.then(function () { + return field.decrypt(self.organizationId); + }).then(function (decField) { + fields.push(decField); + }); + }, Q()); + } + return; + }).then(function () { + model.fields = fields.length ? fields : null; + return model; + }, function (e) { + console.log(e); + }); + }; + + Field.prototype.decrypt = function (orgId) { + var self = this; + var model = { + type: self.type + }; + + return Q().then(function () { + if (self.name) { + return self.name.decrypt(orgId); + } + return null; + }).then(function (val) { + model.name = val; + + if (self.value) { + return self.value.decrypt(orgId); + } + return null; + }).then(function (val) { + model.value = val; + return model; + }); }; Attachment.prototype.decrypt = function (orgId) { @@ -251,14 +309,10 @@ var Folder = function (obj, alreadyEncrypted) { url: self.url }; - var deferred = Q.defer(); - - self.fileName.decrypt(orgId).then(function (val) { + return self.fileName.decrypt(orgId).then(function (val) { model.fileName = val; - deferred.resolve(model); + return model; }); - - return deferred.promise; }; Folder.prototype.decrypt = function () { @@ -267,13 +321,9 @@ var Folder = function (obj, alreadyEncrypted) { id: self.id }; - var deferred = Q.defer(); - - self.name.decrypt().then(function (val) { + return self.name.decrypt().then(function (val) { model.name = val; - deferred.resolve(model); + return model; }); - - return deferred.promise; }; })(); diff --git a/src/popup/app/vault/vaultEditLoginController.js b/src/popup/app/vault/vaultEditLoginController.js index 99a94cbf..13dfa4f1 100644 --- a/src/popup/app/vault/vaultEditLoginController.js +++ b/src/popup/app/vault/vaultEditLoginController.js @@ -2,8 +2,9 @@ angular .module('bit.vault') .controller('vaultEditLoginController', function ($scope, $state, $stateParams, loginService, folderService, - cryptoService, $q, toastr, SweetAlert, utilsService, $analytics, i18nService) { + cryptoService, $q, toastr, SweetAlert, utilsService, $analytics, i18nService, constantsService) { $scope.i18n = i18nService; + $scope.constants = constantsService; $scope.showAttachments = !utilsService.isEdge(); var loginId = $stateParams.loginId; var fromView = $stateParams.fromView; diff --git a/src/popup/app/vault/vaultViewLoginController.js b/src/popup/app/vault/vaultViewLoginController.js index afc07eab..4476be14 100644 --- a/src/popup/app/vault/vaultViewLoginController.js +++ b/src/popup/app/vault/vaultViewLoginController.js @@ -2,7 +2,9 @@ angular .module('bit.vault') .controller('vaultViewLoginController', function ($scope, $state, $stateParams, loginService, toastr, $q, - $analytics, i18nService, utilsService, totpService, $timeout, tokenService, $window, cryptoService, SweetAlert) { + $analytics, i18nService, utilsService, totpService, $timeout, tokenService, $window, cryptoService, SweetAlert, + constantsService) { + $scope.constants = constantsService; $scope.i18n = i18nService; $scope.showAttachments = !utilsService.isEdge(); var from = $stateParams.from, @@ -19,12 +21,7 @@ angular $scope.login = model; if (model.password) { - var maskedPassword = ''; - for (var i = 0; i < model.password.length; i++) { - maskedPassword += '•'; - } - - $scope.login.maskedPassword = maskedPassword; + $scope.login.maskedPassword = $scope.maskValue(model.password); } if (model.uri) { @@ -65,6 +62,10 @@ angular }); }; + $scope.toggleFieldValue = function (field) { + field.showValue = !field.showValue; + }; + $scope.close = function () { if (from === 'current') { $state.go('tabs.current', { @@ -94,6 +95,18 @@ angular toastr.info(i18n.browserNotSupportClipboard); }; + $scope.maskValue = function (value) { + if (!value) { + return value; + } + + var masked = ''; + for (var i = 0; i < value.length; i++) { + masked += '•'; + } + return masked; + }; + $scope.clipboardSuccess = function (e, type) { e.clearSelection(); $analytics.eventTrack('Copied ' + (type === i18nService.username ? 'Username' : 'Password')); diff --git a/src/popup/app/vault/views/vaultEditLogin.html b/src/popup/app/vault/views/vaultEditLogin.html index 5b36ac7a..9dab5ec0 100644 --- a/src/popup/app/vault/views/vaultEditLogin.html +++ b/src/popup/app/vault/views/vaultEditLogin.html @@ -73,6 +73,24 @@ +
+
+ {{i18n.customFields}} +
+
+
+ + + + +
+
+
+
+
+ {{i18n.customFields}} +
+
+
+ + + + + + + {{field.name}} +
+ {{field.value || ' '}} +
+
+ {{maskValue(field.value)}} + {{field.value}} +
+
+ + +
+
+
+
{{i18n.attachments}} diff --git a/src/services/apiService.js b/src/services/apiService.js index ac250f7c..72827f18 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -30,8 +30,8 @@ function initApiService() { } // Desktop - //self.baseUrl = 'http://localhost:4000'; - //self.identityBaseUrl = 'http://localhost:33656'; + self.baseUrl = 'http://localhost:4000'; + self.identityBaseUrl = 'http://localhost:33656'; // Desktop HTTPS //self.baseUrl = 'https://localhost:44377'; @@ -46,8 +46,8 @@ function initApiService() { //self.identityBaseUrl = 'https://preview-identity.bitwarden.com'; // Production - self.baseUrl = 'https://api.bitwarden.com'; - self.identityBaseUrl = 'https://identity.bitwarden.com'; + //self.baseUrl = 'https://api.bitwarden.com'; + //self.identityBaseUrl = 'https://identity.bitwarden.com'; }; // Auth APIs diff --git a/src/services/constantsService.js b/src/services/constantsService.js index 37a8e75a..4618609f 100644 --- a/src/services/constantsService.js +++ b/src/services/constantsService.js @@ -17,6 +17,16 @@ function ConstantsService(i18nService) { Rsa2048_OaepSha256_HmacSha256_B64: 5, Rsa2048_OaepSha1_HmacSha256_B64: 6 }, + cipherType: { + login: 1, + secureNote: 2, + card: 3 + }, + fieldType: { + text: 0, + hidden: 1, + boolean: 2 + }, twoFactorProvider: { u2f: 4, yubikey: 3, diff --git a/src/services/loginService.js b/src/services/loginService.js index 33ba1d61..af1f09cd 100644 --- a/src/services/loginService.js +++ b/src/services/loginService.js @@ -44,6 +44,51 @@ function initLoginService() { return self.cryptoService.encrypt(login.totp, orgKey); }).then(function (cs) { model.totp = cs; + return self.encryptFields(login.fields, orgKey); + }).then(function (fields) { + model.fields = fields; + return model; + }); + }; + + LoginService.prototype.encryptFields = function (fields, key) { + var self = this; + if (!fields || !fields.length) { + return null; + } + + var encFields = []; + return fields.reduce(function (promise, field) { + return promise.then(function () { + return self.encryptField(field, key); + }).then(function (encField) { + encFields.push(encField); + }); + }, Q()).then(function () { + return encFields; + }); + }; + + LoginService.prototype.encryptField = function (field, key) { + var self = this; + + var model = { + type: field.type + }; + + return Q().then(function () { + if (!field.name || field.name === '') { + return null; + } + return self.cryptoService.encrypt(field.name, key); + }).then(function (cs) { + model.name = cs; + if (!field.value || field.value === '') { + return null; + } + return self.cryptoService.encrypt(field.value, key); + }).then(function (cs) { + model.value = cs; return model; }); }; @@ -533,7 +578,7 @@ function initLoginService() { return 0; }; - function sortLoginsByLastUsed(a ,b) { + function sortLoginsByLastUsed(a, b) { var aLastUsed = a.localData && a.localData.lastUsedDate ? a.localData.lastUsedDate : null; var bLastUsed = b.localData && b.localData.lastUsedDate ? b.localData.lastUsedDate : null;