Autolock wip

This commit is contained in:
Mo Bitar
2018-11-13 15:05:57 -06:00
parent 9835992e16
commit 091f4cff7f
11 changed files with 258 additions and 119 deletions

View File

@@ -31,7 +31,8 @@ angular.module('app')
} }
} }
}) })
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager, storageManager, desktopManager) { .controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
storageManager, desktopManager, privilegesManager) {
this.panelController = {}; this.panelController = {};
@@ -198,12 +199,14 @@ angular.module('app')
} }
} }
this.selectNote = function(note, viaClick = false) { this.selectNote = async function(note, viaClick = false) {
if(!note) { if(!note) {
this.createNewNote(); this.createNewNote();
return; return;
} }
let run = () => {
$timeout(() => {
this.selectedNote = note; this.selectedNote = note;
note.conflict_of = null; // clear conflict note.conflict_of = null; // clear conflict
this.selectionMade()(note); this.selectionMade()(note);
@@ -212,6 +215,16 @@ angular.module('app')
if(viaClick && this.isFiltering()) { if(viaClick && this.isFiltering()) {
desktopManager.searchText(this.noteFilter.text); desktopManager.searchText(this.noteFilter.text);
} }
})
}
if(note.locked && await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionViewLockedNotes)) {
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionViewLockedNotes, () => {
run();
});
} else {
run();
}
} }
this.isFiltering = function() { this.isFiltering = function() {

View File

@@ -38,7 +38,6 @@ class AccountMenu {
} }
$scope.canAddPasscode = !authManager.isEphemeralSession(); $scope.canAddPasscode = !authManager.isEphemeralSession();
$scope.syncStatus = syncManager.syncStatus; $scope.syncStatus = syncManager.syncStatus;
$scope.submitMfaForm = function() { $scope.submitMfaForm = function() {
@@ -375,6 +374,24 @@ class AccountMenu {
Passcode Lock Passcode Lock
*/ */
$scope.passcodeAutoLockOptions = passcodeManager.getAutoLockIntervalOptions();
$scope.reloadAutoLockInterval = function() {
passcodeManager.getAutoLockInterval().then((interval) => {
$timeout(() => {
$scope.selectedAutoLockInterval = interval;
console.log("selectedAutoLockInterval", $scope.selectedAutoLockInterval);
})
})
}
$scope.reloadAutoLockInterval();
$scope.selectAutoLockInterval = async function(interval) {
await passcodeManager.setAutoLockInterval(interval);
$scope.reloadAutoLockInterval();
}
$scope.hasPasscode = function() { $scope.hasPasscode = function() {
return passcodeManager.hasPasscode(); return passcodeManager.hasPasscode();
} }

View File

@@ -5,7 +5,7 @@ class HttpManager extends SFHttpManager {
super($timeout); super($timeout);
this.setJWTRequestHandler(async () => { this.setJWTRequestHandler(async () => {
return storageManager.getItem("jwt");; return storageManager.getItem("jwt");
}) })
} }
} }

View File

@@ -1,33 +1,103 @@
angular.module('app') class PasscodeManager {
.provider('passcodeManager', function () {
this.$get = function($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) { constructor(authManager, storageManager) {
return new PasscodeManager($rootScope, $timeout, modelManager, dbManager, authManager, storageManager); document.addEventListener('visibilitychange', () => {
} this.documentVisibilityChanged(document.visibilityState);
});
function PasscodeManager($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) { this.authManager = authManager;
this.storageManager = storageManager;
this._hasPasscode = storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null; this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
this._locked = this._hasPasscode; this._locked = this._hasPasscode;
this.isLocked = function() { const MillisecondsPerSecond = 1000;
PasscodeManager.AutoLockIntervalNone = 0;
PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
}
getAutoLockIntervalOptions() {
return [
{
value: PasscodeManager.AutoLockIntervalNone,
label: "None"
},
{
value: PasscodeManager.AutoLockIntervalOneMinute,
label: "1 Min"
},
{
value: PasscodeManager.AutoLockIntervalFiveMinutes,
label: "5 Min"
},
{
value: PasscodeManager.AutoLockIntervalOneHour,
label: "1 Hr"
}
]
}
documentVisibilityChanged(visbility) {
let visible = document.visibilityState == "visible";
if(!visible) {
this.beginAutoLockTimer();
} else {
this.cancelAutoLockTimer();
}
}
async beginAutoLockTimer() {
console.log("beginAutoLockTimer");
var interval = await this.getAutoLockInterval();
this.lockTimeout = setTimeout(() => {
this.lockApplication();
}, interval);
}
cancelAutoLockTimer() {
console.log("cancelAutoLockTimer");
clearTimeout(this.lockTimeout);
}
lockApplication() {
console.log("lockApplication");
window.location.reload();
this.cancelAutoLockTimer();
}
isLocked() {
return this._locked; return this._locked;
} }
this.hasPasscode = function() { hasPasscode() {
return this._hasPasscode; return this._hasPasscode;
} }
this.keys = function() { keys() {
return this._keys; return this._keys;
} }
this.passcodeAuthParams = function() { async setAutoLockInterval(interval) {
var authParams = JSON.parse(storageManager.getItemSync("offlineParams", StorageManager.Fixed)); console.log("Set autolock interval", interval);
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.Fixed);
}
async getAutoLockInterval() {
let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.Fixed);
console.log("Got interval", interval);
return interval && JSON.parse(interval);
}
passcodeAuthParams() {
var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
if(authParams && !authParams.version) { if(authParams && !authParams.version) {
var keys = this.keys(); var keys = this.keys();
if(keys && keys.ak) { if(keys && keys.ak) {
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have thier version stored in authParams. // If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
authParams.version = "002"; authParams.version = "002";
} else { } else {
authParams.version = "001"; authParams.version = "001";
@@ -36,7 +106,7 @@ angular.module('app')
return authParams; return authParams;
} }
this.verifyPasscode = async function(passcode) { async verifyPasscode(passcode) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
var params = this.passcodeAuthParams(); var params = this.passcodeAuthParams();
let keys = await SFJS.crypto.computeEncryptionKeysForUser(passcode, params); let keys = await SFJS.crypto.computeEncryptionKeysForUser(passcode, params);
@@ -48,7 +118,7 @@ angular.module('app')
}) })
} }
this.unlock = function(passcode, callback) { unlock(passcode, callback) {
var params = this.passcodeAuthParams(); var params = this.passcodeAuthParams();
SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => { SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
if(keys.pw !== params.hash) { if(keys.pw !== params.hash) {
@@ -65,7 +135,7 @@ angular.module('app')
}); });
} }
this.setPasscode = (passcode, callback) => { setPasscode(passcode, callback) {
var uuid = SFJS.crypto.generateUUIDSync(); var uuid = SFJS.crypto.generateUUIDSync();
SFJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => { SFJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
@@ -81,32 +151,33 @@ angular.module('app')
this.encryptLocalStorage(keys, authParams); this.encryptLocalStorage(keys, authParams);
// After it's cleared, it's safe to write to it // After it's cleared, it's safe to write to it
storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed); this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
callback(true); callback(true);
}); });
} }
this.changePasscode = (newPasscode, callback) => { changePasscode(newPasscode, callback) {
this.setPasscode(newPasscode, callback); this.setPasscode(newPasscode, callback);
} }
this.clearPasscode = function() { clearPasscode() {
storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
storageManager.removeItem("offlineParams", StorageManager.Fixed); this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
this._keys = null; this._keys = null;
this._hasPasscode = false; this._hasPasscode = false;
} }
this.encryptLocalStorage = function(keys, authParams) { encryptLocalStorage(keys, authParams) {
storageManager.setKeys(keys, authParams); this.storageManager.setKeys(keys, authParams);
// Switch to Ephemeral storage, wiping Fixed storage // Switch to Ephemeral storage, wiping Fixed storage
// Last argument is `force`, which we set to true because in the case of changing passcode // Last argument is `force`, which we set to true because in the case of changing passcode
storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true); this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
} }
this.decryptLocalStorage = async function(keys, authParams) { async decryptLocalStorage(keys, authParams) {
storageManager.setKeys(keys, authParams); this.storageManager.setKeys(keys, authParams);
return storageManager.decryptStorage(); return this.storageManager.decryptStorage();
} }
} }
});
angular.module('app').service('passcodeManager', PasscodeManager);

View File

@@ -15,10 +15,14 @@ class PrivilegesManager {
PrivilegesManager.ActionManageExtensions = "ActionManageExtensions"; PrivilegesManager.ActionManageExtensions = "ActionManageExtensions";
PrivilegesManager.ActionDownloadBackup = "ActionDownloadBackup"; PrivilegesManager.ActionDownloadBackup = "ActionDownloadBackup";
PrivilegesManager.ActionViewLockedNotes = "ActionViewLockedNotes";
PrivilegesManager.ActionManagePrivileges = "ActionManagePrivileges";
this.availableActions = [ this.availableActions = [
PrivilegesManager.ActionManageExtensions, PrivilegesManager.ActionManageExtensions,
PrivilegesManager.ActionDownloadBackup PrivilegesManager.ActionDownloadBackup,
PrivilegesManager.ActionViewLockedNotes,
PrivilegesManager.ActionManagePrivileges
] ]
this.availableCredentials = [ this.availableCredentials = [
@@ -119,10 +123,19 @@ class PrivilegesManager {
metadata[PrivilegesManager.ActionManageExtensions] = { metadata[PrivilegesManager.ActionManageExtensions] = {
label: "Manage Extensions" label: "Manage Extensions"
} }
metadata[PrivilegesManager.ActionDownloadBackup] = { metadata[PrivilegesManager.ActionDownloadBackup] = {
label: "Download Backups" label: "Download Backups"
}; };
metadata[PrivilegesManager.ActionViewLockedNotes] = {
label: "View Locked Notes"
};
metadata[PrivilegesManager.ActionManagePrivileges] = {
label: "Manage Privileges"
}
return metadata[action]; return metadata[action];
} }

View File

@@ -1,4 +1,11 @@
$heading-height: 75px; $heading-height: 75px;
#editor-column {
.locked {
opacity: 0.8;
}
}
.editor { .editor {
flex: 1 50%; flex: 1 50%;
display: flex; display: flex;
@@ -7,10 +14,6 @@ $heading-height: 75px;
background-color: white; background-color: white;
} }
.locked {
opacity: 0.8;
}
#editor-title-bar { #editor-title-bar {
width: 100%; width: 100%;

View File

@@ -12,6 +12,10 @@
} }
} }
#privileges-modal {
width: 700px;
}
#password-wizard { #password-wizard {
font-size: 16px; font-size: 16px;
} }

View File

@@ -130,7 +130,12 @@
.horizontal-group .horizontal-group
%a.info{"ng-click" => "changePasscodePressed()"} Change Passcode %a.info{"ng-click" => "changePasscodePressed()"} Change Passcode
%a.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode %a.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode
.panel-row
.horizontal-group
%p Autolock:
%a.info{"ng-repeat" => "option in passcodeAutoLockOptions", "ng-click" => "selectAutoLockInterval(option.value)",
"ng-class" => "{'info bold' : option.value == selectedAutoLockInterval}"}
{{option.label}}
.panel-section{"ng-if" => "!importData.loading"} .panel-section{"ng-if" => "!importData.loading"}

View File

@@ -20,3 +20,14 @@
%p {{displayInfoForAction(action)}} %p {{displayInfoForAction(action)}}
%th{"ng-repeat" => "credential in availableCredentials"} %th{"ng-repeat" => "credential in availableCredentials"}
%input{"type" => "checkbox", "ng-checked" => "isCredentialRequiredForAction(action, credential)", "ng-click" => "checkboxValueChanged(action, credential)"} %input{"type" => "checkbox", "ng-checked" => "isCredentialRequiredForAction(action, credential)", "ng-click" => "checkboxValueChanged(action, credential)"}
.footer
%h2 About Privileges
%p
Privileges represent interface level authentication for accessing certain items and features.
Note that when your application is unlocked, your data exists in temporary memory in an unencrypted state.
Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
%p
Privileges sync across your other devices (not including mobile); however, note that if you require
a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode
requirement will be ignored on that device.

View File

@@ -39,6 +39,6 @@
.item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} .item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"}
.label Refresh .label Refresh
.item#lock-item{"ng-if" => "ctrl.hasPasscode()"} .item#lock-item{"ng-if" => "ctrl.hasPasscode()", "title" => "Locks application and wipes unencrypted data from memory."}
.label .label
%i.icon.ion-locked#footer-lock-icon{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"} %i.icon.ion-locked#footer-lock-icon{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"}

View File

@@ -59,9 +59,11 @@
.faded {{note.savedTagsString || note.tagsString()}} .faded {{note.savedTagsString || note.tagsString()}}
.name{"ng-if" => "note.title"} .name{"ng-if" => "note.title"}
%span.locked.tinted{"ng-if" => "note.locked", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"}
%i.icon.ion-locked.medium-text
{{note.title}} {{note.title}}
.note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview"} .note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.locked"}
.html-preview{"ng-if" => "note.content.preview_html", "ng-bind-html" => "note.content.preview_html"} .html-preview{"ng-if" => "note.content.preview_html", "ng-bind-html" => "note.content.preview_html"}
.plain-preview{"ng-if" => "!note.content.preview_html && note.content.preview_plain"} {{note.content.preview_plain}} .plain-preview{"ng-if" => "!note.content.preview_html && note.content.preview_plain"} {{note.content.preview_plain}}
.default-preview{"ng-if" => "!note.content.preview_html && !note.content.preview_plain"} {{note.text}} .default-preview{"ng-if" => "!note.content.preview_html && !note.content.preview_plain"} {{note.text}}