Wip
This commit is contained in:
@@ -296,7 +296,7 @@ class AccountMenu {
|
|||||||
}.bind(this)
|
}.bind(this)
|
||||||
|
|
||||||
if(data.auth_params) {
|
if(data.auth_params) {
|
||||||
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
|
SFJS.crypto.computeEncryptionKeysForUser(password, data.auth_params, (keys) => {
|
||||||
try {
|
try {
|
||||||
SFItemTransformer.decryptMultipleItems(data.items, keys, false); /* throws = false as we don't want to interrupt all decryption if just one fails */
|
SFItemTransformer.decryptMultipleItems(data.items, keys, false); /* throws = false as we don't want to interrupt all decryption if just one fails */
|
||||||
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
||||||
@@ -323,7 +323,7 @@ class AccountMenu {
|
|||||||
callback(null);
|
callback(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}.bind(this));
|
});
|
||||||
} else {
|
} else {
|
||||||
onDataReady();
|
onDataReady();
|
||||||
}
|
}
|
||||||
@@ -468,30 +468,6 @@ class AccountMenu {
|
|||||||
return keys && !keys.ak;
|
return keys && !keys.ak;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.clickedSecurityUpdate = function() {
|
|
||||||
if(!$scope.securityUpdateData) {
|
|
||||||
$scope.securityUpdateData = {};
|
|
||||||
}
|
|
||||||
$scope.securityUpdateData.showForm = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.submitSecurityUpdateForm = function() {
|
|
||||||
$scope.securityUpdateData.processing = true;
|
|
||||||
var authParams = authManager.getAuthParams();
|
|
||||||
|
|
||||||
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: $scope.securityUpdateData.password}, authParams), function(keys){
|
|
||||||
if(keys.mk !== authManager.keys().mk) {
|
|
||||||
alert("Invalid password. Please try again.");
|
|
||||||
$timeout(function(){
|
|
||||||
$scope.securityUpdateData.processing = false;
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
authManager.saveKeys(keys);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Encryption Status
|
Encryption Status
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class ItemParams {
|
|||||||
constructor(item, keys, version) {
|
constructor(item, keys, version) {
|
||||||
this.item = item;
|
this.item = item;
|
||||||
this.keys = keys;
|
this.keys = keys;
|
||||||
this.version = version || "002";
|
this.version = version || SFJS.crypto.version();
|
||||||
}
|
}
|
||||||
|
|
||||||
paramsForExportFile(includeDeleted) {
|
paramsForExportFile(includeDeleted) {
|
||||||
@@ -36,7 +36,7 @@ class ItemParams {
|
|||||||
|
|
||||||
var params = {uuid: this.item.uuid, content_type: this.item.content_type, deleted: this.item.deleted, created_at: this.item.created_at};
|
var params = {uuid: this.item.uuid, content_type: this.item.content_type, deleted: this.item.deleted, created_at: this.item.created_at};
|
||||||
if(!this.item.errorDecrypting) {
|
if(!this.item.errorDecrypting) {
|
||||||
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params;
|
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params.
|
||||||
var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
|
var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
|
||||||
if(this.keys && !doNotEncrypt) {
|
if(this.keys && !doNotEncrypt) {
|
||||||
var encryptedParams = SFItemTransformer.encryptItem(this.item, this.keys, this.version);
|
var encryptedParams = SFItemTransformer.encryptItem(this.item, this.keys, this.version);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
|
this.$get = function($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
|
||||||
return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager);
|
return new AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
|
function AuthManager($rootScope, $timeout, httpManager, modelManager, dbManager, storageManager, singletonManager) {
|
||||||
@@ -77,7 +77,7 @@ angular.module('app')
|
|||||||
|
|
||||||
var keys = this.keys();
|
var keys = this.keys();
|
||||||
if(keys && keys.ak) {
|
if(keys && keys.ak) {
|
||||||
return "002";
|
return "3";
|
||||||
} else {
|
} else {
|
||||||
return "001";
|
return "001";
|
||||||
}
|
}
|
||||||
@@ -86,14 +86,25 @@ angular.module('app')
|
|||||||
this.costMinimumForVersion = function(version) {
|
this.costMinimumForVersion = function(version) {
|
||||||
// all current versions have a min of 3000
|
// all current versions have a min of 3000
|
||||||
// future versions will increase this
|
// future versions will increase this
|
||||||
return 3000;
|
return SFJS.crypto.costMinimumForVersion(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isProtocolVersionSupported = function(version) {
|
this.isProtocolVersionSupported = function(version) {
|
||||||
var supportedVersions = ["001", "002"];
|
var supportedVersions = ["001", "002", "3"];
|
||||||
return supportedVersions.includes(version);
|
return supportedVersions.includes(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Upon sign in to an outdated version, the user will be presented with an alert requiring them to confirm
|
||||||
|
understanding they are signing in with an older version of the protocol, and must upgrade immediately after completing sign in.
|
||||||
|
*/
|
||||||
|
this.isProtocolVersionOutdated = function(version) {
|
||||||
|
return ["001"].includes(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.supportsPasswordDerivationCost = function(cost) {
|
||||||
|
return SFJS.crypto.supportsPasswordDerivationCost(cost);
|
||||||
|
}
|
||||||
|
|
||||||
this.getAuthParamsForEmail = function(url, email, extraParams, callback) {
|
this.getAuthParamsForEmail = function(url, email, extraParams, callback) {
|
||||||
var requestUrl = url + "/auth/params";
|
var requestUrl = url + "/auth/params";
|
||||||
httpManager.getAbsolute(requestUrl, _.merge({email: email}, extraParams), function(response){
|
httpManager.getAbsolute(requestUrl, _.merge({email: email}, extraParams), function(response){
|
||||||
@@ -107,17 +118,12 @@ angular.module('app')
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.supportsPasswordDerivationCost = function(cost) {
|
|
||||||
// some passwords are created on platforms with stronger pbkdf2 capabilities, like iOS,
|
|
||||||
// which accidentally used 60,000 iterations (now adjusted), which CryptoJS can't handle here (WebCrypto can however).
|
|
||||||
// if user has high password cost and is using browser that doesn't support WebCrypto,
|
|
||||||
// we want to tell them that they can't login with this browser.
|
|
||||||
return SFJS.crypto.supportsPasswordDerivationCost(cost);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.login = function(url, email, password, ephemeral, extraParams, callback) {
|
this.login = function(url, email, password, ephemeral, extraParams, callback) {
|
||||||
this.getAuthParamsForEmail(url, email, extraParams, function(authParams){
|
this.getAuthParamsForEmail(url, email, extraParams, function(authParams){
|
||||||
|
|
||||||
|
// SF3 requires a unique identifier in the auth params
|
||||||
|
authParams.identifier = email;
|
||||||
|
|
||||||
if(authParams.error) {
|
if(authParams.error) {
|
||||||
callback(authParams);
|
callback(authParams);
|
||||||
return;
|
return;
|
||||||
@@ -129,11 +135,18 @@ angular.module('app')
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!this.isProtocolVersionSupported(authParams.version)) {
|
if(!this.isProtocolVersionSupported(authParams.version)) {
|
||||||
let message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security-update for more information.";
|
let message = "The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.org/help/security for more information.";
|
||||||
callback({error: {message: message}});
|
callback({error: {message: message}});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(this.isProtocolVersionOutdated(authParams.version)) {
|
||||||
|
let message = `The encryption version for your account, ${authParams.version}, is outdated. You may proceed with login, but are advised to follow prompts for Security Updates once inside. Please visit standardnotes.org/help/security for more information.`
|
||||||
|
if(!confirm(message)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.supportsPasswordDerivationCost(authParams.pw_cost)) {
|
if(!this.supportsPasswordDerivationCost(authParams.pw_cost)) {
|
||||||
let message = "Your account was created on a platform with higher security capabilities than this browser supports. " +
|
let message = "Your account was created on a platform with higher security capabilities than this browser supports. " +
|
||||||
"If we attempted to generate your login keys here, it would take hours. " +
|
"If we attempted to generate your login keys here, it would take hours. " +
|
||||||
@@ -144,13 +157,13 @@ angular.module('app')
|
|||||||
|
|
||||||
var minimum = this.costMinimumForVersion(authParams.version);
|
var minimum = this.costMinimumForVersion(authParams.version);
|
||||||
if(authParams.pw_cost < minimum) {
|
if(authParams.pw_cost < minimum) {
|
||||||
let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/password-upgrade for more information.";
|
let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/security for more information.";
|
||||||
callback({error: {message: message}});
|
callback({error: {message: message}});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
|
SFJS.crypto.computeEncryptionKeysForUser(password, authParams, function(keys){
|
||||||
|
console.log("Signing in with params", authParams, keys);
|
||||||
var requestUrl = url + "/auth/sign_in";
|
var requestUrl = url + "/auth/sign_in";
|
||||||
var params = _.merge({password: keys.pw, email: email}, extraParams);
|
var params = _.merge({password: keys.pw, email: email}, extraParams);
|
||||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
httpManager.postAbsolute(requestUrl, params, function(response){
|
||||||
@@ -191,40 +204,40 @@ angular.module('app')
|
|||||||
|
|
||||||
this.saveKeys = function(keys) {
|
this.saveKeys = function(keys) {
|
||||||
this._keys = keys;
|
this._keys = keys;
|
||||||
// Doesn't need to be saved.
|
// pw doesn't need to be saved.
|
||||||
// storageManager.setItem("pw", keys.pw);
|
// storageManager.setItem("pw", keys.pw);
|
||||||
storageManager.setItem("mk", keys.mk);
|
storageManager.setItem("mk", keys.mk);
|
||||||
storageManager.setItem("ak", keys.ak);
|
storageManager.setItem("ak", keys.ak);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.register = function(url, email, password, ephemeral, callback) {
|
this.register = function(url, email, password, ephemeral, callback) {
|
||||||
SFJS.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
|
SFJS.crypto.generateInitialEncryptionKeysForUser(email, password, (keys, authParams) => {
|
||||||
var requestUrl = url + "/auth";
|
var requestUrl = url + "/auth";
|
||||||
var params = _.merge({password: keys.pw, email: email}, authParams);
|
var params = _.merge({password: keys.pw, email: email}, authParams);
|
||||||
|
|
||||||
httpManager.postAbsolute(requestUrl, params, function(response){
|
httpManager.postAbsolute(requestUrl, params, (response) => {
|
||||||
this.setEphemeral(ephemeral);
|
this.setEphemeral(ephemeral);
|
||||||
this.handleAuthResponse(response, email, url, authParams, keys);
|
this.handleAuthResponse(response, email, url, authParams, keys);
|
||||||
callback(response);
|
callback(response);
|
||||||
}.bind(this), function(response){
|
}, (response) => {
|
||||||
console.error("Registration error", response);
|
console.error("Registration error", response);
|
||||||
if(typeof response !== 'object') {
|
if(typeof response !== 'object') {
|
||||||
response = {error: {message: "A server error occurred while trying to register. Please try again."}};
|
response = {error: {message: "A server error occurred while trying to register. Please try again."}};
|
||||||
}
|
}
|
||||||
callback(response);
|
callback(response);
|
||||||
}.bind(this))
|
})
|
||||||
}.bind(this));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changePassword = function(email, new_password, callback) {
|
this.changePassword = function(email, current_password, new_password, callback) {
|
||||||
SFJS.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){
|
SFJS.crypto.generateInitialEncryptionKeysForUser(email, new_password, (keys, authParams) => {
|
||||||
var requestUrl = storageManager.getItem("server") + "/auth/change_pw";
|
var requestUrl = storageManager.getItem("server") + "/auth/change_pw";
|
||||||
var params = _.merge({new_password: keys.pw}, authParams);
|
var params = _.merge({current_password: current_password, new_password: keys.pw}, authParams);
|
||||||
|
|
||||||
httpManager.postAbsolute(requestUrl, params, function(response) {
|
httpManager.postAbsolute(requestUrl, params, (response) => {
|
||||||
this.handleAuthResponse(response, email, null, authParams, keys);
|
this.handleAuthResponse(response, email, null, authParams, keys);
|
||||||
callback(response);
|
callback(response);
|
||||||
}.bind(this), function(response){
|
}, (response) => {
|
||||||
var error = response;
|
var error = response;
|
||||||
if(!error) {
|
if(!error) {
|
||||||
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
|
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
|
||||||
@@ -232,7 +245,7 @@ angular.module('app')
|
|||||||
console.error("Change pw error", response);
|
console.error("Change pw error", response);
|
||||||
callback({error: error});
|
callback({error: error});
|
||||||
})
|
})
|
||||||
}.bind(this))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateAuthParams = function(authParams, callback) {
|
this.updateAuthParams = function(authParams, callback) {
|
||||||
@@ -258,20 +271,10 @@ angular.module('app')
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.protocolVersion() === "001") {
|
let latest = SFJS.crypto.version();
|
||||||
if(this.keys().ak) {
|
|
||||||
// upgrade to 002
|
if(this.protocolVersion() !== latest) {
|
||||||
var authParams = this.getAuthParams();
|
// Prompt user to perform security update
|
||||||
authParams.version = "002";
|
|
||||||
this.updateAuthParams(authParams, function(response){
|
|
||||||
if(!response.error) {
|
|
||||||
// let rest of UI load first
|
|
||||||
$timeout(function(){
|
|
||||||
alert("Your encryption version has been updated. To take full advantage of this update, please resync all your items by clicking Account -> Advanced -> Re-encrypt All Items.")
|
|
||||||
}, 750);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ class DesktopManager {
|
|||||||
return this.applicationDataPath;
|
return this.applicationDataPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sending a component in its raw state is really slow for the desktop app */
|
/*
|
||||||
|
Sending a component in its raw state is really slow for the desktop app
|
||||||
|
Keys are not passed into ItemParams, so the result is not encrypted
|
||||||
|
*/
|
||||||
convertComponentForTransmission(component) {
|
convertComponentForTransmission(component) {
|
||||||
return new ItemParams(component).paramsForExportFile(true);
|
return new ItemParams(component).paramsForExportFile(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ angular.module('app')
|
|||||||
|
|
||||||
this.unlock = function(passcode, callback) {
|
this.unlock = function(passcode, callback) {
|
||||||
var params = this.passcodeAuthParams();
|
var params = this.passcodeAuthParams();
|
||||||
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, params), function(keys){
|
SFJS.crypto.computeEncryptionKeysForUser(passcode, params, (keys) => {
|
||||||
if(keys.pw !== params.hash) {
|
if(keys.pw !== params.hash) {
|
||||||
callback(false);
|
callback(false);
|
||||||
return;
|
return;
|
||||||
@@ -38,26 +38,24 @@ angular.module('app')
|
|||||||
this.decryptLocalStorage(keys);
|
this.decryptLocalStorage(keys);
|
||||||
this._locked = false;
|
this._locked = false;
|
||||||
callback(true);
|
callback(true);
|
||||||
}.bind(this));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setPasscode = (passcode, callback) => {
|
this.setPasscode = (passcode, callback) => {
|
||||||
var cost = SFJS.crypto.defaultPasswordGenerationCost();
|
var uuid = SFJS.crypto.generateUUID();
|
||||||
var salt = SFJS.crypto.generateRandomKey(512);
|
|
||||||
var defaultParams = {pw_cost: cost, pw_salt: salt, version: "002"};
|
|
||||||
|
|
||||||
SFJS.crypto.computeEncryptionKeysForUser(_.merge({password: passcode}, defaultParams), function(keys) {
|
SFJS.crypto.generateInitialEncryptionKeysForUser(uuid, passcode, (keys, authParams) => {
|
||||||
defaultParams.hash = keys.pw;
|
authParams.hash = keys.pw;
|
||||||
this._keys = keys;
|
this._keys = keys;
|
||||||
this._hasPasscode = true;
|
this._hasPasscode = true;
|
||||||
|
|
||||||
// Encrypting will initially clear localStorage
|
// Encrypting will initially clear localStorage
|
||||||
this.encryptLocalStorage(keys);
|
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(defaultParams), StorageManager.Fixed);
|
storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
|
||||||
callback(true);
|
callback(true);
|
||||||
}.bind(this));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changePasscode = (newPasscode, callback) => {
|
this.changePasscode = (newPasscode, callback) => {
|
||||||
@@ -71,7 +69,7 @@ angular.module('app')
|
|||||||
this._hasPasscode = false;
|
this._hasPasscode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.encryptLocalStorage = function(keys) {
|
this.encryptLocalStorage = function(keys, authParams) {
|
||||||
storageManager.setKeys(keys);
|
storageManager.setKeys(keys);
|
||||||
// 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
|
||||||
|
|||||||
@@ -142,8 +142,9 @@ class StorageManager {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeys(keys) {
|
setKeys(keys, authParams) {
|
||||||
this.encryptedStorageKeys = keys;
|
this.encryptedStorageKeys = keys;
|
||||||
|
this.encryptedStorageAuthParams = authParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeEncryptedStorageToDisk() {
|
writeEncryptedStorageToDisk() {
|
||||||
@@ -152,7 +153,7 @@ class StorageManager {
|
|||||||
encryptedStorage.storage = this.storageAsHash();
|
encryptedStorage.storage = this.storageAsHash();
|
||||||
|
|
||||||
// Save new encrypted storage in Fixed storage
|
// Save new encrypted storage in Fixed storage
|
||||||
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys);
|
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
|
||||||
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
|
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user