more services and async callthroughs for vault list/add

This commit is contained in:
Kyle Spearrin 2016-09-04 00:34:24 -04:00
parent 32f4ab4987
commit 8f0a24b1b9
14 changed files with 303 additions and 166 deletions

View File

@ -3,3 +3,4 @@ var tokenService = new TokenService();
var apiService = new ApiService(tokenService);
var userService = new UserService(tokenService, apiService);
var siteService = new SiteService(cryptoService, userService, apiService);
var folderService = new FolderService(cryptoService, userService, apiService);

View File

@ -24,6 +24,7 @@
"services/tokenService.js",
"services/apiService.js",
"services/userService.js",
"services/folderService.js",
"services/siteService.js",
"background.js"
]

View File

@ -1,4 +1,4 @@
var CipherString = function (encryptedString) {
var CipherString = function (encryptedString) {
this.encryptedString = encryptedString;
if (encryptedString) {
@ -20,20 +20,35 @@
};
}();
var Site = function (obj) {
var cryptoService = chrome.extension.getBackgroundPage().cryptoService;
var Site = function (obj, alreadyEncrypted) {
this.id = obj.id;
this.folderId = obj.folderId;
this.name = cryptoService.encrypt(obj.name);
this.uri = cryptoService.encrypt(obj.uri);
this.username = cryptoService.encrypt(obj.username);
this.password = cryptoService.encrypt(obj.password);
this.notes = cryptoService.encrypt(obj.notes);
if (alreadyEncrypted === true) {
this.name = obj.name;
this.uri = obj.uri;
this.username = obj.username;
this.password = obj.password;
this.notes = obj.notes;
}
else {
this.name = new CipherString(obj.name);
this.uri = new CipherString(obj.uri);
this.username = new CipherString(obj.username);
this.password = new CipherString(obj.password);
this.notes = new CipherString(obj.notes);
}
this.favorite = new obj.favorite;
};
var Folder = function (obj) {
var Folder = function (obj, alreadyEncrypted) {
this.id = obj.id;
this.name = new CipherString(obj.name);
if (alreadyEncrypted === true) {
this.name = obj.name;
}
else {
this.name = new CipherString(obj.name);
}
};

View File

@ -13,6 +13,9 @@
.factory('apiService', function () {
return chrome.extension.getBackgroundPage().apiService;
})
.factory('folderService', function () {
return chrome.extension.getBackgroundPage().folderService;
})
.factory('siteService', function () {
return chrome.extension.getBackgroundPage().siteService;
});

View File

@ -1,14 +1,41 @@
angular
.module('bit.vault')
.controller('vaultAddSiteController', function ($scope, siteService) {
.controller('vaultAddSiteController', function ($scope, siteService, cryptoService) {
$scope.site = {
folderId: null
};
$scope.createSite = function (model) {
var site = new Site(model);
siteService.save(model, function () {
$scope.createSite = function (model) {
var newModel = model;
encryptSite(newModel, function (siteModel) {
var site = new Site(siteModel, true);
siteService.save(site, function () {
$scope.close();
});
});
};
$scope.close = function () {
$scope.parentScope.closeAddSite();
};
function encryptSite(siteModel, callback) {
cryptoService.encrypt(siteModel.name, function (nameCipherString) {
siteModel.name = nameCipherString;
cryptoService.encrypt(siteModel.uri, function (uriCipherString) {
siteModel.uri = uriCipherString;
cryptoService.encrypt(siteModel.username, function (usernameCipherString) {
siteModel.username = usernameCipherString;
cryptoService.encrypt(siteModel.password, function (passwordCipherString) {
siteModel.password = passwordCipherString;
cryptoService.encrypt(siteModel.notes, function (notesCipherString) {
siteModel.notes = notesCipherString;
callback(siteModel);
});
});
});
});
});
}
});

View File

@ -1,7 +1,41 @@
angular
.module('bit.vault')
.controller('vaultController', function ($scope, $ionicModal) {
.controller('vaultController', function ($scope, $ionicModal, siteService, folderService) {
$scope.parentScope = $scope;
$scope.sites = [];
$scope.folders = [];
var decSites = [];
var decFolders = [{
id: null,
name: '(none)'
}];
folderService.getAll(function (folders) {
siteService.getAll(function (sites) {
for (var i = 0; i < folders.length; i++) {
decFolders.push({
id: folders[i].id,
name: folders[i].name.decrypt()
});
}
for (var j = 0; j < sites.length; j++) {
decSites.push({
id: sites[j].id,
folderId: sites[j].folderId,
favorite: sites[j].favorite,
name: sites[j].name.decrypt(),
username: sites[j].username.decrypt()
});
}
$scope.sites = decSites;
$scope.folders = decFolders;
});
});
$scope.addSite = function () {
$ionicModal.fromTemplateUrl('app/vault/views/vaultAddSite.html', {
scope: $scope,

View File

@ -2,9 +2,12 @@
<ion-nav-buttons side="secondary">
<button class="button button-icon icon ion-ios-plus-empty" ng-click="addSite()"></button>
</ion-nav-buttons>
<ion-content class="padding">
<p>
Some content for your vault.
</p>
<ion-content>
<div class="list">
<div class="item item-button-right">
Site 1
<button class="button button-clear button-dark"><i class="icon ion-more"></i></button>
</div>
</div>
</ion-content>
</ion-view>

View File

@ -1,10 +1,10 @@
<ion-modal-view>
<ion-modal-view ng-controller="vaultAddSiteController">
<ion-header-bar class="bar bar-header bar-positive">
<button class="button button-clear button-primary" ng-click="addSiteModal.hide()">Cancel</button>
<button class="button button-clear button-primary" ng-click="close()">Cancel</button>
<h1 class="title">Add Site</h1>
<button class="button button-clear button-primary" ng-click="createSite(site)">Save</button>
</ion-header-bar>
<ion-content ng-controller="vaultAddSiteController">
<ion-content>
<div class="list">
<div class="item item-divider">
Site Information

View File

@ -1,9 +1,11 @@
function ApiService(tokenService) {
this.baseUrl = 'https://api.bitwarden.com';
this.tokenService = tokenService;
initApiService();
};
!function () {
function initApiService() {
// Auth APIs
ApiService.prototype.postToken = function (tokenRequest, success, error) {
@ -235,4 +237,4 @@
function handleError(errorCallback, jqXHR, textStatus, errorThrown) {
errorCallback(new ErrorResponse(jqXHR));
}
}();
};

View File

@ -1,8 +1,8 @@
function CryptoService() {
initCryptoService();
};
!function () {
function initCryptoService() {
var _key,
_b64Key,
_aes;
@ -32,9 +32,9 @@
return callback(_key);
}
chrome.storage.local.get('key', function (key) {
if (key) {
_key = sjcl.codec.base64.toBits(key);
chrome.storage.local.get('key', function (obj) {
if (obj && obj.key) {
_key = sjcl.codec.base64.toBits(obj.key);
}
if (b64 && b64 === true) {
@ -80,46 +80,60 @@
return sjcl.codec.base64.fromBits(hashBits);
};
CryptoService.prototype.getAes = function () {
if (!_aes && this.getKey()) {
_aes = new sjcl.cipher.aes(this.getKey());
CryptoService.prototype.getAes = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
return _aes;
this.getKey(false, function (key) {
if (!_aes && key) {
_aes = new sjcl.cipher.aes(key);
}
callback(_aes);
});
};
CryptoService.prototype.encrypt = function (plaintextValue, key) {
if (!this.getKey() && !key) {
throw 'Encryption key unavailable.';
CryptoService.prototype.encrypt = function (plaintextValue, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
if (!key) {
key = this.getKey();
}
this.getKey(false, function (key) {
if (!key) {
throw 'Encryption key unavailable.';
}
var response = {};
var params = {
mode: "cbc",
iv: sjcl.random.randomWords(4, 0)
};
var response = {};
var params = {
mode: "cbc",
iv: sjcl.random.randomWords(4, 0)
};
var ctJson = sjcl.encrypt(key, plaintextValue, params, response);
var ctJson = sjcl.encrypt(key, plaintextValue, params, response);
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
var iv = sjcl.codec.base64.fromBits(response.iv);
var ct = ctJson.match(/"ct":"([^"]*)"/)[1];
var iv = sjcl.codec.base64.fromBits(response.iv);
return new CipherString(iv + "|" + ct);
callback(new CipherString(iv + "|" + ct));
});
};
CryptoService.prototype.decrypt = function (cipherString) {
if (!this.getAes()) {
throw 'AES encryption unavailable.';
CryptoService.prototype.decrypt = function (cipherStrin, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var ivBits = sjcl.codec.base64.toBits(cipherString.initializationVector);
var ctBits = sjcl.codec.base64.toBits(cipherString.cipherText);
this.getAes(function (aes) {
if (!aes) {
throw 'AES encryption unavailable.';
}
var decBits = sjcl.mode.cbc.decrypt(this.getAes(), ctBits, ivBits, null);
return sjcl.codec.utf8String.fromBits(decBits);
var ivBits = sjcl.codec.base64.toBits(cipherString.initializationVector);
var ctBits = sjcl.codec.base64.toBits(cipherString.cipherText);
var decBits = sjcl.mode.cbc.decrypt(aes, ctBits, ivBits, null);
callback(sjcl.codec.utf8String.fromBits(decBits));
});
};
}();
};

View File

@ -0,0 +1,50 @@
function FolderService(cryptoService, userService, apiService) {
this.cryptoService = cryptoService;
this.userService = userService;
this.apiService = apiService;
initFolderService();
};
function initFolderService() {
this.userService.getUserId(function (userId) {
var foldersKey = 'folders_' + userId;
FolderService.prototype.get = function (id, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
chrome.storage.local.get(foldersKey, function (obj) {
var folders = obj[foldersKey];
if (id in folders) {
callback(new Folder(folders[id]));
return;
}
callback(null);
});
};
FolderService.prototype.getAll = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
chrome.storage.local.get(foldersKey, function (obj) {
var folders = obj[foldersKey];
var response = [];
for (var id in folders) {
var folder = folders[id];
response.push(new Folder(folder));
}
callback(response);
});
};
function handleError() {
// TODO: check for unauth or forbidden and logout
}
});
};

View File

@ -2,130 +2,115 @@
this.cryptoService = cryptoService;
this.userService = userService;
this.apiService = apiService;
initSiteService();
};
!function () {
var ciphersKey = 'ciphers_' + this.userService.userId;
function initSiteService() {
this.userService.getUserId(function (userId) {
var sitesKey = 'sites_' + userId;
SiteService.prototype.get = function (id, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
SiteService.prototype.get = function (id, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
chrome.storage.local.get(sitesKey, function (obj) {
var sites = obj[sitesKey];
if (id in sites) {
callback(new Site(sites[id]));
return;
}
chrome.storage.local.get(ciphersKey, function (obj) {
if (!obj) {
callback(null);
});
};
SiteService.prototype.getAll = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var sites = obj[ciphersKey];
if (id in sites) {
callback(new Site(sites[id]));
return;
chrome.storage.local.get(sitesKey, function (obj) {
var sites = obj[sitesKey];
var response = [];
for (var id in sites) {
response.push(new Site(sites[id]));
}
callback(response);
});
};
SiteService.prototype.save = function (site, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
callback(null);
});
};
var newRecord = site.id === null,
self = this;
SiteService.prototype.getAll = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
chrome.storage.local.get(ciphersKey, function (obj) {
if (!obj) {
callback([]);
var request = new SiteRequest(site);
if (newRecord) {
self.apiService.postSite(request, apiSuccess, handleError);
}
else {
self.apiService.putSite(site.id, request, apiSuccess, handleError);
}
var sites = obj[ciphersKey];
var response = [];
for (var id in sites) {
response.push(new Site(sites[id]));
}
function apiSuccess(response) {
userService.getUserId(function (userId) {
var data = new SiteData(response, userId);
callback(response);
});
};
chrome.storage.local.get(sitesKey, function (obj) {
var sites = obj[sitesKey];
if (!newRecord && site.id in sites) {
sites[site.id] = data;
}
else {
sites.push(data);
site.id = data.id;
}
SiteService.prototype.save = function (site, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var newRecord = site.id === null,
self = this;
var request = new SiteRequest(site);
if (newRecord) {
self.apiService.postSite(request, apiSuccess, handleError);
}
else {
self.apiService.putSite(site.id, request, apiSuccess, handleError);
}
function apiSuccess(response) {
userService.getUserId(function (userId) {
var data = new SiteData(response, userId);
chrome.storage.local.get(ciphersKey, function (obj) {
if (!obj) {
obj = {};
obj[ciphersKey] = [];
}
var sites = obj[ciphersKey];
if (!newRecord && site.id in sites) {
sites[site.id] = data;
}
else {
sites.push(data);
site.id = data.id;
}
obj[ciphersKey] = sites;
chrome.storage.local.set(obj, function () {
callback(site);
obj[sitesKey] = sites;
chrome.storage.local.set(obj, function () {
callback(site);
});
});
});
});
}
};
}
};
SiteService.prototype.delete = function (id, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
SiteService.prototype.delete = function (id, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
self.apiService.deleteCipher(id, apiSuccess, handleError);
self.apiService.deleteCipher(id, apiSuccess, handleError);
function apiSuccess(response) {
userService.getUserId(function (userId) {
chrome.storage.local.get(ciphersKey, function (obj) {
if (!obj) {
obj = {};
obj[ciphersKey] = [];
}
function apiSuccess(response) {
userService.getUserId(function (userId) {
chrome.storage.local.get(sitesKey, function (obj) {
var sites = obj[sitesKey];
if (id in sites) {
var index = sites.indexOf(sites[id]);
sites.splice(index, 1);
var sites = obj[ciphersKey];
if (id in sites) {
var index = sites.indexOf(sites[id]);
sites.splice(index, 1);
obj[ciphersKey] = sites;
chrome.storage.local.set(obj, function () {
obj[sitesKey] = sites;
chrome.storage.local.set(obj, function () {
callback();
});
}
else {
callback();
});
}
else {
callback();
}
}
});
});
});
}
};
function handleError() {
// TODO: check for unauth or forbidden and logout
}
};
function handleError() {
// TODO: check for unauth or forbidden and logout
}
}();
});
};

View File

@ -1,8 +1,8 @@
function TokenService() {
initTokenService();
};
!function () {
function initTokenService() {
var _token;
TokenService.prototype.setToken = function (token, callback) {
@ -101,4 +101,4 @@
}
return window.decodeURIComponent(escape(window.atob(output))); //polyfill https://github.com/davidchambers/Base64.js
};
}();
};

View File

@ -1,9 +1,11 @@
function UserService(tokenService, apiService) {
this.tokenService = tokenService;
this.apiService = apiService;
initUserService();
};
!function () {
function initUserService() {
var _userProfile = null;
UserService.prototype.getUserId = function (callback) {
@ -90,4 +92,4 @@
callback(profile !== null && profile.twoFactor);
});
};
}();
};