Allow encrypted backups when using passcode

This commit is contained in:
Mo Bitar
2017-11-06 09:43:22 -06:00
parent 9d4dea4e95
commit 4f675086d3
4 changed files with 55 additions and 15 deletions

View File

@@ -2,7 +2,8 @@
class DesktopManager { class DesktopManager {
constructor($rootScope, modelManager, authManager) { constructor($rootScope, modelManager, authManager, passcodeManager) {
this.passcodeManager = passcodeManager;
this.modelManager = modelManager; this.modelManager = modelManager;
this.authManager = authManager; this.authManager = authManager;
this.$rootScope = $rootScope; this.$rootScope = $rootScope;
@@ -29,10 +30,21 @@ class DesktopManager {
} }
desktop_requestBackupFile() { desktop_requestBackupFile() {
var keys, authParams, protocolVersion;
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys();
authParams = this.passcodeManager.passcodeAuthParams();
protocolVersion = authParams.version;
} else {
keys = this.authManager.keys();
authParams = this.authManager.getAuthParams();
protocolVersion = this.authManager.protocolVersion();
}
let data = this.modelManager.getAllItemsJSONData( let data = this.modelManager.getAllItemsJSONData(
this.authManager.keys(), keys,
this.authManager.getAuthParams(), authParams,
this.authManager.protocolVersion(), protocolVersion,
true /* return null on empty */ true /* return null on empty */
); );
return data; return data;

View File

@@ -8,13 +8,17 @@ class AccountMenu {
}; };
} }
controller($scope, authManager, modelManager, syncManager, dbManager, passcodeManager, $timeout, storageManager) { controller($scope, $rootScope, authManager, modelManager, syncManager, dbManager, passcodeManager, $timeout, storageManager) {
'ngInject'; 'ngInject';
$scope.formData = {mergeLocal: true, url: syncManager.serverURL, ephemeral: false}; $scope.formData = {mergeLocal: true, url: syncManager.serverURL, ephemeral: false};
$scope.user = authManager.user; $scope.user = authManager.user;
$scope.server = syncManager.serverURL; $scope.server = syncManager.serverURL;
$scope.encryptedBackupsAvailable = function() {
return authManager.user || passcodeManager.hasPasscode();
}
$scope.syncStatus = syncManager.syncStatus; $scope.syncStatus = syncManager.syncStatus;
$scope.encryptionKey = function() { $scope.encryptionKey = function() {
@@ -153,6 +157,9 @@ class AccountMenu {
syncManager.markAllItemsDirtyAndSaveOffline(function(){ syncManager.markAllItemsDirtyAndSaveOffline(function(){
block(); block();
}, true) }, true)
// Allows desktop to make backup file
$rootScope.$broadcast("major-data-change");
} else { } else {
modelManager.resetLocalMemory(); modelManager.resetLocalMemory();
storageManager.clearAllModels(function(){ storageManager.clearAllModels(function(){
@@ -174,7 +181,7 @@ class AccountMenu {
/* Import/Export */ /* Import/Export */
$scope.archiveFormData = {encrypted: $scope.user ? true : false}; $scope.archiveFormData = {encrypted: $scope.encryptedBackupsAvailable() ? true : false};
$scope.user = authManager.user; $scope.user = authManager.user;
$scope.submitImportPassword = function() { $scope.submitImportPassword = function() {
@@ -361,8 +368,19 @@ class AccountMenu {
$scope.downloadDataArchive = function() { $scope.downloadDataArchive = function() {
// download in Standard File format // download in Standard File format
var keys = $scope.archiveFormData.encrypted ? authManager.keys() : null; var keys, authParams, protocolVersion;
var data = $scope.itemsData(keys); if($scope.archiveFormData.encrypted) {
if(authManager.offline() && passcodeManager.hasPasscode()) {
keys = passcodeManager.keys();
authParams = passcodeManager.passcodeAuthParams();
protocolVersion = authParams.version;
} else {
keys = authManager.keys();
authParams = authManager.getAuthParams();
protocolVersion = authManager.protocolVersion();
}
}
var data = $scope.itemsData(keys, authParams, protocolVersion);
downloadData(data, `SN Archive - ${new Date()}.txt`); downloadData(data, `SN Archive - ${new Date()}.txt`);
// download as zipped plain text files // download as zipped plain text files
@@ -372,8 +390,8 @@ class AccountMenu {
} }
} }
$scope.itemsData = function(keys) { $scope.itemsData = function(keys, authParams, protocolVersion) {
let data = modelManager.getAllItemsJSONData(keys, authManager.getAuthParams(), authManager.protocolVersion()); let data = modelManager.getAllItemsJSONData(keys, authParams, protocolVersion);
let blobData = new Blob([data], {type: 'text/json'}); let blobData = new Blob([data], {type: 'text/json'});
return blobData; return blobData;
} }
@@ -516,6 +534,8 @@ class AccountMenu {
if(offline) { if(offline) {
syncManager.markAllItemsDirtyAndSaveOffline(); syncManager.markAllItemsDirtyAndSaveOffline();
// Allows desktop to make backup file
$rootScope.$broadcast("major-data-change");
} }
}) })
}) })
@@ -529,8 +549,12 @@ class AccountMenu {
} }
if(confirm(message)) { if(confirm(message)) {
passcodeManager.clearPasscode(); passcodeManager.clearPasscode();
if(authManager.offline()) { if(authManager.offline()) {
syncManager.markAllItemsDirtyAndSaveOffline(); syncManager.markAllItemsDirtyAndSaveOffline();
// Don't create backup here, as if the user is temporarily removing the passcode to change it,
// we don't want to write unencrypted data to disk.
// $rootScope.$broadcast("major-data-change");
} }
} }
} }

View File

@@ -22,8 +22,12 @@ angular.module('app.frontend')
return this._keys; return this._keys;
} }
this.passcodeAuthParams = function() {
return JSON.parse(storageManager.getItem("offlineParams", StorageManager.Fixed));
}
this.unlock = function(passcode, callback) { this.unlock = function(passcode, callback) {
var params = JSON.parse(storageManager.getItem("offlineParams", StorageManager.Fixed)); var params = this.passcodeAuthParams();
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, params), function(keys){ Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, params), function(keys){
if(keys.pw !== params.hash) { if(keys.pw !== params.hash) {
callback(false); callback(false);
@@ -40,7 +44,7 @@ angular.module('app.frontend')
this.setPasscode = function(passcode, callback) { this.setPasscode = function(passcode, callback) {
var cost = Neeto.crypto.defaultPasswordGenerationCost(); var cost = Neeto.crypto.defaultPasswordGenerationCost();
var salt = Neeto.crypto.generateRandomKey(512); var salt = Neeto.crypto.generateRandomKey(512);
var defaultParams = {pw_cost: cost, pw_salt: salt}; var defaultParams = {pw_cost: cost, pw_salt: salt, version: "002"};
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, defaultParams), function(keys) { Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, defaultParams), function(keys) {
defaultParams.hash = keys.pw; defaultParams.hash = keys.pw;

View File

@@ -141,15 +141,15 @@
.mt-25{"ng-if" => "!importData.loading"} .mt-25{"ng-if" => "!importData.loading"}
%h4 Data Archives %h4 Data Archives
.mt-5{"ng-if" => "user"} .mt-5{"ng-if" => "encryptedBackupsAvailable()"}
%label.normal.inline{"ng-if" => "user"} %label.normal.inline
%input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"} %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"}
Encrypted Encrypted
%label.normal.inline %label.normal.inline
%input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"} %input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"}
Decrypted Decrypted
%a.block.mt-5{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} Export Data Archive %a.block.mt-5{"ng-click" => "downloadDataArchive()", "ng-class" => "{'mt-5' : !user}"} Download Data Archive
%label.block.mt-5 %label.block.mt-5
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"} %input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}