From 7bbd18e75c90c35d92e16e1ae22e04d7dea0e0a8 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 10 Dec 2016 21:32:29 -0600 Subject: [PATCH 1/4] spec compliance wip --- app/assets/javascripts/app/app.services.js | 14 ++ .../app/frontend/controllers/editor.js | 34 +-- .../app/frontend/controllers/header.js | 93 +++----- .../app/frontend/controllers/home.js | 12 +- .../app/frontend/controllers/notes.js | 6 +- .../javascripts/app/frontend/models/note.js | 19 +- .../javascripts/app/services/apiController.js | 208 ++++++++++++------ .../app/services/directives/snippet.js | 2 +- .../templates/frontend/editor.html.haml | 4 +- app/assets/templates/frontend/notes.html.haml | 2 +- .../initializers/filter_parameter_logging.rb | 2 +- 11 files changed, 231 insertions(+), 165 deletions(-) diff --git a/app/assets/javascripts/app/app.services.js b/app/assets/javascripts/app/app.services.js index 4b8ef67a4..3bb263cb5 100644 --- a/app/assets/javascripts/app/app.services.js +++ b/app/assets/javascripts/app/app.services.js @@ -9,6 +9,20 @@ angular .config(function (RestangularProvider, apiControllerProvider) { var url = apiControllerProvider.defaultServerURL(); RestangularProvider.setBaseUrl(url); + + RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) { + var token = localStorage.getItem("jwt"); + if(token) { + headers = _.extend(headers, {Authorization: "Bearer " + localStorage.getItem("jwt")}); + } + + return { + element: element, + params: params, + headers: headers, + httpConfig: httpConfig + }; + }); }) // Shared function for configure auth service. Can be overwritten. diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index ce9da5f09..b96f7915a 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -66,25 +66,25 @@ angular.module('app.frontend') .controller('EditorCtrl', function ($sce, $timeout, apiController, markdownRenderer, $rootScope) { this.demoNotes = [ - {name: "Live print a file with tail", content: "tail -f log/production.log"}, - {name: "Create SSH tunnel", content: "ssh -i .ssh/key.pem -N -L 3306:example.com:3306 ec2-user@example.com"}, - {name: "List of processes running on port", content: "lsof -i:8080"}, - {name: "Set ENV from file", content: "export $(cat .envfile | xargs)"}, - {name: "Find process by name", content: "ps -ax | grep "}, - {name: "NPM install without sudo", content: "sudo chown -R $(whoami) ~/.npm"}, - {name: "Email validation regex", content: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"}, - {name: "Ruby generate 256 bit key", content: "Digest::SHA256.hexdigest(SecureRandom.random_bytes(32))"}, - {name: "Mac add user to user group", content: "sudo dseditgroup -o edit -a USERNAME -t user GROUPNAME"}, - {name: "Kill Mac OS System Apache", content: "sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist"}, - {name: "Docker run with mount binding and port", content: "docker run -v /home/vagrant/www/app:/var/www/app -p 8080:80 -d kpi/s3"}, - {name: "MySQL grant privileges", content: "GRANT [type of permission] ON [database name].[table name] TO ‘[username]’@'%’;"}, - {name: "MySQL list users", content: "SELECT User FROM mysql.user;"}, + {title: "Live print a file with tail", content: "tail -f log/production.log"}, + {title: "Create SSH tunnel", content: "ssh -i .ssh/key.pem -N -L 3306:example.com:3306 ec2-user@example.com"}, + {title: "List of processes running on port", content: "lsof -i:8080"}, + {title: "Set ENV from file", content: "export $(cat .envfile | xargs)"}, + {title: "Find process by name", content: "ps -ax | grep "}, + {title: "NPM install without sudo", content: "sudo chown -R $(whoami) ~/.npm"}, + {title: "Email validation regex", content: "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"}, + {title: "Ruby generate 256 bit key", content: "Digest::SHA256.hexdigest(SecureRandom.random_bytes(32))"}, + {title: "Mac add user to user group", content: "sudo dseditgroup -o edit -a USERNAME -t user GROUPNAME"}, + {title: "Kill Mac OS System Apache", content: "sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist"}, + {title: "Docker run with mount binding and port", content: "docker run -v /home/vagrant/www/app:/var/www/app -p 8080:80 -d kpi/s3"}, + {title: "MySQL grant privileges", content: "GRANT [type of permission] ON [database name].[table name] TO ‘[username]’@'%’;"}, + {title: "MySQL list users", content: "SELECT User FROM mysql.user;"}, ]; this.showSampler = !this.user.id && this.user.filteredNotes().length == 0; this.demoNoteNames = _.map(this.demoNotes, function(note){ - return note.name; + return note.title; }); this.currentDemoContent = {text: null}; @@ -94,7 +94,7 @@ angular.module('app.frontend') }.bind(this) this.callback = function(index) { - this.currentDemoContent.text = this.demoNotes[index].content; + this.currentDemoContent.text = this.demoNotes[index].text; }.bind(this) this.contentCallback = function(index) { @@ -102,7 +102,7 @@ angular.module('app.frontend') this.setNote = function(note, oldNote) { this.editorMode = 'edit'; - if(note.content.length == 0) { + if(note.text.length == 0) { this.focusTitle(100); } @@ -138,7 +138,7 @@ angular.module('app.frontend') } this.renderedContent = function() { - return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content)); + return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.text)); } var statusTimeout; diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index 55e46e5ff..4e5bc6271 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -13,9 +13,9 @@ angular.module('app.frontend') bindToController: true, link:function(scope, elem, attrs, ctrl) { - scope.$on('auth:login-success', function(event, user) { - ctrl.onAuthSuccess(user); - }); + // scope.$on('auth:login-success', function(event, user) { + // ctrl.onAuthSuccess(user); + // }); scope.$on('auth:validation-success', function(ev) { setTimeout(function(){ @@ -45,9 +45,8 @@ angular.module('app.frontend') this.signOutPressed = function() { this.showAccountMenu = false; - $auth.signOut(); this.logout()(); - apiController.clearGk(); + apiController.signout(); window.location.reload(); } @@ -55,48 +54,15 @@ angular.module('app.frontend') this.passwordChangeData.status = "Generating New Keys..."; $timeout(function(){ - var current_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.current_password, this.user.email); - var new_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.new_password, this.user.email); - // var new_pw_conf_keys = Neeto.crypto.generateEncryptionKeysForUser(this.passwordChangeData.new_password_confirmation, this.user.email); - - var data = {}; - data.current_password = current_keys.pw; - data.password = new_keys.pw; - data.password_confirmation = new_keys.pw; - - var user = this.user; - - if(data.password == data.password_confirmation) { - $auth.updatePassword(data) - .then(function(response) { - this.showNewPasswordForm = false; - if(user.local_encryption_enabled) { - // reencrypt data with new gk - apiController.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){ - if(success) { - apiController.setGk(new_keys.gk); - alert("Your password has been changed and your data re-encrypted."); - } else { - // rollback password - $auth.updatePassword({current_password: new_keys.pw, password: current_keys.pw, password_confirmation: current_keys.pw }) - .then(function(response){ - alert("There was an error changing your password. Your password has been rolled back."); - window.location.reload(); - }) - } - }); - } else { - alert("Your password has been changed."); - } - }.bind(this)) - .catch(function(response){ - this.showNewPasswordForm = false; - alert("There was an error changing your password. Please try again."); - }.bind(this)) - - } else { + if(data.password != data.password_confirmation) { alert("Your new password does not match its confirmation."); + return; } + + apiController.changePassword(this.user, this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){ + + }) + }.bind(this)) } @@ -116,17 +82,14 @@ angular.module('app.frontend') this.loginSubmitPressed = function() { this.loginData.status = "Generating Login Keys..."; $timeout(function(){ - var keys = Neeto.crypto.generateEncryptionKeysForUser(this.loginData.user_password, this.loginData.email); - var data = {password: keys.pw, email: this.loginData.email}; - - apiController.setGk(keys.gk); - $auth.submitLogin(data) - .then(function(response){ - - }) - .catch(function(response){ - this.loginData.status = response.errors[0]; - }.bind(this)) + apiController.login(this.loginData.email, this.loginData.user_password, function(response){ + console.log("login response", response); + if(response.errors) { + this.loginData.status = response.errors[0]; + } else { + this.onAuthSuccess(response.user); + } + }.bind(this)); }.bind(this)) } @@ -134,18 +97,12 @@ angular.module('app.frontend') this.loginData.status = "Generating Account Keys..."; $timeout(function(){ - var keys = Neeto.crypto.generateEncryptionKeysForUser(this.loginData.user_password, this.loginData.email); - var data = {password: keys.pw, email: this.loginData.email}; - - apiController.setGk(keys.gk); - - $auth.submitRegistration(data) - .then(function(response) { - $auth.user.id = response.data.data.id; - this.onAuthSuccess($auth.user); - }.bind(this)) - .catch(function(response) { - this.loginData.status = response.data.errors.full_messages[0]; + apiController.register(this.loginData.email, this.loginData.user_password, function(response){ + if(response.errors) { + this.loginData.status = response.errors[0]; + } else { + this.onAuthSuccess(response.user); + } }.bind(this)); }.bind(this)) } diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 0ef95faa2..938f39db5 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -23,16 +23,16 @@ angular.module('app.frontend') $scope.groups = groups; } - $auth.validateUser() - .then(function () { - $scope.defaultUser = new User($auth.user); + apiController.getCurrentUser(function(response){ + console.log("get current response", response); + if(response && !response.errors) { + $scope.defaultUser = new User(response.plain()); $rootScope.title = "Notes — Neeto"; onUserSet(); - - }) - .catch(function () { + } else { $scope.defaultUser = new User(apiController.localUser()); onUserSet(); + } }); /* diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js index b695e61eb..354f4c024 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/frontend/controllers/notes.js @@ -145,8 +145,8 @@ angular.module('app.frontend') } this.createNewNote = function() { - var name = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); - this.newNote = new Note({name: name, content: '', dummy: true}); + var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); + this.newNote = new Note({title: title, content: '', dummy: true}); this.newNote.shared_via_group = this.group.presentation && this.group.presentation.enabled; this.selectNote(this.newNote); this.addNew()(this.newNote); @@ -158,7 +158,7 @@ angular.module('app.frontend') if(this.noteFilter.text.length == 0) { note.visible = true; } else { - note.visible = note.name.toLowerCase().includes(this.noteFilter.text) || note.content.toLowerCase().includes(this.noteFilter.text); + note.visible = note.title.toLowerCase().includes(this.noteFilter.text) || note.text.toLowerCase().includes(this.noteFilter.text); } return note.visible; }.bind(this) diff --git a/app/assets/javascripts/app/frontend/models/note.js b/app/assets/javascripts/app/frontend/models/note.js index 8df4bf8b3..2b9253ea7 100644 --- a/app/assets/javascripts/app/frontend/models/note.js +++ b/app/assets/javascripts/app/frontend/models/note.js @@ -2,13 +2,30 @@ var Note = function (json_obj) { _.merge(this, json_obj); }; +Note.prototype = { + set content(content) { + try { + var data = JSON.parse(content); + this.title = data.title || data.name; + this.text = data.text || data.content; + } + catch(e) { + this.text = content; + } + } +} + +Note.prototype.JSONContent = function() { + return JSON.stringify({title: this.title, text: this.text}); +}; + /* Returns true if note is shared individually or via group */ Note.prototype.isPublic = function() { return this.hasEnabledPresentation() || this.shared_via_group; }; Note.prototype.isEncrypted = function() { - return this.local_eek ? true : false; + return this.loc_eek || this.local_eek ? true : false; } Note.prototype.hasEnabledPresentation = function() { diff --git a/app/assets/javascripts/app/services/apiController.js b/app/assets/javascripts/app/services/apiController.js index d31bd2a65..571e56c8f 100644 --- a/app/assets/javascripts/app/services/apiController.js +++ b/app/assets/javascripts/app/services/apiController.js @@ -48,6 +48,90 @@ angular.module('app.services') } } + + /* + Auth + */ + + this.getCurrentUser = function(callback) { + if(!localStorage.getItem("jwt")) { + callback(null); + return; + } + Restangular.one("users/current").get().then(function(response){ + callback(response); + }) + } + + this.login = function(email, password, callback) { + var keys = Neeto.crypto.generateEncryptionKeysForUser(password, email); + this.setGk(keys.gk); + var request = Restangular.one("auth/sign_in.json"); + request.user = {password: keys.pw, email: email}; + request.post().then(function(response){ + localStorage.setItem("jwt", response.token); + callback(response); + }) + } + + this.register = function(email, password, callback) { + var keys = Neeto.crypto.generateEncryptionKeysForUser(password, email); + this.setGk(keys.gk); + var request = Restangular.one("auth.json"); + request.user = {password: keys.pw, email: email}; + request.post().then(function(response){ + localStorage.setItem("jwt", response.token); + callback(response); + }) + } + + this.changePassword = function(user, current_password, new_password) { + var current_keys = Neeto.crypto.generateEncryptionKeysForUser(current_password, user.email); + var new_keys = Neeto.crypto.generateEncryptionKeysForUser(new_password, user.email); + + var data = {}; + data.current_password = current_keys.pw; + data.password = new_keys.pw; + data.password_confirmation = new_keys.pw; + + var user = this.user; + + this._performPasswordChange(current_keys, new_keys, function(response){ + if(response && !response.errors) { + // this.showNewPasswordForm = false; + if(user.local_encryption_enabled) { + // reencrypt data with new gk + this.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){ + if(success) { + this.setGk(new_keys.gk); + alert("Your password has been changed and your data re-encrypted."); + } else { + // rollback password + this._performPasswordChange(new_keys, current_keys, function(response){ + alert("There was an error changing your password. Your password has been rolled back."); + window.location.reload(); + }) + } + }.bind(this)); + } else { + alert("Your password has been changed."); + } + } else { + // this.showNewPasswordForm = false; + alert("There was an error changing your password. Please try again."); + } + }) + } + + this._performPasswordChange = function(email, current_keys, new_keys, callback) { + var request = Restangular.one("auth"); + request.user = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email}; + request.patch().then(function(response){ + callback(response); + }) + } + + /* User */ @@ -171,14 +255,14 @@ angular.module('app.services') this.shareGroup = function(user, group, callback) { var shareFn = function() { - Restangular.one("users", user.id).one("groups", group.id).one("share").post() + Restangular.one("users", user.id).one("groups", group.id).one("presentations").post() .then(function(response){ - var obj = response.plain(); + var presentation = response.plain(); group.notes.forEach(function(note){ note.shared_via_group = true; }); - _.merge(group, {presentation: obj.presentation}); - callback(obj); + _.merge(group, {presentation: presentation}); + callback(presentation); }) } @@ -195,11 +279,12 @@ angular.module('app.services') } this.unshareGroup = function(user, group, callback) { - Restangular.one("users", user.id).one("groups", group.id).one("unshare").post() - .then(function(response){ - var obj = response.plain(); - _.merge(group, {presentation: obj.presentation}); - callback(obj); + var request = Restangular.one("users", user.id).one("groups", group.id).one("presentations", group.presentation.id); + request.enabled = false; + request.patch().then(function(response){ + var presentation = response.plain(); + _.merge(group, {presentation: presentation}); + callback(presentation); }) } @@ -212,40 +297,16 @@ angular.module('app.services') */ this.saveBatchNotes = function(user, notes, encryptionEnabled, callback) { - notes = _.cloneDeep(notes); - notes.forEach(function(note){ - if(encryptionEnabled && !note.isPublic()) { - note.content = null; - note.name = null; - } else { - note.local_encrypted_content = null; - note.local_eek = null; - } - }) - var request = Restangular.one("users", user.id).one("notes/batch_update"); - request.notes = notes; + request.notes = _.map(notes, function(note){ + return this.createRequestParamsFromNote(note, user); + }.bind(this)); request.put().then(function(response){ var success = response.plain().success; callback(success); }) } - this.preprocessNoteForSaving = function(note, user) { - if(user.local_encryption_enabled && !note.pending_share && !note.isPublic()) { - // encrypt - this.encryptSingleNote(note, this.retrieveGk()); - note.content = null; // dont send unencrypted content to server - note.name = null; - } - else { - // decrypt - note.local_encrypted_content = null; - note.local_eek = null; - note.local_encryption_scheme = null; - } - } - this.saveNote = function(user, note, callback) { if(!user.id) { this.writeUserToLocalStorage(user); @@ -253,18 +314,15 @@ angular.module('app.services') return; } - var snipCopy = _.cloneDeep(note); - - this.preprocessNoteForSaving(snipCopy, user); + var params = this.createRequestParamsFromNote(note, user); var request = Restangular.one("users", user.id).one("notes", note.id); - _.merge(request, snipCopy); - + _.merge(request, params); + console.log("saving note", request); request.customOperation(request.id ? "put" : "post") .then(function(response) { var responseObject = response.plain(); responseObject.content = note.content; - responseObject.name = note.name; _.merge(note, responseObject); callback(note); }) @@ -273,6 +331,26 @@ angular.module('app.services') }) } + this.createRequestParamsFromNote = function(note, user) { + var params = {}; + + if(user.local_encryption_enabled && !note.pending_share && !note.isPublic()) { + // encrypted + var noteCopy = _.cloneDeep(note); + this.encryptSingleNote(noteCopy, this.retrieveGk()); + + params.loc_enc_content = noteCopy.loc_enc_content; + params.loc_eek = noteCopy.loc_eek; + } + else { + // decrypted + params.content = note.JSONContent(); + } + + return params; + } + + this.deleteNote = function(user, note, callback) { if(!user.id) { this.writeUserToLocalStorage(user); @@ -289,10 +367,10 @@ angular.module('app.services') if(!user.id) { if(confirm("Note: You are not signed in. Any note you share cannot be edited or unshared.")) { var request = Restangular.one("notes").one("share"); - _.merge(request, {name: note.name, content: note.content}); + _.merge(request, {name: note.title, content: note.content}); request.post().then(function(response){ - var obj = response.plain(); - _.merge(note, {presentation: obj.presentation}); + var presentation = response.plain(); + _.merge(note, {presentation: presentation}); note.locked = true; this.writeUserToLocalStorage(user); callback(note); @@ -300,10 +378,10 @@ angular.module('app.services') } } else { var shareFn = function(note, callback) { - Restangular.one("users", user.id).one("notes", note.id).one("share").post() + Restangular.one("users", user.id).one("notes", note.id).one("presentations").post() .then(function(response){ - var obj = response.plain(); - _.merge(note, {presentation: obj.presentation}); + var presentation = response.plain(); + _.merge(note, {presentation: presentation}); callback(note); }) } @@ -322,10 +400,11 @@ angular.module('app.services') } this.unshareNote = function(user, note, callback) { - Restangular.one("users", user.id).one("notes", note.id).one("unshare").post() - .then(function(response){ - var obj = response.plain(); - _.merge(note, {presentation: obj.presentation}); + var request = Restangular.one("users", user.id).one("notes", note.id).one("presentations", note.presentation.id); + request.enabled = false; + request.patch().then(function(response){ + var presentation = response.plain(); + _.merge(note, {presentation: presentation}); callback(note); }) } @@ -351,8 +430,8 @@ angular.module('app.services') var notes = _.map(user.filteredNotes(), function(note){ return { id: note.id, - name: note.name, - content: note.content, + title: note.title, + text: note.text, created_at: note.created_at, modified_at: note.modified_at, group_id: note.group_id @@ -455,20 +534,20 @@ angular.module('app.services') localStorage.setItem('gk', gk); } - this.clearGk = function() { + this.signout = function() { + localStorage.removeItem("jwt"); localStorage.removeItem("gk"); } this.encryptSingleNote = function(note, key) { var ek = null; if(note.isEncrypted()) { - ek = Neeto.crypto.decryptText(note.local_eek, key); + ek = Neeto.crypto.decryptText(note.loc_eek, key); } else { ek = Neeto.crypto.generateRandomEncryptionKey(); - note.local_eek = Neeto.crypto.encryptText(ek, key); + note.loc_eek = Neeto.crypto.encryptText(ek, key); } - var text = JSON.stringify({name: note.name, content: note.content}); - note.local_encrypted_content = Neeto.crypto.encryptText(text, ek); + note.loc_enc_content = Neeto.crypto.encryptText(note.JSONContent(), ek); note.local_encryption_scheme = "1.0"; } @@ -491,10 +570,9 @@ angular.module('app.services') } this.decryptSingleNote = function(note, key) { - var ek = Neeto.crypto.decryptText(note.local_eek, key); - var obj = JSON.parse(Neeto.crypto.decryptText(note.local_encrypted_content, ek)); - note.name = obj.name; - note.content = obj.content; + var ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key); + var content = Neeto.crypto.decryptText(note.loc_enc_content || note.local_encrypted_content, ek); + note.content = content; } this.decryptNotes = function(notes, key) { @@ -514,9 +592,9 @@ angular.module('app.services') notes.forEach(function(note){ if(note.isEncrypted()) { // first decrypt eek with old key - var ek = Neeto.crypto.decryptText(note.local_eek, oldKey); + var ek = Neeto.crypto.decryptText(note.loc_eek, oldKey); // now encrypt ek with new key - note.local_eek = Neeto.crypto.encryptText(ek, newKey); + note.loc_eek = Neeto.crypto.encryptText(ek, newKey); } }); diff --git a/app/assets/javascripts/app/services/directives/snippet.js b/app/assets/javascripts/app/services/directives/snippet.js index fa78e22c8..22939125b 100644 --- a/app/assets/javascripts/app/services/directives/snippet.js +++ b/app/assets/javascripts/app/services/directives/snippet.js @@ -12,6 +12,6 @@ angular }) .controller('SingleNoteCtrl', function ($rootScope, $scope, $state, markdownRenderer) { $scope.renderedContent = function() { - return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText($scope.note.content)); + return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText($scope.note.text)); } }); diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index 58679af63..bbfd6554e 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -2,7 +2,7 @@ .content .section-title-bar.editor-heading{"ng-class" => "{'shared' : ctrl.note.isPublic() }"} .title - %input.input#note-title-editor{"ng-model" => "ctrl.note.name", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", + %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", "ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "select-on-click" => "true"} .save-status {{ctrl.noteStatus}} @@ -56,6 +56,6 @@ "iteration-callback" => "ctrl.callback", "prebegin-fn" => "ctrl.prebeginFn", "iteration-delay" => "2000", "cursor" => ""} %code{"ng-if" => "ctrl.currentDemoContent.text"} .content-sampler.sampler{"typewrite" => "true", "text" => "ctrl.currentDemoContent.text", "type-delay" => "10", "iteration-callback" => "ctrl.contentCallback"} - %textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.content", + %textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"} .preview{"ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 11c280562..998ef4c30 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -34,5 +34,5 @@ "ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}", "ng-attr-draggable" => "{{note.dummy ? undefined : 'true'}}", "note" => "note"} .name - {{note.name}} + {{note.title}} .date {{note.created_at || 'Now'}} diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index 2e56791c8..a5f79566b 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,4 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password, :content, :name, :local_encrypted_content, :local_eek] +Rails.application.config.filter_parameters += [:password, :content, :name, :local_encrypted_content, :loc_eek] From 358fd8998901b18649cc89a3b6112afed776466f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 10 Dec 2016 22:08:53 -0600 Subject: [PATCH 2/4] migrations --- .../javascripts/app/frontend/controllers/header.js | 1 - .../javascripts/app/frontend/controllers/home.js | 1 - app/assets/javascripts/app/services/apiController.js | 10 ++++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index 4e5bc6271..dcc5f0a47 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -83,7 +83,6 @@ angular.module('app.frontend') this.loginData.status = "Generating Login Keys..."; $timeout(function(){ apiController.login(this.loginData.email, this.loginData.user_password, function(response){ - console.log("login response", response); if(response.errors) { this.loginData.status = response.errors[0]; } else { diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 938f39db5..6ca66a70e 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -24,7 +24,6 @@ angular.module('app.frontend') } apiController.getCurrentUser(function(response){ - console.log("get current response", response); if(response && !response.errors) { $scope.defaultUser = new User(response.plain()); $rootScope.title = "Notes — Neeto"; diff --git a/app/assets/javascripts/app/services/apiController.js b/app/assets/javascripts/app/services/apiController.js index 571e56c8f..9392db448 100644 --- a/app/assets/javascripts/app/services/apiController.js +++ b/app/assets/javascripts/app/services/apiController.js @@ -318,7 +318,6 @@ angular.module('app.services') var request = Restangular.one("users", user.id).one("notes", note.id); _.merge(request, params); - console.log("saving note", request); request.customOperation(request.id ? "put" : "post") .then(function(response) { var responseObject = response.plain(); @@ -332,21 +331,20 @@ angular.module('app.services') } this.createRequestParamsFromNote = function(note, user) { - var params = {}; + var params = {id: note.id}; if(user.local_encryption_enabled && !note.pending_share && !note.isPublic()) { // encrypted var noteCopy = _.cloneDeep(note); this.encryptSingleNote(noteCopy, this.retrieveGk()); - params.loc_enc_content = noteCopy.loc_enc_content; - params.loc_eek = noteCopy.loc_eek; + params.loc_enc_content = noteCopy.loc_enc_content || local_encrypted_content; + params.loc_eek = noteCopy.loc_eek || noteCopy.local_eek; } else { // decrypted params.content = note.JSONContent(); } - return params; } @@ -542,7 +540,7 @@ angular.module('app.services') this.encryptSingleNote = function(note, key) { var ek = null; if(note.isEncrypted()) { - ek = Neeto.crypto.decryptText(note.loc_eek, key); + ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key); } else { ek = Neeto.crypto.generateRandomEncryptionKey(); note.loc_eek = Neeto.crypto.encryptText(ek, key); From 8ad377681928d9dfef36ae4278da380c48e7852c Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sun, 11 Dec 2016 23:43:06 -0600 Subject: [PATCH 3/4] import/export --- .../app/frontend/controllers/_base.js | 8 + .../app/frontend/controllers/editor.js | 4 +- .../app/frontend/controllers/header.js | 45 ++-- .../app/frontend/controllers/home.js | 2 - .../app/frontend/controllers/notes.js | 17 +- .../javascripts/app/frontend/models/note.js | 43 ++-- .../javascripts/app/frontend/models/user.js | 7 - .../javascripts/app/services/apiController.js | 219 +++++++++++------- .../app/services/directives/file-change.js | 17 ++ .../templates/frontend/editor.html.haml | 4 +- .../templates/frontend/header.html.haml | 29 +-- app/assets/templates/frontend/notes.html.haml | 2 +- .../initializers/filter_parameter_logging.rb | 2 +- 13 files changed, 219 insertions(+), 180 deletions(-) create mode 100644 app/assets/javascripts/app/services/directives/file-change.js diff --git a/app/assets/javascripts/app/frontend/controllers/_base.js b/app/assets/javascripts/app/frontend/controllers/_base.js index 6754876ad..3c31a4f28 100644 --- a/app/assets/javascripts/app/frontend/controllers/_base.js +++ b/app/assets/javascripts/app/frontend/controllers/_base.js @@ -17,4 +17,12 @@ angular.module('app.frontend') apiController.setGk(new_keys.gk); } + // var note = new Note(); + // note.content = {title: "hello", text: "world"}; + // console.log("note content", note.content); + // console.log("note title", note.title); + // console.log("note json", JSON.stringify(note)); + // + // console.log("Copy", _.cloneDeep(note)); + }); diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index b96f7915a..e42bcf9a3 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -102,7 +102,7 @@ angular.module('app.frontend') this.setNote = function(note, oldNote) { this.editorMode = 'edit'; - if(note.text.length == 0) { + if(note.content.text.length == 0) { this.focusTitle(100); } @@ -138,7 +138,7 @@ angular.module('app.frontend') } this.renderedContent = function() { - return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.text)); + return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.content.text)); } var statusTimeout; diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index dcc5f0a47..f39de05c7 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -18,6 +18,7 @@ angular.module('app.frontend') // }); scope.$on('auth:validation-success', function(ev) { + // TODO setTimeout(function(){ ctrl.onValidationSuccess(); }) @@ -64,7 +65,6 @@ angular.module('app.frontend') }) }.bind(this)) - } this.hasLocalData = function() { @@ -119,11 +119,9 @@ angular.module('app.frontend') } this.onValidationSuccess = function() { - if(this.user.local_encryption_enabled) { apiController.verifyEncryptionStatusOfAllNotes(this.user, function(success){ }); - } } this.encryptionStatusForNotes = function() { @@ -138,31 +136,6 @@ angular.module('app.frontend') return countEncrypted + "/" + allNotes.length + " notes encrypted"; } - this.toggleEncryptionStatus = function() { - this.encryptionConfirmation = true; - } - - this.cancelEncryptionChange = function() { - this.encryptionConfirmation = false; - } - - this.confirmEncryptionChange = function() { - - var callback = function(success, enabled) { - if(success) { - this.encryptionConfirmation = false; - this.user.local_encryption_enabled = enabled; - } - }.bind(this) - - if(this.user.local_encryption_enabled) { - apiController.disableEncryptionForUser(this.user, callback); - } else { - apiController.enableEncryptionForUser(this.user, callback); - } - } - - this.downloadDataArchive = function() { var link = document.createElement('a'); link.setAttribute('download', 'neeto.json'); @@ -170,6 +143,22 @@ angular.module('app.frontend') link.click(); } + this.importFileSelected = function(files) { + var file = files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + apiController.importJSONData(e.target.result, function(success, response){ + console.log("import response", success, response); + if(success) { + // window.location.reload(); + } else { + alert("There was an error importing your data. Please try again."); + } + }) + } + reader.readAsText(file); + } + this.onAuthSuccess = function(user) { this.user.id = user.id; diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 6ca66a70e..3455790d9 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -164,8 +164,6 @@ angular.module('app.frontend') $scope.selectedNote = null; } - note.onDelete(); - if(note.dummy) { return; } diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js index 354f4c024..a81d1f05e 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/frontend/controllers/notes.js @@ -76,18 +76,12 @@ angular.module('app.frontend') return; } - if(this.user.local_encryption_enabled) { - if(!confirm("Sharing this group will disable local encryption on all group notes.")) { - return; - } - } - var callback = function(username) { apiController.shareGroup(this.user, this.group, function(response){ }) }.bind(this); - if(!this.user.getUsername()) { + if(!this.user.username) { ngDialog.open({ template: 'frontend/modals/username.html', controller: 'UsernameModalCtrl', @@ -99,7 +93,7 @@ angular.module('app.frontend') disableAnimation: true }); } else { - callback(this.user.getUsername()); + callback(this.user.username); } } @@ -138,15 +132,12 @@ angular.module('app.frontend') this.selectNote = function(note) { this.selectedNote = note; this.selectionMade()(note); - - note.onDelete = function() { - this.setNotes(this.group.notes, false); - }.bind(this); } this.createNewNote = function() { var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); - this.newNote = new Note({title: title, content: '', dummy: true}); + this.newNote = new Note({dummy: true}); + this.newNote.content.title = title; this.newNote.shared_via_group = this.group.presentation && this.group.presentation.enabled; this.selectNote(this.newNote); this.addNew()(this.newNote); diff --git a/app/assets/javascripts/app/frontend/models/note.js b/app/assets/javascripts/app/frontend/models/note.js index 2b9253ea7..374c98f3b 100644 --- a/app/assets/javascripts/app/frontend/models/note.js +++ b/app/assets/javascripts/app/frontend/models/note.js @@ -1,22 +1,33 @@ var Note = function (json_obj) { + var content; + + Object.defineProperty(this, "content", { + get: function() { + return content; + }, + set: function(value) { + var finalValue = value; + + if(typeof value === 'string') { + try { + decodedValue = JSON.parse(value); + finalValue = decodedValue; + } + catch(e) { + finalValue = value; + } + } + + content = finalValue; + }, + enumerable: true, + }); + _.merge(this, json_obj); -}; -Note.prototype = { - set content(content) { - try { - var data = JSON.parse(content); - this.title = data.title || data.name; - this.text = data.text || data.content; - } - catch(e) { - this.text = content; - } + if(!this.content) { + this.content = {title: "", text: ""}; } -} - -Note.prototype.JSONContent = function() { - return JSON.stringify({title: this.title, text: this.text}); }; /* Returns true if note is shared individually or via group */ @@ -25,7 +36,7 @@ Note.prototype.isPublic = function() { }; Note.prototype.isEncrypted = function() { - return this.loc_eek || this.local_eek ? true : false; + return (this.loc_eek || this.local_eek) && typeof this.content === 'string' ? true : false; } Note.prototype.hasEnabledPresentation = function() { diff --git a/app/assets/javascripts/app/frontend/models/user.js b/app/assets/javascripts/app/frontend/models/user.js index 4954015dc..1421dd92b 100644 --- a/app/assets/javascripts/app/frontend/models/user.js +++ b/app/assets/javascripts/app/frontend/models/user.js @@ -1,10 +1,3 @@ var User = function (json_obj) { _.merge(this, json_obj); }; - -User.prototype.getUsername = function() { - if(!this.presentation) { - return null; - } - return this.presentation.root_path; -}; diff --git a/app/assets/javascripts/app/services/apiController.js b/app/assets/javascripts/app/services/apiController.js index 9392db448..beb5b2555 100644 --- a/app/assets/javascripts/app/services/apiController.js +++ b/app/assets/javascripts/app/services/apiController.js @@ -99,23 +99,19 @@ angular.module('app.services') this._performPasswordChange(current_keys, new_keys, function(response){ if(response && !response.errors) { // this.showNewPasswordForm = false; - if(user.local_encryption_enabled) { - // reencrypt data with new gk - this.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){ - if(success) { - this.setGk(new_keys.gk); - alert("Your password has been changed and your data re-encrypted."); - } else { - // rollback password - this._performPasswordChange(new_keys, current_keys, function(response){ - alert("There was an error changing your password. Your password has been rolled back."); - window.location.reload(); - }) - } - }.bind(this)); - } else { - alert("Your password has been changed."); - } + // reencrypt data with new gk + this.reencryptAllNotesAndSave(user, new_keys.gk, current_keys.gk, function(success){ + if(success) { + this.setGk(new_keys.gk); + alert("Your password has been changed and your data re-encrypted."); + } else { + // rollback password + this._performPasswordChange(new_keys, current_keys, function(response){ + alert("There was an error changing your password. Your password has been rolled back."); + window.location.reload(); + }) + } + }.bind(this)); } else { // this.showNewPasswordForm = false; alert("There was an error changing your password. Please try again."); @@ -144,46 +140,6 @@ angular.module('app.services') }) } - this.enableEncryptionForUser = function(user, callback) { - Restangular.one("users", user.id).one('enable_encryption').post().then(function(response){ - - var enabled = response.plain().local_encryption_enabled; - if(!enabled) { - callback(false, enabled); - return; - } - this.handleEncryptionStatusChange(user, enabled, callback); - }.bind(this)) - } - - this.disableEncryptionForUser = function(user, callback) { - - Restangular.one("users", user.id).one('disable_encryption').post().then(function(response){ - var enabled = response.plain().local_encryption_enabled; - - if(enabled) { - // something went wrong - callback(false, enabled); - return; - } - this.handleEncryptionStatusChange(user, enabled, callback); - }.bind(this)) - } - - this.handleEncryptionStatusChange = function(user, encryptionEnabled, callback) { - var allNotes = user.filteredNotes(); - if(encryptionEnabled) { - allNotes = allNotes.filter(function(note){return note.isPublic() == false}); - this.encryptNotes(allNotes, this.retrieveGk()); - } else { - this.decryptNotes(allNotes, this.retrieveGk()); - } - - this.saveBatchNotes(user, allNotes, encryptionEnabled, function(success) { - callback(success, encryptionEnabled); - }.bind(this)); - } - /* Ensures that if encryption is disabled, all local notes are uncrypted, and that if it's enabled, that all local notes are encrypted @@ -193,7 +149,7 @@ angular.module('app.services') var notesNeedingUpdate = []; var key = this.retrieveGk(); allNotes.forEach(function(note){ - if(user.local_encryption_enabled && !note.isPublic()) { + if(!note.isPublic()) { if(!note.isEncrypted()) { // needs encryption this.encryptSingleNote(note, key); @@ -209,7 +165,7 @@ angular.module('app.services') }.bind(this)) if(notesNeedingUpdate.length > 0) { - this.saveBatchNotes(user, notesNeedingUpdate, user.local_encryption_enabled, callback) + this.saveBatchNotes(user, notesNeedingUpdate, true, callback) } } @@ -266,9 +222,12 @@ angular.module('app.services') }) } - if(user.local_encryption_enabled && group.notes.length > 0) { + if(group.notes.length > 0) { // decrypt group notes first var notes = group.notes; + notes.forEach(function(note){ + note.shared_via_group = true; + }) this.decryptNotesWithLocalKey(notes); this.saveBatchNotes(user, notes, false, function(success){ shareFn(); @@ -333,17 +292,16 @@ angular.module('app.services') this.createRequestParamsFromNote = function(note, user) { var params = {id: note.id}; - if(user.local_encryption_enabled && !note.pending_share && !note.isPublic()) { + if(!note.pending_share && !note.isPublic()) { // encrypted var noteCopy = _.cloneDeep(note); this.encryptSingleNote(noteCopy, this.retrieveGk()); - - params.loc_enc_content = noteCopy.loc_enc_content || local_encrypted_content; - params.loc_eek = noteCopy.loc_eek || noteCopy.local_eek; + params.content = noteCopy.content; + params.loc_eek = noteCopy.loc_eek; } else { // decrypted - params.content = note.JSONContent(); + params.content = JSON.stringify(note.content); } return params; } @@ -365,7 +323,7 @@ angular.module('app.services') if(!user.id) { if(confirm("Note: You are not signed in. Any note you share cannot be edited or unshared.")) { var request = Restangular.one("notes").one("share"); - _.merge(request, {name: note.title, content: note.content}); + _.merge(request, {name: note.content.title, content: note.content}); request.post().then(function(response){ var presentation = response.plain(); _.merge(note, {presentation: presentation}); @@ -384,16 +342,10 @@ angular.module('app.services') }) } - if(user.local_encryption_enabled) { - if(confirm("Note: Sharing this note will remove its local encryption.")) { - note.pending_share = true; - this.saveNote(user, note, function(saved_note){ - shareFn(saved_note, callback); - }) - } - } else { - shareFn(note, callback); - } + note.pending_share = true; + this.saveNote(user, note, function(saved_note){ + shareFn(saved_note, callback); + }) } } @@ -407,6 +359,53 @@ angular.module('app.services') }) } + + + + /* + Import + */ + + this.importJSONData = function(jsonString, callback) { + var data = JSON.parse(jsonString); + console.log("importing data", JSON.parse(jsonString)); + // data.notes = _.map(data.notes, function(json_obj) { + // return new Note(json_obj); + // }); + console.log("objectifying data", this.staticifyObject(data)); + + data.notes.forEach(function(note){ + var presentation = data.presentations.find(function(presentation){ + return presentation.presentable_type == "Note" && presentation.presentable_id == note.id; + }) + + if(presentation) { + // public + // console.log("public note", note); + note.content = JSON.stringify(note.content); + // console.log("after json", note); + } else { + // private + this.encryptSingleNoteWithLocalKey(note); + } + }.bind(this)) + + + var request = Restangular.one("import"); + request.data = data; + console.log("posting import request", request); + request.post().then(function(response){ + callback(true, response); + }) + .catch(function(error){ + callback(false, error); + }) + } + + /* + Export + */ + this.notesDataFile = function(user) { var textFile = null; var makeTextFile = function (text) { @@ -424,18 +423,59 @@ angular.module('app.services') return textFile; }.bind(this); - // remove irrelevant keys + var notes = _.map(user.filteredNotes(), function(note){ + console.log("mapping note", note); return { id: note.id, - title: note.title, - text: note.text, + uuid: note.uuid, + content: note.content, + group_id: note.group_id, created_at: note.created_at, modified_at: note.modified_at, - group_id: note.group_id } }); - return makeTextFile(JSON.stringify(notes, null, 2 /* pretty print */)); + + var groups = _.map(user.groups, function(group){ + return { + id: group.id, + uuid: group.uuid, + name: group.name, + created_at: group.created_at, + modified_at: group.modified_at, + } + }); + + var modelsWithPresentations = user.groups.concat(user.notes).filter(function(model){ + return model.presentation != null; + }) + + var presentations = _.map(modelsWithPresentations, function(model){ + return model.presentation; + }) + + presentations = _.map(presentations, function(presentation){ + return { + id: presentation.id, + uuid: presentation.uuid, + host: presentation.host, + root_path: presentation.root_path, + relative_path: presentation.relative_path, + presentable_type: presentation.presentable_type, + presentable_id: presentation.presentable_id, + enabled: presentation.enabled, + created_at: presentation.created_at, + modified_at: presentation.modified_at, + } + }); + + var data = { + notes: notes, + groups: groups, + presentations: presentations + } + + return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */)); } @@ -539,13 +579,13 @@ angular.module('app.services') this.encryptSingleNote = function(note, key) { var ek = null; - if(note.isEncrypted()) { - ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key); + if(note.loc_eek) { + ek = Neeto.crypto.decryptText(note.loc_eek, key); } else { ek = Neeto.crypto.generateRandomEncryptionKey(); note.loc_eek = Neeto.crypto.encryptText(ek, key); } - note.loc_enc_content = Neeto.crypto.encryptText(note.JSONContent(), ek); + note.content = Neeto.crypto.encryptText(JSON.stringify(note.content), ek); note.local_encryption_scheme = "1.0"; } @@ -563,18 +603,27 @@ angular.module('app.services') this.encryptNotes(notes, this.retrieveGk()); } + this.encryptNonPublicNotesWithLocalKey = function(notes) { + var nonpublic = notes.filter(function(note){ + return !note.isPublic() && !note.pending_share; + }) + this.encryptNotes(nonpublic, this.retrieveGk()); + } + this.decryptSingleNoteWithLocalKey = function(note) { this.decryptSingleNote(note, this.retrieveGk()); } this.decryptSingleNote = function(note, key) { var ek = Neeto.crypto.decryptText(note.loc_eek || note.local_eek, key); - var content = Neeto.crypto.decryptText(note.loc_enc_content || note.local_encrypted_content, ek); + var content = Neeto.crypto.decryptText(note.content, ek); + // console.log("decrypted contnet", content); note.content = content; } this.decryptNotes = function(notes, key) { notes.forEach(function(note){ + // console.log("is encrypted?", note); if(note.isEncrypted()) { this.decryptSingleNote(note, key); } diff --git a/app/assets/javascripts/app/services/directives/file-change.js b/app/assets/javascripts/app/services/directives/file-change.js new file mode 100644 index 000000000..c3e6e67b7 --- /dev/null +++ b/app/assets/javascripts/app/services/directives/file-change.js @@ -0,0 +1,17 @@ +angular + .module('app.services') + .directive('fileChange', function() { + return { + restrict: 'A', + scope: { + handler: '&' + }, + link: function (scope, element) { + element.on('change', function (event) { + scope.$apply(function(){ + scope.handler({files: event.target.files}); + }); + }); + } + }; +}); diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index bbfd6554e..bb0a2b63e 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -2,7 +2,7 @@ .content .section-title-bar.editor-heading{"ng-class" => "{'shared' : ctrl.note.isPublic() }"} .title - %input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", + %input.input#note-title-editor{"ng-model" => "ctrl.note.content.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)", "ng-disabled" => "ctrl.note.locked", "ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "select-on-click" => "true"} .save-status {{ctrl.noteStatus}} @@ -56,6 +56,6 @@ "iteration-callback" => "ctrl.callback", "prebegin-fn" => "ctrl.prebeginFn", "iteration-delay" => "2000", "cursor" => ""} %code{"ng-if" => "ctrl.currentDemoContent.text"} .content-sampler.sampler{"typewrite" => "true", "text" => "ctrl.currentDemoContent.text", "type-delay" => "10", "iteration-callback" => "ctrl.contentCallback"} - %textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.text", + %textarea.editable#note-text-editor{"ng-disabled" => "ctrl.note.locked", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.content.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"} .preview{"ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"} diff --git a/app/assets/templates/frontend/header.html.haml b/app/assets/templates/frontend/header.html.haml index 8754264d9..5edf13511 100644 --- a/app/assets/templates/frontend/header.html.haml +++ b/app/assets/templates/frontend/header.html.haml @@ -52,28 +52,8 @@ .title Local Encryption .desc Encrypt notes locally before sending to server. Neither the server owner nor an intrusive government can decrypt your locally encrypted notes. .action-container - %span.status-title Status: - {{ctrl.user.local_encryption_enabled ? 'enabled' : 'disabled'}} - {{" | "}} - %a{"ng-click" => "ctrl.toggleEncryptionStatus()"} - {{ctrl.user.local_encryption_enabled ? 'Disable' : 'Enable'}} - .subtext{"ng-if" => "ctrl.user.local_encryption_enabled"} - {{ctrl.encryptionStatusForNotes()}} (shared notes not encrypted) - .encryption-confirmation{"ng-if" => "ctrl.encryptionConfirmation"} - %div{"ng-if" => "ctrl.user.local_encryption_enabled"} - %p Are you sure you want to disable local encryption? All currently encrypted notes will be decrypted locally, then sent back to Neeto servers over a secure connection. - %div{"ng-if" => "!ctrl.user.local_encryption_enabled"} - %p We're glad you're taking privacy and security into your own hands. There are a couple things you should note about moving to local encryption: - %ul - %li - If you forget your password, there is no way to reset it or recover it. Your data will be forever lost without your password. - (You can however still change your password, as long as you know your current password.) - %li - The strength of the encryption is tied to the strength of your password. If you take your security seriously, you should use a password of at least 32 characters long. - %p Are you sure you want to enable local encryption? - .buttons - %a.cancel{"ng-click" => "ctrl.cancelEncryptionChange()"} Cancel - %a.confirm{"ng-click" => "ctrl.confirmEncryptionChange()"} Confirm + %span.status-title Status: enabled. + {{ctrl.encryptionStatusForNotes()}} (shared notes not encrypted) .account-item{"ng-if" => "ctrl.user.email"} .icon-container %img.icon.archive{"lazy-img" => "assets/archive.png"} @@ -82,6 +62,10 @@ .desc Note: data archives that you download using the link below are decrypted before save. You should take care to store them in a safe location. .action-container %a#download-archive{"ng-click" => "ctrl.downloadDataArchive()"} Download Latest Data Archive + %label#import-archive + %input{"type" => "file", "style" => "display: none;", "file-change" => "", "handler" => "ctrl.importFileSelected(files)"} + %span + Import Data from Archive .account-item .meta-container @@ -134,4 +118,3 @@ %strong.question How does local encryption work? %p.answer Users who opt into using local encryption can add an additional layer of security over their notes. These notes will be encrypted locally on your machine before being sent over the wire. This means that when Neeto receives your notes, we have no idea what the contents are. And if a government ever forces us to give up your data, we couldn't decrypt it for them even if we wanted to. %p This encryption is based on your password, which is also never sent over the air. The strength of this encryption is directly tied to the strength of your password. - diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index 998ef4c30..df45140d9 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -34,5 +34,5 @@ "ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}", "ng-attr-draggable" => "{{note.dummy ? undefined : 'true'}}", "note" => "note"} .name - {{note.title}} + {{note.content.title}} .date {{note.created_at || 'Now'}} diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index a5f79566b..4a994e1e7 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -1,4 +1,4 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password, :content, :name, :local_encrypted_content, :loc_eek] +Rails.application.config.filter_parameters += [:password] From d200df0b859c573eebda55a0d61029e349490ddd Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 12 Dec 2016 20:00:21 -0600 Subject: [PATCH 4/4] encryption updates --- .../app/frontend/controllers/editor.js | 12 +- .../app/frontend/controllers/groups.js | 4 +- .../app/frontend/controllers/header.js | 18 +- .../app/frontend/controllers/home.js | 27 +-- .../app/frontend/controllers/notes.js | 2 +- .../javascripts/app/frontend/models/group.js | 3 + .../javascripts/app/frontend/models/note.js | 17 +- .../javascripts/app/frontend/models/user.js | 20 +++ .../javascripts/app/services/apiController.js | 161 ++++++++---------- .../templates/frontend/editor.html.haml | 8 +- .../templates/frontend/groups.html.haml | 2 +- app/assets/templates/frontend/notes.html.haml | 6 +- 12 files changed, 136 insertions(+), 144 deletions(-) create mode 100644 app/assets/javascripts/app/frontend/models/group.js diff --git a/app/assets/javascripts/app/frontend/controllers/editor.js b/app/assets/javascripts/app/frontend/controllers/editor.js index e42bcf9a3..4bb7cf926 100644 --- a/app/assets/javascripts/app/frontend/controllers/editor.js +++ b/app/assets/javascripts/app/frontend/controllers/editor.js @@ -235,11 +235,13 @@ angular.module('app.frontend') this.saveUrl = function($event) { $event.target.blur(); - var original = this.note.presentation.root_path; - this.note.presentation.root_path = this.url.token; - apiController.saveNote(this.user, this.note, function(note){ - if(!note) { - this.note.token = original; + + var original = this.note.presentation.relative_path; + this.note.presentation.relative_path = this.url.token; + + apiController.updatePresentation(this.note, this.note.presentation, function(response){ + if(!response) { + this.note.presentation.relative_path = original; this.url.token = original; alert("This URL is not available."); } else { diff --git a/app/assets/javascripts/app/frontend/controllers/groups.js b/app/assets/javascripts/app/frontend/controllers/groups.js index 3d1204937..39989394a 100644 --- a/app/assets/javascripts/app/frontend/controllers/groups.js +++ b/app/assets/javascripts/app/frontend/controllers/groups.js @@ -53,7 +53,7 @@ angular.module('app.frontend') return; } - this.newGroup = {notes : []}; + this.newGroup = new Group({notes : []}); if(!this.user.id) { this.newGroup.id = Neeto.crypto.generateRandomKey() } @@ -92,7 +92,7 @@ angular.module('app.frontend') } this.noteCount = function(group) { - var validNotes = apiController.filterDummyNotes(group.notes); + var validNotes = Note.filterDummyNotes(group.notes); return validNotes.length; } diff --git a/app/assets/javascripts/app/frontend/controllers/header.js b/app/assets/javascripts/app/frontend/controllers/header.js index f39de05c7..88bd95f1e 100644 --- a/app/assets/javascripts/app/frontend/controllers/header.js +++ b/app/assets/javascripts/app/frontend/controllers/header.js @@ -13,17 +13,7 @@ angular.module('app.frontend') bindToController: true, link:function(scope, elem, attrs, ctrl) { - // scope.$on('auth:login-success', function(event, user) { - // ctrl.onAuthSuccess(user); - // }); - scope.$on('auth:validation-success', function(ev) { - // TODO - setTimeout(function(){ - ctrl.onValidationSuccess(); - }) - - }); } } }) @@ -118,17 +108,11 @@ angular.module('app.frontend') }.bind(this)); } - this.onValidationSuccess = function() { - apiController.verifyEncryptionStatusOfAllNotes(this.user, function(success){ - - }); - } - this.encryptionStatusForNotes = function() { var allNotes = this.user.filteredNotes(); var countEncrypted = 0; allNotes.forEach(function(note){ - if(note.isEncrypted()) { + if(note.encryptionEnabled()) { countEncrypted++; } }.bind(this)) diff --git a/app/assets/javascripts/app/frontend/controllers/home.js b/app/assets/javascripts/app/frontend/controllers/home.js index 3455790d9..94f541334 100644 --- a/app/assets/javascripts/app/frontend/controllers/home.js +++ b/app/assets/javascripts/app/frontend/controllers/home.js @@ -6,21 +6,12 @@ angular.module('app.frontend') var onUserSet = function() { - $scope.defaultUser.notes = _.map($scope.defaultUser.notes, function(json_obj) { - return new Note(json_obj); - }); + $scope.allGroup = new Group({name: "All", all: true}); + $scope.groups = $scope.defaultUser.groups; - $scope.defaultUser.filteredNotes = function() { - return apiController.filterDummyNotes($scope.defaultUser.notes); - } - var groups = $scope.defaultUser.groups; - var allNotes = $scope.defaultUser.notes; - groups.forEach(function(group){ - var notes = allNotes.filter(function(note){return note.group_id && note.group_id == group.id}); - group.notes = notes; - }) - $scope.allGroup = {name: "All", all: true}; - $scope.groups = groups; + apiController.verifyEncryptionStatusOfAllNotes($scope.defaultUser, function(success){ + + }); } apiController.getCurrentUser(function(response){ @@ -39,7 +30,7 @@ angular.module('app.frontend') */ $scope.updateAllGroup = function() { - var allNotes = apiController.filterDummyNotes($scope.defaultUser.notes); + var allNotes = Note.filterDummyNotes($scope.defaultUser.notes); $scope.defaultUser.notes = allNotes; $scope.allGroup.notes = allNotes; } @@ -78,6 +69,8 @@ angular.module('app.frontend') originalNote.group_id = null; } else { originalNote.group_id = newGroup.id + originalNote.group = newGroup; + newGroup.notes.unshift(originalNote); newGroup.notes.sort(function(a,b){ //subtract to get a value that is either negative, positive, or zero. @@ -85,8 +78,6 @@ angular.module('app.frontend') }); } - originalNote.shared_via_group = newGroup.presentation && newGroup.presentation.enabled; - apiController.saveNote($scope.defaultUser, originalNote, function(note){ _.merge(originalNote, note); }); @@ -97,7 +88,7 @@ angular.module('app.frontend') */ $scope.notesRemoveGroup = function(group) { - var validNotes = apiController.filterDummyNotes(group.notes); + var validNotes = Note.filterDummyNotes(group.notes); if(validNotes == 0) { // if no more notes, delete group apiController.deleteGroup($scope.defaultUser, group, function(){ diff --git a/app/assets/javascripts/app/frontend/controllers/notes.js b/app/assets/javascripts/app/frontend/controllers/notes.js index a81d1f05e..88805218a 100644 --- a/app/assets/javascripts/app/frontend/controllers/notes.js +++ b/app/assets/javascripts/app/frontend/controllers/notes.js @@ -138,7 +138,7 @@ angular.module('app.frontend') var title = "New Note" + (this.notes ? (" " + (this.notes.length + 1)) : ""); this.newNote = new Note({dummy: true}); this.newNote.content.title = title; - this.newNote.shared_via_group = this.group.presentation && this.group.presentation.enabled; + this.newNote.group = this.group; this.selectNote(this.newNote); this.addNew()(this.newNote); } diff --git a/app/assets/javascripts/app/frontend/models/group.js b/app/assets/javascripts/app/frontend/models/group.js new file mode 100644 index 000000000..957642717 --- /dev/null +++ b/app/assets/javascripts/app/frontend/models/group.js @@ -0,0 +1,3 @@ +var Group = function (json_obj) { + _.merge(this, json_obj); +}; diff --git a/app/assets/javascripts/app/frontend/models/note.js b/app/assets/javascripts/app/frontend/models/note.js index 374c98f3b..53d5cb0bf 100644 --- a/app/assets/javascripts/app/frontend/models/note.js +++ b/app/assets/javascripts/app/frontend/models/note.js @@ -23,6 +23,10 @@ var Note = function (json_obj) { enumerable: true, }); + this.setContentRaw = function(rawContent) { + content = rawContent; + } + _.merge(this, json_obj); if(!this.content) { @@ -30,17 +34,22 @@ var Note = function (json_obj) { } }; +Note.filterDummyNotes = function(notes) { + var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null}); + return filtered; +} + /* Returns true if note is shared individually or via group */ Note.prototype.isPublic = function() { - return this.hasEnabledPresentation() || this.shared_via_group; + return this.presentation || (this.group && this.group.presentation); }; Note.prototype.isEncrypted = function() { - return (this.loc_eek || this.local_eek) && typeof this.content === 'string' ? true : false; + return this.encryptionEnabled() && typeof this.content === 'string' ? true : false; } -Note.prototype.hasEnabledPresentation = function() { - return this.presentation && this.presentation.enabled; +Note.prototype.encryptionEnabled = function() { + return this.loc_eek; } Note.prototype.presentationURL = function() { diff --git a/app/assets/javascripts/app/frontend/models/user.js b/app/assets/javascripts/app/frontend/models/user.js index 1421dd92b..a6ff61349 100644 --- a/app/assets/javascripts/app/frontend/models/user.js +++ b/app/assets/javascripts/app/frontend/models/user.js @@ -1,3 +1,23 @@ var User = function (json_obj) { _.merge(this, json_obj); + + this.notes = _.map(this.notes, function(json_obj) { + return new Note(json_obj); + }); + + this.groups = _.map(this.groups, function(json_obj) { + return new Group(json_obj); + }); + + this.groups.forEach(function(group){ + var notes = this.notes.filter(function(note){return note.group_id && note.group_id == group.id}); + notes.forEach(function(note){ + note.group = group; + }) + group.notes = notes; + }.bind(this)) }; + +User.prototype.filteredNotes = function() { + return Note.filterDummyNotes(this.notes); +} diff --git a/app/assets/javascripts/app/services/apiController.js b/app/assets/javascripts/app/services/apiController.js index beb5b2555..41a1c2d96 100644 --- a/app/assets/javascripts/app/services/apiController.js +++ b/app/assets/javascripts/app/services/apiController.js @@ -147,25 +147,21 @@ angular.module('app.services') this.verifyEncryptionStatusOfAllNotes = function(user, callback) { var allNotes = user.filteredNotes(); var notesNeedingUpdate = []; - var key = this.retrieveGk(); allNotes.forEach(function(note){ if(!note.isPublic()) { - if(!note.isEncrypted()) { - // needs encryption - this.encryptSingleNote(note, key); + if(note.encryptionEnabled() && !note.isEncrypted()) { notesNeedingUpdate.push(note); } } else { if(note.isEncrypted()) { - // needs decrypting - this.decryptSingleNote(note, key); notesNeedingUpdate.push(note); } } }.bind(this)) if(notesNeedingUpdate.length > 0) { - this.saveBatchNotes(user, notesNeedingUpdate, true, callback) + console.log("verifying encryption, notes need updating", notesNeedingUpdate); + this.saveBatchNotes(user, notesNeedingUpdate, callback) } } @@ -210,41 +206,31 @@ angular.module('app.services') } this.shareGroup = function(user, group, callback) { - var shareFn = function() { Restangular.one("users", user.id).one("groups", group.id).one("presentations").post() .then(function(response){ var presentation = response.plain(); - group.notes.forEach(function(note){ - note.shared_via_group = true; - }); _.merge(group, {presentation: presentation}); callback(presentation); - }) - } - if(group.notes.length > 0) { - // decrypt group notes first - var notes = group.notes; - notes.forEach(function(note){ - note.shared_via_group = true; - }) - this.decryptNotesWithLocalKey(notes); - this.saveBatchNotes(user, notes, false, function(success){ - shareFn(); - }) - } else { - shareFn(); - } + if(group.notes.length > 0) { + // decrypt notes + this.saveBatchNotes(user, group.notes, function(success){}) + } + }.bind(this)) } this.unshareGroup = function(user, group, callback) { var request = Restangular.one("users", user.id).one("groups", group.id).one("presentations", group.presentation.id); - request.enabled = false; - request.patch().then(function(response){ - var presentation = response.plain(); - _.merge(group, {presentation: presentation}); - callback(presentation); - }) + request.remove().then(function(response){ + group.presentation = null; + callback(null); + + if(group.notes.length > 0) { + // encrypt notes + var notes = group.notes; + this.saveBatchNotes(user, notes, function(success){}) + } + }.bind(this)) } @@ -255,7 +241,7 @@ angular.module('app.services') Notes */ - this.saveBatchNotes = function(user, notes, encryptionEnabled, callback) { + this.saveBatchNotes = function(user, notes, callback) { var request = Restangular.one("users", user.id).one("notes/batch_update"); request.notes = _.map(notes, function(note){ return this.createRequestParamsFromNote(note, user); @@ -302,6 +288,7 @@ angular.module('app.services') else { // decrypted params.content = JSON.stringify(note.content); + params.loc_eek = null; } return params; } @@ -351,15 +338,29 @@ angular.module('app.services') this.unshareNote = function(user, note, callback) { var request = Restangular.one("users", user.id).one("notes", note.id).one("presentations", note.presentation.id); - request.enabled = false; - request.patch().then(function(response){ - var presentation = response.plain(); - _.merge(note, {presentation: presentation}); - callback(note); + request.remove().then(function(response){ + note.presentation = null; + callback(null); }) } + /* + Presentations + */ + + this.updatePresentation = function(resource, presentation, callback) { + var request = Restangular.one("users", user.id) + .one(resource.constructor.name.toLowerCase() + "s", resource.id) + .one("presentations", resource.presentation.id); + _.merge(request, presentation); + request.patch().then(function(response){ + callback(response.plain()); + }) + .catch(function(error){ + callback(nil); + }) + } /* @@ -368,32 +369,26 @@ angular.module('app.services') this.importJSONData = function(jsonString, callback) { var data = JSON.parse(jsonString); + var user = new User(data); console.log("importing data", JSON.parse(jsonString)); - // data.notes = _.map(data.notes, function(json_obj) { - // return new Note(json_obj); - // }); - console.log("objectifying data", this.staticifyObject(data)); - - data.notes.forEach(function(note){ - var presentation = data.presentations.find(function(presentation){ - return presentation.presentable_type == "Note" && presentation.presentable_id == note.id; - }) - - if(presentation) { - // public - // console.log("public note", note); - note.content = JSON.stringify(note.content); - // console.log("after json", note); + user.notes.forEach(function(note) { + if(note.isPublic()) { + note.setContentRaw(JSON.stringify(note.content)); } else { - // private this.encryptSingleNoteWithLocalKey(note); } + + // prevent circular links + note.group = null; }.bind(this)) + user.groups.forEach(function(group){ + // prevent circular links + group.notes = null; + }) var request = Restangular.one("import"); - request.data = data; - console.log("posting import request", request); + request.data = {notes: user.notes, groups: user.groups}; request.post().then(function(response){ callback(true, response); }) @@ -423,9 +418,24 @@ angular.module('app.services') return textFile; }.bind(this); + var presentationParams = function(presentation) { + if(!presentation) { + return null; + } + + return { + id: presentation.id, + uuid: presentation.uuid, + root_path: presentation.root_path, + relative_path: presentation.relative_path, + presentable_type: presentation.presentable_type, + presentable_id: presentation.presentable_id, + created_at: presentation.created_at, + modified_at: presentation.modified_at, + } + } var notes = _.map(user.filteredNotes(), function(note){ - console.log("mapping note", note); return { id: note.id, uuid: note.uuid, @@ -433,6 +443,7 @@ angular.module('app.services') group_id: note.group_id, created_at: note.created_at, modified_at: note.modified_at, + presentation: presentationParams(note.presentation) } }); @@ -443,36 +454,13 @@ angular.module('app.services') name: group.name, created_at: group.created_at, modified_at: group.modified_at, - } - }); - - var modelsWithPresentations = user.groups.concat(user.notes).filter(function(model){ - return model.presentation != null; - }) - - var presentations = _.map(modelsWithPresentations, function(model){ - return model.presentation; - }) - - presentations = _.map(presentations, function(presentation){ - return { - id: presentation.id, - uuid: presentation.uuid, - host: presentation.host, - root_path: presentation.root_path, - relative_path: presentation.relative_path, - presentable_type: presentation.presentable_type, - presentable_id: presentation.presentable_id, - enabled: presentation.enabled, - created_at: presentation.created_at, - modified_at: presentation.modified_at, + presentation: presentationParams(group.presentation) } }); var data = { notes: notes, - groups: groups, - presentations: presentations + groups: groups } return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */)); @@ -505,18 +493,13 @@ angular.module('app.services') - this.filterDummyNotes = function(notes) { - var filtered = notes.filter(function(note){return note.dummy == false || note.dummy == null}); - return filtered; - } - this.staticifyObject = function(object) { return JSON.parse(JSON.stringify(object)); } this.writeUserToLocalStorage = function(user) { var saveUser = _.cloneDeep(user); - saveUser.notes = this.filterDummyNotes(saveUser.notes); + saveUser.notes = Note.filterDummyNotes(saveUser.notes); saveUser.groups.forEach(function(group){ group.notes = null; }.bind(this)) @@ -645,7 +628,7 @@ angular.module('app.services') } }); - this.saveBatchNotes(user, notes, true, function(success) { + this.saveBatchNotes(user, notes, function(success) { callback(success); }.bind(this)); } diff --git a/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index bb0a2b63e..f3e1c0acc 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -26,11 +26,11 @@ %li %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"} Toggle Markdown Preview .shortcut Cmd + M - %li{"ng-if" => "!ctrl.note.hasEnabledPresentation()"} + %li{"ng-if" => "!ctrl.note.presentation"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.shareNote()"} Share - %li{"ng-if" => "ctrl.note.hasEnabledPresentation()"} + %li{"ng-if" => "ctrl.note.presentation"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.editUrlPressed()"} Edit URL - %li{"ng-if" => "ctrl.note.hasEnabledPresentation()"} + %li{"ng-if" => "ctrl.note.presentation"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.unshareNote()"} Unshare %li %a.text{"ng-click" => "ctrl.deleteNote()"} Delete @@ -40,7 +40,7 @@ .panel-body{"style" => "text-align: center; color: black;"} This editor is Markdown enabled. .menu-right-container - .public-link{"ng-if" => "ctrl.note.hasEnabledPresentation()"} + .public-link{"ng-if" => "ctrl.note.presentation"} %a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForNote(ctrl.note)}}", "target" => "_blank"} %span.icon-rss.icon {{ctrl.publicUrlForNote(note)}} diff --git a/app/assets/templates/frontend/groups.html.haml b/app/assets/templates/frontend/groups.html.haml index f5f7cc198..5a2917358 100644 --- a/app/assets/templates/frontend/groups.html.haml +++ b/app/assets/templates/frontend/groups.html.haml @@ -9,7 +9,7 @@ .count {{ctrl.noteCount(ctrl.allGroup)}} .group{"ng-repeat" => "group in ctrl.groups", "ng-click" => "ctrl.selectGroup(group)", "ng-class" => "{'selected' : ctrl.selectedGroup == group}", "droppable" => true, "drop" => "ctrl.handleDrop", "group" => "group"} - .icon.icon-rss{"ng-if" => "group.presentation.enabled"} + .icon.icon-rss{"ng-if" => "group.presentation"} %input.title{"ng-disabled" => "group != ctrl.selectedGroup", "ng-model" => "group.name", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveGroup($event, group)", "mb-autofocus" => "true", "should-focus" => "ctrl.newGroup", "ng-change" => "ctrl.groupTitleDidChange(group)", "ng-focus" => "ctrl.onGroupTitleFocus(group)"} diff --git a/app/assets/templates/frontend/notes.html.haml b/app/assets/templates/frontend/notes.html.haml index df45140d9..0315b646a 100644 --- a/app/assets/templates/frontend/notes.html.haml +++ b/app/assets/templates/frontend/notes.html.haml @@ -14,14 +14,14 @@ %span.caret %span.sr-only %ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"} - %li{"ng-if" => "!ctrl.group.presentation.enabled"} + %li{"ng-if" => "!ctrl.group.presentation"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupShare($event)"} Share Group - %li{"ng-if" => "ctrl.group.presentation.enabled"} + %li{"ng-if" => "ctrl.group.presentation"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupUnshare()"} Unshare Group %li{"ng-if" => "!ctrl.group.all"} %a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedGroupDelete()"} Delete Group .menu-right-container - .public-link{"ng-if" => "ctrl.group.presentation.enabled"} + .public-link{"ng-if" => "ctrl.group.presentation"} %a.url{"ng-if" => "!ctrl.editingUrl", "href" => "{{ctrl.publicUrlForGroup(ctrl.group)}}", "target" => "_blank"} %span.icon-rss.icon {{ctrl.publicUrlForGroup()}}