spec compliance wip

This commit is contained in:
Mo Bitar
2016-12-10 21:32:29 -06:00
parent c6ab2f4122
commit 7bbd18e75c
11 changed files with 231 additions and 165 deletions

View File

@@ -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.

View File

@@ -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 <application name>"},
{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 <application name>"},
{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;

View File

@@ -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))
}

View File

@@ -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();
}
});
/*

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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);
}
});

View File

@@ -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));
}
});

View File

@@ -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()"}

View File

@@ -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'}}

View File

@@ -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]