From ff411f00b1be906a1c6738111e77db8baf91068b Mon Sep 17 00:00:00 2001 From: azivner Date: Tue, 14 Nov 2017 21:54:12 -0500 Subject: [PATCH] server side WIP - saving encrypted note now works, changing terminology of "encrypted note" to "protected note" --- .../0028__rename_encryption_to_protected.sql | 52 ++++++++++++++++++ public/javascripts/context_menu.js | 4 +- public/javascripts/dialogs/note_history.js | 2 +- public/javascripts/encryption.js | 54 ++++++++----------- public/javascripts/note_editor.js | 10 ++-- public/javascripts/note_tree.js | 4 +- public/javascripts/tree_utils.js | 2 +- routes/api/note_history.js | 6 +-- routes/api/notes.js | 14 ++--- routes/api/tree.js | 9 ++-- services/audit_category.js | 2 +- services/data_encryption.js | 47 +++++++++++++++- services/migration.js | 2 +- services/notes.js | 36 +++++++------ services/protected_session.js | 17 +++++- services/request_context.js | 25 +++++++++ services/sha256.js | 1 + services/sync.js | 4 ++ views/index.ejs | 4 +- 19 files changed, 208 insertions(+), 87 deletions(-) create mode 100644 migrations/0028__rename_encryption_to_protected.sql create mode 100644 services/request_context.js create mode 100644 services/sha256.js diff --git a/migrations/0028__rename_encryption_to_protected.sql b/migrations/0028__rename_encryption_to_protected.sql new file mode 100644 index 000000000..76859827a --- /dev/null +++ b/migrations/0028__rename_encryption_to_protected.sql @@ -0,0 +1,52 @@ +UPDATE audit_log SET category = 'PROTECTED' WHERE category = 'ENCRYPTION'; + +DELETE FROM notes WHERE note_clone_id IS NOT NULL AND note_clone_id != ''; + +CREATE TABLE `notes_mig` ( + `note_id` TEXT NOT NULL, + `note_title` TEXT, + `note_text` TEXT, + `date_created` INT, + `date_modified` INT, + `is_protected` INT NOT NULL DEFAULT 0, + `is_deleted` INT NOT NULL DEFAULT 0, + PRIMARY KEY(`note_id`) +); + +INSERT INTO notes_mig (note_id, note_title, note_text, date_created, date_modified, is_protected, is_deleted) + SELECT note_id, note_title, note_text, date_created, date_modified, encryption, is_deleted FROM notes; + +DROP TABLE notes; +ALTER TABLE notes_mig RENAME TO notes; + +CREATE INDEX `IDX_notes_is_deleted` ON `notes` ( + `is_deleted` +); + +CREATE TABLE `notes_history_mig` ( + `note_history_id` TEXT NOT NULL PRIMARY KEY, + `note_id` TEXT NOT NULL, + `note_title` TEXT, + `note_text` TEXT, + `is_protected` INT, + `date_modified_from` INT, + `date_modified_to` INT +); + +INSERT INTO notes_history_mig (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to) + SELECT note_history_id, note_id, note_title, note_text, encryption, date_modified_from, date_modified_to FROM notes_history; + +DROP TABLE notes_history; +ALTER TABLE notes_history_mig RENAME TO notes_history; + +CREATE INDEX `IDX_notes_history_note_id` ON `notes_history` ( + `note_id` +); + +CREATE INDEX `IDX_notes_history_note_date_modified_from` ON `notes_history` ( + `date_modified_from` +); + +CREATE INDEX `IDX_notes_history_note_date_modified_to` ON `notes_history` ( + `date_modified_to` +); \ No newline at end of file diff --git a/public/javascripts/context_menu.js b/public/javascripts/context_menu.js index b8c428dcf..33e044b80 100644 --- a/public/javascripts/context_menu.js +++ b/public/javascripts/context_menu.js @@ -57,9 +57,9 @@ const contextMenu = (function() { if (ui.cmd === "insertNoteHere") { const parentKey = treeUtils.getParentKey(node); - const encryption = treeUtils.getParentEncryption(node); + const isProtected = treeUtils.getParentEncryption(node); - noteEditor.createNote(node, parentKey, 'after', encryption); + noteEditor.createNote(node, parentKey, 'after', isProtected); } else if (ui.cmd === "insertChildNote") { noteEditor.createNote(node, node.key, 'into'); diff --git a/public/javascripts/dialogs/note_history.js b/public/javascripts/dialogs/note_history.js index 07f2e5cc7..8178047a7 100644 --- a/public/javascripts/dialogs/note_history.js +++ b/public/javascripts/dialogs/note_history.js @@ -58,7 +58,7 @@ const noteHistory = (function() { let noteTitle = historyItem.note_title; let noteText = historyItem.note_text; - if (historyItem.encryption > 0) { + if (historyItem.is_protected) { noteTitle = encryption.decryptString(noteTitle); noteText = encryption.decryptString(noteText); } diff --git a/public/javascripts/encryption.js b/public/javascripts/encryption.js index f7574d5ae..0e3d0cdc2 100644 --- a/public/javascripts/encryption.js +++ b/public/javascripts/encryption.js @@ -27,7 +27,7 @@ const encryption = (function() { encryptionSessionTimeout = encSessTimeout; } - function ensureEncryptionIsAvailable(requireEncryption, modal) { + function ensureProtectedSession(requireEncryption, modal) { const dfd = $.Deferred(); if (requireEncryption && !isEncryptionAvailable()) { @@ -116,15 +116,6 @@ const encryption = (function() { return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); } - function encryptNoteIfNecessary(note) { - if (note.detail.encryption === 0) { - return note; - } - else { - return encryptNote(note); - } - } - function encryptString(str) { return encrypt(getDataAes(), str); } @@ -184,36 +175,34 @@ const encryption = (function() { note.detail.note_title = encryptString(note.detail.note_title); note.detail.note_text = encryptString(note.detail.note_text); - note.detail.encryption = 1; + note.detail.is_protected = true; return note; } - async function encryptNoteAndSendToServer() { - await ensureEncryptionIsAvailable(true, true); + async function protectNoteAndSendToServer() { + await ensureProtectedSession(true, true); const note = noteEditor.getCurrentNote(); noteEditor.updateNoteFromInputs(note); - encryptNote(note); + note.detail.is_protected = true; await noteEditor.saveNoteToServer(note); - await changeEncryptionOnNoteHistory(note.detail.note_id, true); - noteEditor.setNoteBackgroundIfEncrypted(note); } - async function changeEncryptionOnNoteHistory(noteId, encrypt) { + async function changeEncryptionOnNoteHistory(noteId, protect) { const result = await $.ajax({ - url: baseApiUrl + 'notes-history/' + noteId + "?encryption=" + (encrypt ? 0 : 1), + url: baseApiUrl + 'notes-history/' + noteId + "?encryption=" + (protect ? 0 : 1), type: 'GET', error: () => showError("Error getting note history.") }); for (const row of result) { - if (encrypt) { + if (protect) { row.note_title = encryptString(row.note_title); row.note_text = encryptString(row.note_text); } @@ -222,7 +211,7 @@ const encryption = (function() { row.note_text = decryptString(row.note_text); } - row.encryption = encrypt ? 1 : 0; + row.is_protected = protect; await $.ajax({ url: baseApiUrl + 'notes-history', @@ -236,14 +225,14 @@ const encryption = (function() { } } - async function decryptNoteAndSendToServer() { - await ensureEncryptionIsAvailable(true, true); + async function unprotectNoteAndSendToServer() { + await ensureProtectedSession(true, true); const note = noteEditor.getCurrentNote(); noteEditor.updateNoteFromInputs(note); - note.detail.encryption = 0; + note.detail.is_protected = false; await noteEditor.saveNoteToServer(note); @@ -253,13 +242,13 @@ const encryption = (function() { } async function encryptSubTree(noteId) { - await ensureEncryptionIsAvailable(true, true); + await ensureProtectedSession(true, true); updateSubTreeRecursively(noteId, note => { - if (note.detail.encryption === null || note.detail.encryption === 0) { + if (!note.detail.is_protected) { encryptNote(note); - note.detail.encryption = 1; + note.detail.is_protected = true; return true; } @@ -280,13 +269,13 @@ const encryption = (function() { } async function decryptSubTree(noteId) { - await ensureEncryptionIsAvailable(true, true); + await ensureProtectedSession(true, true); updateSubTreeRecursively(noteId, note => { - if (note.detail.encryption === 1) { + if (note.detail.is_protected) { decryptNote(note); - note.detail.encryption = 0; + note.detail.is_protected = false; return true; } @@ -368,14 +357,13 @@ const encryption = (function() { return { setEncryptionSessionTimeout, - ensureEncryptionIsAvailable, + ensureProtectedSession, resetEncryptionSession, isEncryptionAvailable, - encryptNoteIfNecessary, encryptString, decryptString, - encryptNoteAndSendToServer, - decryptNoteAndSendToServer, + protectNoteAndSendToServer, + unprotectNoteAndSendToServer, encryptSubTree, decryptSubTree, getProtectedSessionId diff --git a/public/javascripts/note_editor.js b/public/javascripts/note_editor.js index f365fe9f2..95252a27a 100644 --- a/public/javascripts/note_editor.js +++ b/public/javascripts/note_editor.js @@ -59,8 +59,6 @@ const noteEditor = (function() { updateNoteFromInputs(note); - encryption.encryptNoteIfNecessary(note); - await saveNoteToServer(note); } @@ -70,7 +68,7 @@ const noteEditor = (function() { note.detail.note_text = contents; - if (!note.detail.encryption) { + if (!note.detail.is_protected) { const linkRegexp = /]+?href="[^"]*app#([A-Za-z0-9]{22})"[^>]*?>[^<]+?<\/a>/g; let match; @@ -170,11 +168,11 @@ const noteEditor = (function() { function setTreeBasedOnEncryption(note) { const node = treeUtils.getNodeByKey(note.detail.note_id); - node.toggleClass("encrypted", note.detail.encryption > 0); + node.toggleClass("encrypted", note.detail.is_protected); } function setNoteBackgroundIfEncrypted(note) { - if (note.detail.encryption > 0) { + if (note.detail.is_protected) { $(".note-editable").addClass("encrypted"); encryptButton.hide(); decryptButton.show(); @@ -197,7 +195,7 @@ const noteEditor = (function() { noteTitleEl.focus().select(); } - await encryption.ensureEncryptionIsAvailable(currentNote.detail.encryption > 0, false); + await encryption.ensureProtectedSession(currentNote.detail.is_protected, false); noteDetailWrapperEl.show(); diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js index 81273c639..8943221b0 100644 --- a/public/javascripts/note_tree.js +++ b/public/javascripts/note_tree.js @@ -25,7 +25,7 @@ const noteTree = (function() { note.title = note.note_title; - if (note.encryption > 0) { + if (note.is_protected) { note.extraClasses = "encrypted"; } else { @@ -63,7 +63,7 @@ const noteTree = (function() { noteEditor.createNote(node, parentKey, 'after', encryption); }, "ctrl+insert": node => { - noteEditor.createNote(node, node.key, 'into', node.data.encryption); + noteEditor.createNote(node, node.key, 'into', node.data.is_protected); }, "del": node => { treeChanges.deleteNode(node); diff --git a/public/javascripts/tree_utils.js b/public/javascripts/tree_utils.js index 7121c6d67..c63dc7a0b 100644 --- a/public/javascripts/tree_utils.js +++ b/public/javascripts/tree_utils.js @@ -8,7 +8,7 @@ const treeUtils = (function() { } function getParentEncryption(node) { - return node.getParent() === null ? 0 : node.getParent().data.encryption; + return node.getParent() === null ? 0 : node.getParent().data.is_protected; } function getNodeByKey(noteId) { diff --git a/routes/api/note_history.js b/routes/api/note_history.js index cfe7477ea..e1c4315a7 100644 --- a/routes/api/note_history.js +++ b/routes/api/note_history.js @@ -7,15 +7,15 @@ const auth = require('../../services/auth'); router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { const noteId = req.params.noteId; - const encryption = req.query.encryption; + const isProtected = req.query.is_protected; let history; - if (encryption === undefined) { + if (isProtected === undefined) { history = await sql.getResults("select * from notes_history where note_id = ? order by date_modified_to desc", [noteId]); } else { - history = await sql.getResults("select * from notes_history where note_id = ? and encryption = ? order by date_modified_to desc", [noteId, encryption]); + history = await sql.getResults("select * from notes_history where note_id = ? and is_protected = ? order by date_modified_to desc", [noteId, is_protected]); } res.send(history); diff --git a/routes/api/notes.js b/routes/api/notes.js index c4ba3fedb..5b83ac47a 100644 --- a/routes/api/notes.js +++ b/routes/api/notes.js @@ -9,6 +9,7 @@ const utils = require('../../services/utils'); const notes = require('../../services/notes'); const protected_session = require('../../services/protected_session'); const data_encryption = require('../../services/data_encryption'); +const RequestContext = require('../../services/request_context'); router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { let noteId = req.params.noteId; @@ -17,12 +18,7 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { let detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); - if (detail.note_clone_id) { - noteId = detail.note_clone_id; - detail = sql.getSingleResult("select * from notes where note_id = ?", [noteId]); - } - - if (detail.encryption > 0) { + if (detail.is_protected) { const dataKey = protected_session.getDataKey(req); detail.note_title = data_encryption.decrypt(dataKey, detail.note_title); @@ -49,11 +45,11 @@ router.post('/:parentNoteId/children', async (req, res, next) => { }); router.put('/:noteId', async (req, res, next) => { - const newNote = req.body; + const note = req.body; let noteId = req.params.noteId; - const browserId = utils.browserId(req); + const reqCtx = new RequestContext(req); - await notes.updateNote(noteId, newNote, browserId); + await notes.updateNote(noteId, note, reqCtx); res.send({}); }); diff --git a/routes/api/tree.js b/routes/api/tree.js index 1ff4273d5..98fa3e286 100644 --- a/routes/api/tree.js +++ b/routes/api/tree.js @@ -13,13 +13,10 @@ const data_encryption = require('../../services/data_encryption'); router.get('/', auth.checkApiAuth, async (req, res, next) => { const notes = await sql.getResults("select " + "notes_tree.*, " - + "COALESCE(clone.note_title, notes.note_title) as note_title, " - + "notes.note_clone_id, " - + "notes.encryption, " - + "case when notes.note_clone_id is null or notes.note_clone_id = '' then 0 else 1 end as is_clone " + + "notes.note_title, " + + "notes.is_protected " + "from notes_tree " + "join notes on notes.note_id = notes_tree.note_id " - + "left join notes as clone on notes.note_clone_id = clone.note_id " + "where notes.is_deleted = 0 and notes_tree.is_deleted = 0 " + "order by note_pid, note_pos"); @@ -29,7 +26,7 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { const dataKey = protected_session.getDataKey(req); for (const note of notes) { - if (note['encryption']) { + if (note.is_protected) { note.note_title = data_encryption.decrypt(dataKey, note.note_title); } diff --git a/services/audit_category.js b/services/audit_category.js index e438a2bc6..ac6e25b4f 100644 --- a/services/audit_category.js +++ b/services/audit_category.js @@ -9,7 +9,7 @@ module.exports = { CREATE_NOTE: 'CREATE', DELETE_NOTE: 'DELETE', CHANGE_PARENT: 'PARENT', - ENCRYPTION: 'ENCRYPTION', + PROTECTED: 'PROTECTED', CHANGE_PASSWORD: 'PASSWORD', SETTINGS: 'SETTINGS', SYNC: 'SYNC' diff --git a/services/data_encryption.js b/services/data_encryption.js index 0a9a779d9..f15db23b8 100644 --- a/services/data_encryption.js +++ b/services/data_encryption.js @@ -1,6 +1,8 @@ -const protected_session = require('./protected_session'); +"use strict"; + const utils = require('./utils'); const aesjs = require('./aes'); +const sha256 = require('./sha256'); function getProtectedSessionId(req) { return req.headers['x-protected-session-id']; @@ -10,6 +12,15 @@ function getDataAes(dataKey) { return new aesjs.ModeOfOperation.ctr(dataKey, new aesjs.Counter(5)); } +function arraysIdentical(a, b) { + let i = a.length; + if (i !== b.length) return false; + while (i--) { + if (a[i] !== b[i]) return false; + } + return true; +} + function decrypt(dataKey, encryptedBase64) { if (!dataKey) { return "[protected]"; @@ -24,10 +35,42 @@ function decrypt(dataKey, encryptedBase64) { const digest = decryptedBytes.slice(0, 4); const payload = decryptedBytes.slice(4); + const hashArray = sha256Array(payload); + + const computedDigest = hashArray.slice(0, 4); + + if (!arraysIdentical(digest, computedDigest)) { + return false; + } + return aesjs.utils.utf8.fromBytes(payload); } +function encrypt(dataKey, plainText) { + if (!dataKey) { + throw new Error("No data key!"); + } + + const aes = getDataAes(dataKey); + + const payload = Array.from(aesjs.utils.utf8.toBytes(plainText)); + const digest = sha256Array(payload).slice(0, 4); + + const digestWithPayload = digest.concat(payload); + + const encryptedBytes = aes.encrypt(digestWithPayload); + + return utils.toBase64(encryptedBytes); +} + +function sha256Array(content) { + const hash = sha256.create(); + hash.update(content); + return hash.array(); +} + module.exports = { getProtectedSessionId, - decrypt + decrypt, + encrypt }; \ No newline at end of file diff --git a/services/migration.js b/services/migration.js index 60b1542f5..19b2c067d 100644 --- a/services/migration.js +++ b/services/migration.js @@ -4,7 +4,7 @@ const options = require('./options'); const fs = require('fs-extra'); const log = require('./log'); -const APP_DB_VERSION = 27; +const APP_DB_VERSION = 28; const MIGRATIONS_DIR = "./migrations"; async function migrate() { diff --git a/services/notes.js b/services/notes.js index 3ff5902dc..48d5aceb0 100644 --- a/services/notes.js +++ b/services/notes.js @@ -3,6 +3,7 @@ const options = require('./options'); const utils = require('./utils'); const notes = require('./notes'); const audit_category = require('./audit_category'); +const data_encryption = require('./data_encryption'); async function createNewNote(parentNoteId, note, browserId) { const noteId = utils.newNoteId(); @@ -46,10 +47,9 @@ async function createNewNote(parentNoteId, note, browserId) { 'note_id': noteId, 'note_title': note.note_title, 'note_text': '', - 'note_clone_id': '', 'date_created': now, 'date_modified': now, - 'encryption': note.encryption + 'is_protected': note.is_protected }); await sql.insert("notes_tree", { @@ -64,13 +64,17 @@ async function createNewNote(parentNoteId, note, browserId) { return noteId; } -async function updateNote(noteId, newNote, browserId) { - const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); +async function encryptNote(note, ctx) { + note.detail.note_title = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_title); + note.detail.note_text = data_encryption.encrypt(ctx.getDataKey(), note.detail.note_text); +} - if (origNoteDetail.note_clone_id) { - noteId = origNoteDetail.note_clone_id; +async function updateNote(noteId, newNote, ctx) { + if (newNote.detail.is_protected) { + await encryptNote(newNote, ctx); } + const origNoteDetail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]); const now = utils.nowTimestamp(); @@ -82,10 +86,10 @@ async function updateNote(noteId, newNote, browserId) { await sql.doInTransaction(async () => { if (noteHistoryId) { - await sql.execute("update notes_history set note_title = ?, note_text = ?, encryption = ?, date_modified_to = ? where note_history_id = ?", [ + await sql.execute("update notes_history set note_title = ?, note_text = ?, is_protected = ?, date_modified_to = ? where note_history_id = ?", [ newNote.detail.note_title, newNote.detail.note_text, - newNote.detail.encryption, + newNote.detail.is_protected, now, noteHistoryId ]); @@ -93,25 +97,25 @@ async function updateNote(noteId, newNote, browserId) { else { noteHistoryId = utils.randomString(16); - await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, encryption, date_modified_from, date_modified_to) " + + await sql.execute("insert into notes_history (note_history_id, note_id, note_title, note_text, is_protected, date_modified_from, date_modified_to) " + "values (?, ?, ?, ?, ?, ?, ?)", [ noteHistoryId, noteId, newNote.detail.note_title, newNote.detail.note_text, - newNote.detail.encryption, + newNote.detail.is_protected, now, now ]); } await sql.addNoteHistorySync(noteHistoryId); - await addNoteAudits(origNoteDetail, newNote.detail, browserId); + await addNoteAudits(origNoteDetail, newNote.detail, ctx.browserId); - await sql.execute("update notes set note_title = ?, note_text = ?, encryption = ?, date_modified = ? where note_id = ?", [ + await sql.execute("update notes set note_title = ?, note_text = ?, is_protected = ?, date_modified = ? where note_id = ?", [ newNote.detail.note_title, newNote.detail.note_text, - newNote.detail.encryption, + newNote.detail.is_protected, now, noteId]); @@ -147,10 +151,10 @@ async function addNoteAudits(origNote, newNote, browserId) { await sql.addAudit(audit_category.UPDATE_CONTENT, browserId, noteId); } - if (!origNote || newNote.encryption !== origNote.encryption) { - const origEncryption = origNote ? origNote.encryption : null; + if (!origNote || newNote.is_protected !== origNote.is_protected) { + const origIsProtected = origNote ? origNote.is_protected : null; - await sql.addAudit(audit_category.ENCRYPTION, browserId, noteId, origEncryption, newNote.encryption); + await sql.addAudit(audit_category.PROTECTED, browserId, noteId, origIsProtected, newNote.is_protected); } } diff --git a/services/protected_session.js b/services/protected_session.js index 6096b863a..40b9bf8d0 100644 --- a/services/protected_session.js +++ b/services/protected_session.js @@ -1,3 +1,5 @@ +"use strict"; + const utils = require('./utils'); function setDataKey(req, decryptedDataKey) { @@ -7,8 +9,12 @@ function setDataKey(req, decryptedDataKey) { return req.session.protectedSessionId; } +function getProtectedSessionId(req) { + return req.headers['x-protected-session-id']; +} + function getDataKey(req) { - const protectedSessionId = req.headers['x-protected-session-id']; + const protectedSessionId = getProtectedSessionId(req); if (protectedSessionId && req.session.protectedSessionId === protectedSessionId) { return req.session.decryptedDataKey; @@ -18,7 +24,14 @@ function getDataKey(req) { } } +function isProtectedSessionAvailable(req) { + const protectedSessionId = getProtectedSessionId(req); + + return protectedSessionId && req.session.protectedSessionId === protectedSessionId; +} + module.exports = { setDataKey, - getDataKey + getDataKey, + isProtectedSessionAvailable }; \ No newline at end of file diff --git a/services/request_context.js b/services/request_context.js new file mode 100644 index 000000000..f9c83c596 --- /dev/null +++ b/services/request_context.js @@ -0,0 +1,25 @@ +"use strict"; + +const protected_session = require('./protected_session'); + +module.exports = function(req) { + const browserId = req.headers['x-browser-id']; + + function isProtectedSessionAvailable() { + return protected_session.isProtectedSessionAvailable(req); + } + + function getDataKey() { + if (!isProtectedSessionAvailable()) { + throw new Error("Protected session is not available"); + } + + return protected_session.getDataKey(req); + } + + return { + browserId, + isProtectedSessionAvailable, + getDataKey + }; +}; \ No newline at end of file diff --git a/services/sha256.js b/services/sha256.js new file mode 100644 index 000000000..9f0e0a149 --- /dev/null +++ b/services/sha256.js @@ -0,0 +1 @@ +!function(){"use strict";function t(t,i){i?(p[0]=p[16]=p[1]=p[2]=p[3]=p[4]=p[5]=p[6]=p[7]=p[8]=p[9]=p[10]=p[11]=p[12]=p[13]=p[14]=p[15]=0,this.blocks=p):this.blocks=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],t?(this.h0=3238371032,this.h1=914150663,this.h2=812702999,this.h3=4144912697,this.h4=4290775857,this.h5=1750603025,this.h6=1694076839,this.h7=3204075428):(this.h0=1779033703,this.h1=3144134277,this.h2=1013904242,this.h3=2773480762,this.h4=1359893119,this.h5=2600822924,this.h6=528734635,this.h7=1541459225),this.block=this.start=this.bytes=0,this.finalized=this.hashed=!1,this.first=!0,this.is224=t}function i(i,r,e){var n="string"!=typeof i;if(n){if(null===i||void 0===i)throw h;i.constructor===s.ArrayBuffer&&(i=new Uint8Array(i))}var o=i.length;if(n){if("number"!=typeof o||!Array.isArray(i)&&(!a||!ArrayBuffer.isView(i)))throw h}else{for(var f,u=[],o=i.length,c=0,y=0;o>y;++y)f=i.charCodeAt(y),128>f?u[c++]=f:2048>f?(u[c++]=192|f>>6,u[c++]=128|63&f):55296>f||f>=57344?(u[c++]=224|f>>12,u[c++]=128|f>>6&63,u[c++]=128|63&f):(f=65536+((1023&f)<<10|1023&i.charCodeAt(++y)),u[c++]=240|f>>18,u[c++]=128|f>>12&63,u[c++]=128|f>>6&63,u[c++]=128|63&f);i=u}i.length>64&&(i=new t(r,!0).update(i).array());for(var p=[],l=[],y=0;64>y;++y){var d=i[y]||0;p[y]=92^d,l[y]=54^d}t.call(this,r,e),this.update(l),this.oKeyPad=p,this.inner=!0,this.sharedMemory=e}var h="input is invalid type",s="object"==typeof window?window:{},r=!s.JS_SHA256_NO_NODE_JS&&"object"==typeof process&&process.versions&&process.versions.node;r&&(s=global);var e=!s.JS_SHA256_NO_COMMON_JS&&"object"==typeof module&&module.exports,n="function"==typeof define&&define.amd,a="undefined"!=typeof ArrayBuffer,o="0123456789abcdef".split(""),f=[-2147483648,8388608,32768,128],u=[24,16,8,0],c=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],y=["hex","array","digest","arrayBuffer"],p=[];(s.JS_SHA256_NO_NODE_JS||!Array.isArray)&&(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var l=function(i,h){return function(s){return new t(h,!0).update(s)[i]()}},d=function(i){var h=l("hex",i);r&&(h=v(h,i)),h.create=function(){return new t(i)},h.update=function(t){return h.create().update(t)};for(var s=0;so;){if(this.hashed&&(this.hashed=!1,f[0]=this.block,f[16]=f[1]=f[2]=f[3]=f[4]=f[5]=f[6]=f[7]=f[8]=f[9]=f[10]=f[11]=f[12]=f[13]=f[14]=f[15]=0),i)for(n=this.start;r>o&&64>n;++o)f[n>>2]|=t[o]<o&&64>n;++o)e=t.charCodeAt(o),128>e?f[n>>2]|=e<e?(f[n>>2]|=(192|e>>6)<>2]|=(128|63&e)<e||e>=57344?(f[n>>2]|=(224|e>>12)<>2]|=(128|e>>6&63)<>2]|=(128|63&e)<>2]|=(240|e>>18)<>2]|=(128|e>>12&63)<>2]|=(128|e>>6&63)<>2]|=(128|63&e)<=64?(this.block=f[16],this.start=n-64,this.hash(),this.hashed=!0):this.start=n}return this}},t.prototype.finalize=function(){if(!this.finalized){this.finalized=!0;var t=this.blocks,i=this.lastByteIndex;t[16]=this.block,t[i>>2]|=f[3&i],this.block=t[16],i>=56&&(this.hashed||this.hash(),t[0]=this.block,t[16]=t[1]=t[2]=t[3]=t[4]=t[5]=t[6]=t[7]=t[8]=t[9]=t[10]=t[11]=t[12]=t[13]=t[14]=t[15]=0),t[15]=this.bytes<<3,this.hash()}},t.prototype.hash=function(){var t,i,h,s,r,e,n,a,o,f,u,y=this.h0,p=this.h1,l=this.h2,d=this.h3,v=this.h4,A=this.h5,w=this.h6,b=this.h7,g=this.blocks;for(t=16;64>t;++t)r=g[t-15],i=(r>>>7|r<<25)^(r>>>18|r<<14)^r>>>3,r=g[t-2],h=(r>>>17|r<<15)^(r>>>19|r<<13)^r>>>10,g[t]=g[t-16]+i+g[t-7]+h<<0;for(u=p&l,t=0;64>t;t+=4)this.first?(this.is224?(a=300032,r=g[0]-1413257819,b=r-150054599<<0,d=r+24177077<<0):(a=704751109,r=g[0]-210244248,b=r-1521486534<<0,d=r+143694565<<0),this.first=!1):(i=(y>>>2|y<<30)^(y>>>13|y<<19)^(y>>>22|y<<10),h=(v>>>6|v<<26)^(v>>>11|v<<21)^(v>>>25|v<<7),a=y&p,s=a^y&l^u,n=v&A^~v&w,r=b+h+n+c[t]+g[t],e=i+s,b=d+r<<0,d=r+e<<0),i=(d>>>2|d<<30)^(d>>>13|d<<19)^(d>>>22|d<<10),h=(b>>>6|b<<26)^(b>>>11|b<<21)^(b>>>25|b<<7),o=d&y,s=o^d&p^a,n=b&v^~b&A,r=w+h+n+c[t+1]+g[t+1],e=i+s,w=l+r<<0,l=r+e<<0,i=(l>>>2|l<<30)^(l>>>13|l<<19)^(l>>>22|l<<10),h=(w>>>6|w<<26)^(w>>>11|w<<21)^(w>>>25|w<<7),f=l&d,s=f^l&y^o,n=w&b^~w&v,r=A+h+n+c[t+2]+g[t+2],e=i+s,A=p+r<<0,p=r+e<<0,i=(p>>>2|p<<30)^(p>>>13|p<<19)^(p>>>22|p<<10),h=(A>>>6|A<<26)^(A>>>11|A<<21)^(A>>>25|A<<7),u=p&l,s=u^p&d^f,n=A&w^~A&b,r=v+h+n+c[t+3]+g[t+3],e=i+s,v=y+r<<0,y=r+e<<0;this.h0=this.h0+y<<0,this.h1=this.h1+p<<0,this.h2=this.h2+l<<0,this.h3=this.h3+d<<0,this.h4=this.h4+v<<0,this.h5=this.h5+A<<0,this.h6=this.h6+w<<0,this.h7=this.h7+b<<0},t.prototype.hex=function(){this.finalize();var t=this.h0,i=this.h1,h=this.h2,s=this.h3,r=this.h4,e=this.h5,n=this.h6,a=this.h7,f=o[t>>28&15]+o[t>>24&15]+o[t>>20&15]+o[t>>16&15]+o[t>>12&15]+o[t>>8&15]+o[t>>4&15]+o[15&t]+o[i>>28&15]+o[i>>24&15]+o[i>>20&15]+o[i>>16&15]+o[i>>12&15]+o[i>>8&15]+o[i>>4&15]+o[15&i]+o[h>>28&15]+o[h>>24&15]+o[h>>20&15]+o[h>>16&15]+o[h>>12&15]+o[h>>8&15]+o[h>>4&15]+o[15&h]+o[s>>28&15]+o[s>>24&15]+o[s>>20&15]+o[s>>16&15]+o[s>>12&15]+o[s>>8&15]+o[s>>4&15]+o[15&s]+o[r>>28&15]+o[r>>24&15]+o[r>>20&15]+o[r>>16&15]+o[r>>12&15]+o[r>>8&15]+o[r>>4&15]+o[15&r]+o[e>>28&15]+o[e>>24&15]+o[e>>20&15]+o[e>>16&15]+o[e>>12&15]+o[e>>8&15]+o[e>>4&15]+o[15&e]+o[n>>28&15]+o[n>>24&15]+o[n>>20&15]+o[n>>16&15]+o[n>>12&15]+o[n>>8&15]+o[n>>4&15]+o[15&n];return this.is224||(f+=o[a>>28&15]+o[a>>24&15]+o[a>>20&15]+o[a>>16&15]+o[a>>12&15]+o[a>>8&15]+o[a>>4&15]+o[15&a]),f},t.prototype.toString=t.prototype.hex,t.prototype.digest=function(){this.finalize();var t=this.h0,i=this.h1,h=this.h2,s=this.h3,r=this.h4,e=this.h5,n=this.h6,a=this.h7,o=[t>>24&255,t>>16&255,t>>8&255,255&t,i>>24&255,i>>16&255,i>>8&255,255&i,h>>24&255,h>>16&255,h>>8&255,255&h,s>>24&255,s>>16&255,s>>8&255,255&s,r>>24&255,r>>16&255,r>>8&255,255&r,e>>24&255,e>>16&255,e>>8&255,255&e,n>>24&255,n>>16&255,n>>8&255,255&n];return this.is224||o.push(a>>24&255,a>>16&255,a>>8&255,255&a),o},t.prototype.array=t.prototype.digest,t.prototype.arrayBuffer=function(){this.finalize();var t=new ArrayBuffer(this.is224?28:32),i=new DataView(t);return i.setUint32(0,this.h0),i.setUint32(4,this.h1),i.setUint32(8,this.h2),i.setUint32(12,this.h3),i.setUint32(16,this.h4),i.setUint32(20,this.h5),i.setUint32(24,this.h6),this.is224||i.setUint32(28,this.h7),t},i.prototype=new t,i.prototype.finalize=function(){if(t.prototype.finalize.call(this),this.inner){this.inner=!1;var i=this.array();t.call(this,this.is224,this.sharedMemory),this.update(this.oKeyPad),this.update(i),t.prototype.finalize.call(this)}};var b=d();b.sha256=b,b.sha224=d(!0),b.sha256.hmac=w(),b.sha224.hmac=w(!0),e?module.exports=b:(s.sha256=b.sha256,s.sha224=b.sha224,n&&define(function(){return b}))}(); \ No newline at end of file diff --git a/services/sync.js b/services/sync.js index 64e136eea..cc22a0796 100644 --- a/services/sync.js +++ b/services/sync.js @@ -239,6 +239,10 @@ async function syncRequest(syncContext, method, uri, body) { if (isSyncSetup) { log.info("Setting up sync to " + SYNC_SERVER + " with timeout " + SYNC_TIMEOUT); + if (SYNC_PROXY) { + log.info("Sync proxy: " + SYNC_PROXY); + } + setInterval(sync, 60000); // kickoff initial sync immediately diff --git a/views/index.ejs b/views/index.ejs index c03f062d5..d2529ea1f 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -68,7 +68,7 @@
- -