Added setting to not store key via lock options, only keeping it in memory. Fixed some i18n and created constants service

This commit is contained in:
Kyle Spearrin 2016-10-24 22:16:47 -04:00
parent 25fef2d826
commit 80ed37ada6
18 changed files with 400 additions and 65 deletions

View File

@ -382,5 +382,61 @@
"browserNotSupportClipboard": {
"message": "Your web browser does not support easy clipboard copying. Copy it manually instead.",
"description": "Your web browser does not support easy clipboard copying. Copy it manually instead."
},
"verifyMasterPassword": {
"message": "Verify Master Password",
"description": "Verify Master Password"
},
"invalidMasterPassword": {
"message": "Invalid master password",
"description": "Invalid master password"
},
"errorsHaveOccurred": {
"message": "Invalid master password",
"description": "Invalid master password"
},
"lockOptions": {
"message": "Lock Options",
"description": "Lock Options"
},
"immediately": {
"message": "Immediately",
"description": "Immediately"
},
"oneMinute": {
"message": "1 minute",
"description": "1 minute"
},
"fiveMinutes": {
"message": "5 minutes",
"description": "5 minutes"
},
"fifteenMinutes": {
"message": "15 minutes",
"description": "15 minutes"
},
"thirtyMinutes": {
"message": "30 minutes",
"description": "30 minutes"
},
"oneHour": {
"message": "1 hour",
"description": "1 hour"
},
"fourHours": {
"message": "4 hours",
"description": "4 hours"
},
"onRestart": {
"message": "On Restart",
"description": "On Restart"
},
"never": {
"message": "Never",
"description": "Never"
},
"security": {
"message": "Security",
"description": "Security"
}
}

View File

@ -1,7 +1,8 @@
var isBackground = true;
var i18nService = new i18nService();
var constantsService = new ConstantsService();
var utilsService = new UtilsService();
var cryptoService = new CryptoService();
var cryptoService = new CryptoService(constantsService);
var tokenService = new TokenService();
var apiService = new ApiService(tokenService);
var userService = new UserService(tokenService, apiService, cryptoService);

View File

@ -33,6 +33,7 @@
"models/dataModels.js",
"models/domainModels.js",
"services/i18nService.js",
"services/constantsService.js",
"services/utilsService.js",
"services/cryptoService.js",
"services/tokenService.js",

View File

@ -7,7 +7,7 @@
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.login}}</button>
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
</div>
<div class="title">{{i18n.bitwarden}}</div>
<div class="title">{{i18n.appName}}</div>
</div>
<div class="content">
<div class="list">

View File

@ -14,5 +14,6 @@
'bit.current',
'bit.vault',
'bit.settings',
'bit.tools'
'bit.tools',
'bit.lock'
]);

View File

@ -163,27 +163,41 @@
controller: 'settingsEditFolderController',
data: { authorize: true },
params: { animation: null }
})
.state('lock', {
url: '/lock',
templateUrl: 'app/lock/views/lock.html',
controller: 'lockController',
data: { authorize: true },
params: { animation: null }
});
})
.run(function ($rootScope, userService, loginService, tokenService, $state) {
.run(function ($rootScope, userService, loginService, cryptoService, tokenService, $state) {
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
tokenService.getToken(function (token) {
userService.isAuthenticated(function (isAuthenticated) {
if (!toState.data || !toState.data.authorize) {
if (isAuthenticated && !tokenService.isTokenExpired(token)) {
event.preventDefault();
$state.go('tabs.current');
cryptoService.getKey(false, function (key) {
tokenService.getToken(function (token) {
userService.isAuthenticated(function (isAuthenticated) {
if (!toState.data || !toState.data.authorize) {
if (isAuthenticated && !tokenService.isTokenExpired(token)) {
event.preventDefault();
if (!key) {
$state.go('lock');
}
else {
$state.go('tabs.current');
}
}
return;
}
return;
}
if (!isAuthenticated || tokenService.isTokenExpired(token)) {
event.preventDefault();
loginService.logOut(function () {
$state.go('home');
});
}
if (!isAuthenticated || tokenService.isTokenExpired(token)) {
event.preventDefault();
loginService.logOut(function () {
$state.go('home');
});
}
});
});
});
});

View File

@ -0,0 +1,45 @@
angular
.module('bit.lock')
.controller('lockController', function ($scope, $state, $analytics, i18nService, loginService, cryptoService, toastr,
userService, SweetAlert) {
$scope.i18n = i18nService;
$('#master-password').focus();
$scope.logOut = function () {
loginService.logOut(function () {
SweetAlert.swal({
title: 'Log Out',
text: 'Are you sure you want to log out?',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'Cancel'
}, function (confirmed) {
if (confirmed) {
loginService.logOut(function () {
$analytics.eventTrack('Logged Out');
$state.go('home');
});
}
});
});
};
$scope.submit = function () {
userService.getEmail(function (email) {
var key = cryptoService.makeKey($scope.masterPassword, email);
cryptoService.hashPassword($scope.masterPassword, key, function (keyHash) {
cryptoService.getKeyHash(true, function (storedKeyHash) {
if (storedKeyHash && keyHash && storedKeyHash === keyHash) {
cryptoService.setKey(key, function () {
$state.go('tabs.current');
});
}
else {
toastr.error(i18nService.invalidMasterPassword, i18nService.errorsHaveOccurred);
}
});
});
});
};
});

View File

@ -0,0 +1,2 @@
angular
.module('bit.lock', ['ngAnimate', 'toastr']);

View File

@ -0,0 +1,25 @@
<form name="theForm" ng-submit="submit()" bit-form="submitPromise">
<div class="header">
<div class="right">
<button type="submit" class="btn btn-link">{{i18n.submit}}</button>
</div>
<div class="title">{{i18n.verifyMasterPassword}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-icon-input">
<i class="fa fa-lock fa-lg fa-fw"></i>
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
<input id="master-password" type="password" name="MasterPassword" placeholder="{{i18n.masterPass}}"
ng-model="masterPassword">
</div>
</div>
</div>
</div>
<p class="text-center text-accent">
<a ng-click="logOut()" href="">{{i18n.logOut}}</a>
</p>
</div>
</form>

View File

@ -39,4 +39,7 @@
})
.factory('i18nService', function () {
return chrome.extension.getBackgroundPage().i18nService;
})
.factory('constantsService', function () {
return chrome.extension.getBackgroundPage().constantsService;
});

View File

@ -19,17 +19,19 @@
tokenService.setToken(response.token, function () {
cryptoService.setKey(key, function () {
if (response.profile) {
userService.setUserId(response.profile.id, function () {
userService.setEmail(response.profile.email, function () {
chrome.runtime.sendMessage({ command: 'loggedIn' });
deferred.resolve(response);
cryptoService.setKeyHash(hashedPassword, function () {
if (response.profile) {
userService.setUserId(response.profile.id, function () {
userService.setEmail(response.profile.email, function () {
chrome.runtime.sendMessage({ command: 'loggedIn' });
deferred.resolve(response);
});
});
});
}
else {
deferred.resolve(response);
}
}
else {
deferred.resolve(response);
}
});
});
});
}, function (error) {
@ -68,14 +70,16 @@
userService.getUserId(function (userId) {
tokenService.clearToken(function () {
cryptoService.clearKey(function () {
userService.clearUserId(function () {
userService.clearEmail(function () {
siteService.clear(userId, function () {
folderService.clear(userId, function () {
$rootScope.vaultSites = null;
$rootScope.vaultFolders = null;
chrome.runtime.sendMessage({ command: 'loggedOut' });
callback();
cryptoService.clearKeyHash(function () {
userService.clearUserId(function () {
userService.clearEmail(function () {
siteService.clear(userId, function () {
folderService.clear(userId, function () {
$rootScope.vaultSites = null;
$rootScope.vaultFolders = null;
chrome.runtime.sendMessage({ command: 'loggedOut' });
callback();
});
});
});
});

View File

@ -2,19 +2,42 @@
.module('bit.settings')
.controller('settingsController', function ($scope, loginService, $state, SweetAlert, utilsService, $analytics,
i18nService) {
var gaKey = 'disableGa';
i18nService, constantsService, cryptoService) {
utilsService.initListSectionItemListeners($(document), angular);
$scope.disableGa = false;
$scope.lockOption = '';
$scope.i18n = i18nService;
chrome.storage.local.get(gaKey, function (obj) {
if (obj && obj[gaKey]) {
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
if (obj && obj[constantsService.disableGaKey]) {
$scope.disableGa = true;
}
else {
$scope.disableGa = false;
}
});
chrome.storage.local.get(constantsService.lockOptionKey, function (obj) {
if (obj && (obj[constantsService.lockOptionKey] || obj[constantsService.lockOptionKey] === 0)) {
$scope.lockOption = obj[constantsService.lockOptionKey].toString();
}
else {
$scope.lockOption = '';
}
});
$scope.changeLockOption = function () {
var obj = {};
obj[constantsService.lockOptionKey] = null;
if ($scope.lockOption && $scope.lockOption !== '') {
obj[constantsService.lockOptionKey] = parseInt($scope.lockOption);
}
chrome.storage.local.set(obj, function () {
cryptoService.toggleKey(function () { });
});
};
$scope.logOut = function () {
SweetAlert.swal({
title: 'Log Out',
@ -82,20 +105,20 @@
}
$scope.updateGa = function () {
chrome.storage.local.get(gaKey, function (obj) {
if (obj[gaKey]) {
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
if (obj[constantsService.disableGaKey]) {
// enable
obj[gaKey] = false;
obj[constantsService.disableGaKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Google Analytics');
obj[gaKey] = true;
obj[constantsService.disableGaKey] = true;
}
chrome.storage.local.set(obj, function () {
$scope.disableGa = obj[gaKey];
if (!obj[gaKey]) {
$scope.disableGa = obj[constantsService.disableGaKey];
if (!obj[constantsService.disableGaKey]) {
$analytics.eventTrack('Enabled Google Analytics');
}
});

View File

@ -3,6 +3,31 @@
</div>
<div class="content content-tabs">
<div class="list">
<div class="list-section">
<div class="list-section-header">
{{i18n.security}}
</div>
<div class="list-section-items">
<div class="list-section-item">
<label for="lock-option" class="item-label">{{i18n.lockOptions}}</label>
<select id="lock-option" name="LockOption" ng-model="lockOption" ng-change="changeLockOption()">
<option value="0">{{i18n.immediately}}</option>
<option value="1">{{i18n.oneMinute}}</option>
<option value="5">{{i18n.fiveMinutes}}</option>
<option value="15">{{i18n.fifteedMinutes}}</option>
<option value="30">{{i18n.thirtyMinutes}}</option>
<option value="60">{{i18n.oneHour}}</option>
<option value="240">{{i18n.fourHours}}</option>
<option value="-1">{{i18n.onRestart}}</option>
<option value="">{{i18n.never}}</option>
</select>
</div>
<a class="list-section-item" href="" ng-click="twoStep()">
{{i18n.twoStepLogin}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
</div>
<div class="list-section">
<div class="list-section-header">
{{i18n.account}}
@ -16,10 +41,6 @@
{{i18n.changeEmail}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="twoStep()">
{{i18n.twoStepLogin}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="logOut()">
{{i18n.logOut}}
</a>

View File

@ -41,7 +41,7 @@
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item">
<label for="folder" class="item-label">Folder</label>
<label for="folder" class="item-label">{{i18n.folder}}</label>
<select id="folder" name="FolderId" ng-model="site.folderId">
<option ng-repeat="folder in folders | orderBy: ['name']" value="{{folder.id}}">
{{folder.name}}

View File

@ -76,6 +76,9 @@
<script src="app/tools/toolsModule.js"></script>
<script src="app/tools/toolsController.js"></script>
<script src="app/tools/toolsPasswordGeneratorController.js"></script>
<script src="app/lock/lockModule.js"></script>
<script src="app/lock/lockController.js"></script>
</head>
<body ng-controller="mainController as main" class="{{main.animation}}">
<div ui-view class="main-view"></div>

View File

@ -0,0 +1,6 @@
function ConstantsService() {
return {
disableGaKey: 'disableGa',
lockOptionKey: 'lockOption'
};
};

View File

@ -1,10 +1,13 @@
function CryptoService() {
function CryptoService(constantsService) {
this.constantsService = constantsService;
initCryptoService();
};
function initCryptoService() {
var _key,
_b64Key,
_keyHash,
_b64KeyHash,
_aes;
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
@ -14,9 +17,33 @@ function initCryptoService() {
throw 'callback function required';
}
var self = this;
_key = key;
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
// if we have a lock option set, we do not store the key
callback();
return;
}
chrome.storage.local.set({
'key': sjcl.codec.base64.fromBits(key)
}, function () {
callback();
});
});
}
CryptoService.prototype.setKeyHash = function (keyHash, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
_keyHash = sjcl.codec.base64.toBits(keyHash);
chrome.storage.local.set({
'key': sjcl.codec.base64.fromBits(key)
'keyHash': keyHash
}, function () {
callback();
});
@ -28,23 +55,73 @@ function initCryptoService() {
}
if (b64 && b64 === true && _b64Key) {
return callback(_b64Key);
callback(_b64Key);
return;
}
else if (!b64 && _key) {
return callback(_key);
callback(_key);
return;
}
else if (b64 && b64 === true && _key && !_b64Key) {
_b64Key = sjcl.codec.base64.fromBits(_key);
callback(_b64Key);
return;
}
chrome.storage.local.get('key', function (obj) {
if (obj && obj.key) {
_key = sjcl.codec.base64.toBits(obj.key);
var self = this;
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
// if we have a lock option set, we do not try to fetch the storage key since it should not even be there
callback(null);
return;
}
if (b64 && b64 === true) {
_b64Key = sjcl.codec.base64.fromBits(_key);
return callback(_b64Key);
chrome.storage.local.get('key', function (obj) {
if (obj && obj.key) {
_key = sjcl.codec.base64.toBits(obj.key);
if (b64 && b64 === true) {
_b64Key = obj.key;
callback(_b64Key);
return;
}
}
callback(_key);
});
});
};
CryptoService.prototype.getKeyHash = function (b64, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
if (b64 && b64 === true && _b64KeyHash) {
callback(_b64KeyHash);
}
else if (!b64 && _keyHash) {
callback(_keyHash);
return;
}
else if (b64 && b64 === true && _keyHash && !_b64KeyHash) {
_b64KeyHash = sjcl.codec.base64.fromBits(_keyHash);
callback(_b64KeyHash);
return;
}
chrome.storage.local.get('keyHash', function (obj) {
if (obj && obj.keyHash) {
_keyHash = sjcl.codec.base64.toBits(obj.keyHash);
if (b64 && b64 === true) {
_b64KeyHash = obj.keyHash;
callback(_b64KeyHash);
return;
}
}
return callback(_key);
callback(_keyHash);
});
};
@ -59,6 +136,44 @@ function initCryptoService() {
});
};
CryptoService.prototype.clearKeyHash = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
_keyHash = _b64KeyHash = null;
chrome.storage.local.remove('keyHash', function () {
callback();
});
};
CryptoService.prototype.toggleKey = function (callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
var self = this;
self.getKey(false, function (key) {
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
// if we have a lock option set, clear the key
self.clearKey(function () {
_key = key;
callback();
return;
});
}
else {
// no lock option, so store the current key
self.setKey(key, function () {
callback();
return;
});
}
});
});
};
CryptoService.prototype.makeKey = function (password, salt, b64) {
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);

View File

@ -1,5 +1,6 @@
function i18nService() {
return {
appName: chrome.i18n.getMessage('appName'),
loginOrCreateNewAccount: chrome.i18n.getMessage('loginOrCreateNewAccount'),
createAccount: chrome.i18n.getMessage('createAccount'),
login: chrome.i18n.getMessage('login'),
@ -91,6 +92,20 @@ function i18nService() {
disableGa: chrome.i18n.getMessage('disableGa'),
rateExtension: chrome.i18n.getMessage('rateExtension'),
rateExtensionDesc: chrome.i18n.getMessage('rateExtensionDesc'),
browserNotSupportClipboard: chrome.i18n.getMessage('browserNotSupportClipboard')
browserNotSupportClipboard: chrome.i18n.getMessage('browserNotSupportClipboard'),
verifyMasterPassword: chrome.i18n.getMessage('verifyMasterPassword'),
invalidMasterPassword: chrome.i18n.getMessage('invalidMasterPassword'),
errorsHaveOccurred: chrome.i18n.getMessage('errorsHaveOccurred'),
lockOptions: chrome.i18n.getMessage('lockOptions'),
immediately: chrome.i18n.getMessage('immediately'),
oneMinute: chrome.i18n.getMessage('oneMinute'),
fiveMinutes: chrome.i18n.getMessage('fiveMinutes'),
fifteenMinutes: chrome.i18n.getMessage('fifteenMinutes'),
thirtyMinutes: chrome.i18n.getMessage('thirtyMinutes'),
oneHour: chrome.i18n.getMessage('oneHour'),
fourHours: chrome.i18n.getMessage('fourHours'),
onRestart: chrome.i18n.getMessage('onRestart'),
never: chrome.i18n.getMessage('never'),
security: chrome.i18n.getMessage('security')
};
};