Files
standardnotes-app-web/app/assets/javascripts/app/services/apiController.js
2016-12-15 17:37:26 -06:00

567 lines
17 KiB
JavaScript

angular.module('app.frontend')
.provider('apiController', function () {
function domainName() {
var domain_comps = location.hostname.split(".");
var domain = domain_comps[domain_comps.length - 2] + "." + domain_comps[domain_comps.length - 1];
return domain;
}
var url;
this.defaultServerURL = function() {
if(!url) {
url = localStorage.getItem("server");
if(!url) {
url = location.protocol + "//" + domainName() + (location.port ? ':' + location.port: '');
}
}
return url;
}
this.$get = function(Restangular, modelManager) {
return new ApiController(Restangular, modelManager);
}
function ApiController(Restangular, modelManager) {
this.setUser = function(user) {
this.user = user;
}
/*
Config
*/
this.getServer = function() {
if(!url) {
url = localStorage.getItem("server");
if(!url) {
url = location.protocol + "//" + domainName() + (location.port ? ':' + location.port: '');
this.setServer(url);
}
}
return url;
}
this.setServer = function(url, refresh) {
localStorage.setItem("server", url);
if(refresh) {
window.location.reload();
}
}
/*
Auth
*/
this.getCurrentUser = function(callback) {
if(!localStorage.getItem("jwt")) {
callback(null);
return;
}
Restangular.one("users/current").get().then(function(response){
var plain = response.plain();
var items = plain.items;
this.decryptItemsWithLocalKey(items);
callback(plain);
}.bind(this))
.catch(function(error){
callback(null);
})
}
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;
// reencrypt data with new gk
this.reencryptAllItemsAndSave(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.");
}
})
}
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
*/
this.setUsername = function(user, username, callback) {
var request = Restangular.one("users", user.id).one("set_username");
request.username = username;
request.post().then(function(response){
callback(response.plain());
})
}
/*
Ensures that if encryption is disabled, all local items are uncrypted,
and that if it's enabled, that all local items are encrypted
*/
this.verifyEncryptionStatusOfAllItems = function(user, callback) {
var allItems = user.filteredItems();
var itemsNeedingUpdate = [];
allItems.forEach(function(item){
if(!item.isPublic()) {
if(item.encryptionEnabled() && !item.isEncrypted()) {
itemsNeedingUpdate.push(item);
}
} else {
if(item.isEncrypted()) {
itemsNeedingUpdate.push(item);
}
}
}.bind(this))
if(itemsNeedingUpdate.length > 0) {
console.log("verifying encryption, items need updating", itemsNeedingUpdate);
this.saveBatchItems(user, itemsNeedingUpdate, callback)
}
}
/*
Items
*/
this.saveDirtyItems = function(callback) {
var dirtyItems = modelManager.dirtyItems;
this.saveItems(dirtyItems, function(response){
modelManager.clearDirtyItems();
callback();
})
}
this.saveItems = function(items, callback) {
console.log("saving items", items);
var request = Restangular.one("users", this.user.uuid).one("items");
request.items = _.map(items, function(item){
return this.createRequestParamsForItem(item);
}.bind(this));
console.log("sending request items", request.items);
request.post().then(function(response) {
var savedItems = response.items;
console.log("response items", savedItems);
// items.forEach(function(item) {
// _.merge(item, _.find(savedItems, {uuid: item.uuid}));
// })
callback(response);
})
}
this.createRequestParamsForItem = function(item) {
var itemCopy = _.cloneDeep(item);
var params = {uuid: item.uuid, content_type: item.content_type};
itemCopy.content.references = _.map(itemCopy.content.references, function(reference){
return {uuid: reference.uuid, content_type: reference.content_type};
})
if(!item.isPublic()) {
// encrypted
this.encryptSingleItem(itemCopy, this.retrieveGk());
params.content = itemCopy.content;
params.loc_eek = itemCopy.loc_eek;
}
else {
// decrypted
params.content = JSON.stringify(item.content);
params.loc_eek = null;
}
return params;
}
this.deleteItem = function(item, callback) {
if(!this.user.id) {
this.writeUserToLocalStorage(this.user);
callback(true);
} else {
Restangular.one("users", this.user.uuid).one("items", item.uuid).remove()
.then(function(response) {
callback(true);
})
}
}
this.shareItem = function(item, callback) {
if(!this.user.id) {
alert("You must be signed in to share.");
} else {
Restangular.one("users", this.user.uuid).one("items", item.uuid).one("presentations").post()
.then(function(response){
var presentation = response.plain();
_.merge(item, {presentation: presentation});
callback(item);
// decrypt references
if(item.references.length > 0) {
this.saveBatchItems(item.references, function(success){})
}
})
}
}
this.unshareItem = function(item, callback) {
var request = Restangular.one("users", this.user.uuid).one("items", item.uuid).one("presentations", item.presentation.uuid);
request.remove().then(function(response){
item.presentation = null;
callback(null);
// encrypt references
if(item.references.length > 0) {
this.saveBatchItems(item.references, function(success){})
}
})
}
/*
Presentations
*/
this.updatePresentation = function(resource, presentation, callback) {
var request = Restangular.one("users", this.user.id)
.one("items", resource.id)
.one("presentations", resource.presentation.id);
_.merge(request, presentation);
request.patch().then(function(response){
callback(response.plain());
})
.catch(function(error){
callback(nil);
})
}
/*
Import
*/
this.importJSONData = function(jsonString, callback) {
var data = JSON.parse(jsonString);
var user = new User(data);
console.log("importing data", JSON.parse(jsonString));
user.items.forEach(function(item) {
if(item.isPublic()) {
item.setContentRaw(JSON.stringify(item.content));
} else {
this.encryptSingleItemWithLocalKey(item);
}
// prevent circular links
item.tag = null;
}.bind(this))
user.tags.forEach(function(tag){
// prevent circular links
tag.items = null;
})
var request = Restangular.one("import");
request.data = {items: user.items, tags: user.tags};
request.post().then(function(response){
callback(true, response);
})
.catch(function(error){
callback(false, error);
})
}
/*
Export
*/
this.itemsDataFile = function(user) {
var textFile = null;
var makeTextFile = function (text) {
var data = new Blob([text], {type: 'text/json'});
// If we are replacing a previously generated file we need to
// manually revoke the object URL to avoid memory leaks.
if (textFile !== null) {
window.URL.revokeObjectURL(textFile);
}
textFile = window.URL.createObjectURL(data);
// returns a URL you can use as a href
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 items = _.map(user.filteredItems(), function(item){
return {
id: item.id,
uuid: item.uuid,
content: item.content,
tag_id: item.tag_id,
created_at: item.created_at,
modified_at: item.modified_at,
presentation: presentationParams(item.presentation)
}
});
var tags = _.map(user.tags, function(tag){
return {
id: tag.id,
uuid: tag.uuid,
name: tag.name,
created_at: tag.created_at,
modified_at: tag.modified_at,
presentation: presentationParams(tag.presentation)
}
});
var data = {
items: items,
tags: tags
}
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
}
/*
Merging
*/
this.mergeLocalDataRemotely = function(user, callback) {
var request = Restangular.one("users", user.id).one("merge");
var tags = user.tags;
request.items = user.items;
request.items.forEach(function(item){
if(item.tag_id) {
var tag = tags.filter(function(tag){return tag.id == item.tag_id})[0];
item.tag_name = tag.name;
}
})
request.post().then(function(response){
callback();
localStorage.removeItem('user');
})
}
this.staticifyObject = function(object) {
return JSON.parse(JSON.stringify(object));
}
this.writeUserToLocalStorage = function(user) {
var saveUser = _.cloneDeep(user);
saveUser.items = Item.filterDummyItems(saveUser.items);
saveUser.tags.forEach(function(tag){
tag.items = null;
}.bind(this))
this.writeToLocalStorage('user', saveUser);
}
this.writeToLocalStorage = function(key, value) {
localStorage.setItem(key, angular.toJson(value));
}
this.localUser = function() {
var user = JSON.parse(localStorage.getItem('user'));
if(!user) {
user = {items: [], tags: []};
}
user.shouldMerge = true;
return user;
}
/*
Drafts
*/
this.saveDraftToDisk = function(draft) {
// localStorage.setItem("draft", JSON.stringify(draft));
}
this.clearDraft = function() {
localStorage.removeItem("draft");
}
this.getDraft = function() {
var draftString = localStorage.getItem("draft");
if(!draftString || draftString == 'undefined') {
return null;
}
return new Item(JSON.parse(draftString));
}
/*
Encrpytion
*/
this.retrieveGk = function() {
if(!this.gk) {
this.gk = localStorage.getItem("gk");
}
return this.gk;
}
this.setGk = function(gk) {
localStorage.setItem('gk', gk);
}
this.signout = function() {
localStorage.removeItem("jwt");
localStorage.removeItem("gk");
}
this.encryptSingleItem = function(item, key) {
var ek = null;
if(item.loc_eek) {
ek = Neeto.crypto.decryptText(item.loc_eek, key);
} else {
ek = Neeto.crypto.generateRandomEncryptionKey();
item.loc_eek = Neeto.crypto.encryptText(ek, key);
}
item.content = Neeto.crypto.encryptText(JSON.stringify(item.content), ek);
item.local_encryption_scheme = "1.0";
}
this.encryptItems = function(items, key) {
items.forEach(function(item){
this.encryptSingleItem(item, key);
}.bind(this));
}
this.encryptSingleItemWithLocalKey = function(item) {
this.encryptSingleItem(item, this.retrieveGk());
}
this.encryptItemsWithLocalKey = function(items) {
this.encryptItems(items, this.retrieveGk());
}
this.encryptNonPublicItemsWithLocalKey = function(items) {
var nonpublic = items.filter(function(item){
return !item.isPublic() && !item.pending_share;
})
this.encryptItems(nonpublic, this.retrieveGk());
}
this.decryptSingleItemWithLocalKey = function(item) {
this.decryptSingleItem(item, this.retrieveGk());
}
this.decryptSingleItem = function(item, key) {
var ek = Neeto.crypto.decryptText(item.loc_eek || item.local_eek, key);
var content = Neeto.crypto.decryptText(item.content, ek);
// console.log("decrypted contnet", content);
item.content = content;
}
this.decryptItems = function(items, key) {
items.forEach(function(item){
// console.log("is encrypted?", item);
if(item.loc_eek && typeof item.content === 'string') {
this.decryptSingleItem(item, key);
}
}.bind(this));
}
this.decryptItemsWithLocalKey = function(items) {
this.decryptItems(items, this.retrieveGk());
}
this.reencryptAllItemsAndSave = function(user, newKey, oldKey, callback) {
var items = user.filteredItems();
items.forEach(function(item){
if(item.loc_eek && typeof item.content === 'string') {
// first decrypt eek with old key
var ek = Neeto.crypto.decryptText(item.loc_eek, oldKey);
// now encrypt ek with new key
item.loc_eek = Neeto.crypto.encryptText(ek, newKey);
}
});
this.saveBatchItems(user, items, function(success) {
callback(success);
}.bind(this));
}
}
});