From 3184ad419ea9aa9be0adf1daaf8a0bce7f65806d Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 20 May 2021 13:54:20 +0200 Subject: [PATCH] Ability to submit form answers --- www/common/common-hash.js | 6 + www/common/cryptpad-common.js | 11 ++ www/form/inner.js | 207 ++++++++++++++++++++++------------ www/form/main.js | 26 +++++ 4 files changed, 175 insertions(+), 75 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index cf3ca76a4..c59f5babe 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -34,6 +34,12 @@ var factory = function (Util, Crypto, Keys, Nacl) { var keyPair = Nacl.sign.keyPair.fromSecretKey(privateKey); return Nacl.util.encodeBase64(keyPair.publicKey); }; + Hash.getCurvePublicFromPrivate = function (curvePrivateSafeStr) { + var curvePrivateStr = Crypto.b64AddSlashes(curvePrivateSafeStr); + var privateKey = Nacl.util.decodeBase64(curvePrivateStr); + var keyPair = Nacl.box.keyPair.fromSecretKey(privateKey); + return Nacl.util.encodeBase64(keyPair.publicKey); + }; var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) { var version = secret.version; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e9635d5b5..ba0624a08 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -100,6 +100,17 @@ define([ cb(keys); }); }; + common.getFormKeys = function (cb) { + postMessage("GET", { + key: ['curvePrivate'], + }, function (obj) { + if (obj.error) { return void cb(); } + cb({ + curvePrivate: obj, + curvePublic: Hash.getCurvePublicFromPrivate(obj) + }); + }); + }; common.makeNetwork = function (cb) { require([ diff --git a/www/form/inner.js b/www/form/inner.js index d9393faf1..f50583e37 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -59,6 +59,13 @@ define([ Messages.form_duplicates = "Duplicate entries have been removed"; + Messages.form_reset = "Reset"; + Messages.form_sent = "Sent"; + + // XXX to update our own answers, we need to store the server hash of the message + // and we'll be able to use getHistoryRange to fetch this message when we come back + + var makeFormSettings = function (framework) { // XXX // Button to set results as public @@ -66,6 +73,59 @@ define([ // Button to clear all answers? }; + var editOptions = function (v, cb) { + var add = h('button.btn.btn-secondary', [ + h('i.fa.fa-plus'), + h('span', Messages.tag_add) + ]); + + // Show existing options + var getOption = function (val) { + var input = h('input', {value:val}); + var del = h('button.btn.btn-danger', h('i.fa.fa-times')); + var el = h('div.cp-form-edit-block-input', [ input, del ]); + $(del).click(function () { $(el).remove(); }); + return el; + }; + var inputs = v.map(getOption); + inputs.push(add); + var container = h('div.cp-form-edit-block', inputs); + + // Add option + var $add = $(add).click(function () { + $add.before(getOption(Messages.form_newOption)); + }); + + // Cancel changes + var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); + $(cancelBlock).click(function () { cb(); }); + + // Save changes + var saveBlock = h('button.btn.btn-primary', [ + h('i.fa.fa-floppy-o'), + h('span', Messages.settings_save) + ]); + $(saveBlock).click(function () { + $(saveBlock).attr('disabled', 'disabled'); + var values = []; + var duplicates = false; + $(container).find('input').each(function (i, el) { + var val = $(el).val().trim(); + if (values.indexOf(val) === -1) { values.push(val); } + else { duplicates = true; } + }); + if (duplicates) { + UI.warn(Messages.form_duplicates); + } + cb({values: values}); + }); + + return [ + container, + h('div', [cancelBlock, saveBlock]) + ]; + }; + var TYPES = { input: { get: function () { @@ -75,6 +135,7 @@ define([ tag: tag, getValue: function () { return $tag.val(); }, //setValue: function (val) { $tag.val(val); } + reset: function () { $tag.val(''); } }; }, icon: h('i.fa.fa-font') @@ -102,59 +163,10 @@ define([ }); return res; }, + reset: function () { $(tag).find('input').removeAttr('checked'); }, edit: function (cb) { var v = opts.values.slice(); - - var add = h('button.btn.btn-secondary', [ - h('i.fa.fa-plus'), - h('span', Messages.tag_add) - ]); - - // Show existing options - var getOption = function (val) { - var input = h('input', {value:val}); - var del = h('button.btn.btn-danger', h('i.fa.fa-times')); - var el = h('div.cp-form-edit-block-input', [ input, del ]); - $(del).click(function () { $(el).remove(); }); - return el; - }; - var inputs = v.map(getOption); - inputs.push(add); - var container = h('div.cp-form-edit-block', inputs); - - // Add option - var $add = $(add).click(function () { - $add.before(getOption(Messages.form_newOption)); - }); - - // Cancel changes - var cancelBlock = h('button.btn.btn-secondary', Messages.cancel); - $(cancelBlock).click(function () { cb(); }); - - // Save changes - var saveBlock = h('button.btn.btn-primary', [ - h('i.fa.fa-floppy-o'), - h('span', Messages.settings_save) - ]); - $(saveBlock).click(function () { - $(saveBlock).attr('disabled', 'disabled'); - var values = []; - var duplicates = false; - $(container).find('input').each(function (i, el) { - var val = $(el).val().trim(); - if (values.indexOf(val) === -1) { values.push(val); } - else { duplicates = true; } - }); - if (duplicates) { - UI.warn(Messages.form_duplicates); - } - cb({values: values}); - }); - - return [ - container, - h('div', [cancelBlock, saveBlock]) - ]; + return editOptions(v, cb); } //setValue: function (val) {} }; @@ -164,23 +176,60 @@ define([ } }; - var renderForm = function (content, editable) { + var makeFormControls = function (framework, content) { + var send = h('button.btn.btn-primary', Messages.poll_commit); + var reset = h('button.btn.btn-danger-alt', Messages.form_reset); + $(reset).click(function () { + if (!Array.isArray(APP.formBlocks)) { return; } + APP.formBlocks.forEach(function (data) { + if (typeof(data.reset) === "function") { data.reset(); } + }); + }); + var $send = $(send).click(function () { + $send.attr('disabled', 'disabled'); + if (!Array.isArray(APP.formBlocks)) { return; } + var results = {}; + APP.formBlocks.forEach(function (data) { + results[data.uid] = data.getValue(); + }); + var sframeChan = framework._.sfCommon.getSframeChannel(); + sframeChan.query('Q_FORM_SUBMIT', { + mailbox: content.answers, + results: results + }, function (err, data) { + console.error(data); + if (err || (data && data.error)) { + console.error(err || data.error); + return void UI.warn(Messages.error); + } + UI.alert(Messages.form_sent); + }); + }); + return h('div.cp-form-send-container', [send, reset]); }; var updateForm = function (framework, content, editable) { var $container = $('div.cp-form-creator-content'); var form = content.form; + APP.formBlocks = []; + // XXX order array later var elements = Object.keys(form).map(function (uid) { var block = form[uid]; var type = block.type; var model = TYPES[type]; if (!model) { return; } + var data = model.get(block.opts); + data.uid = uid; + var q = h('div.cp-form-block-question', block.q || Messages.form_default); var edit, editContainer; + + APP.formBlocks.push(data); + if (editable) { // Question @@ -231,6 +280,7 @@ define([ } $(editContainer).empty(); block.opts = newOpts; + framework.localChange(); var $oldTag = $(data.tag); framework._.cpNfInner.chainpad.onSettle(function () { $edit.show(); @@ -257,34 +307,43 @@ define([ }); $container.empty().append(elements); + $container.append(makeFormControls(framework, content)); }; var andThen = function (framework) { framework.start(); var content = {}; - var $container = $('#cp-app-form-container'); + var sframeChan = framework._.sfCommon.getSframeChannel(); + var metadataMgr = framework._.cpNfInner.metadataMgr; + + var priv = metadataMgr.getPrivateData(); + APP.isEditor = Boolean(priv.form_public); var makeFormCreator = function () { - var controls = Object.keys(TYPES).map(function (type) { - var btn = h('button.btn', [ - TYPES[type].icon.cloneNode(), - h('span', Messages['form_type_'+type]) - ]); - $(btn).click(function () { - var uid = Util.uid(); - content.form[uid] = { - //q: Messages.form_default, - //opts: opts - type: type, - }; - framework.localChange(); - updateForm(framework, content, true); + var controlContainer; + if (APP.isEditor) { + var controls = Object.keys(TYPES).map(function (type) { + + var btn = h('button.btn', [ + TYPES[type].icon.cloneNode(), + h('span', Messages['form_type_'+type]) + ]); + $(btn).click(function () { + var uid = Util.uid(); + content.form[uid] = { + //q: Messages.form_default, + //opts: opts + type: type, + }; + framework.localChange(); + updateForm(framework, content, true); + }); + return btn; }); - return btn; - }); - var controlContainer = h('div.cp-form-creator-control', controls); + controlContainer = h('div.cp-form-creator-control', controls); + } var contentContainer = h('div.cp-form-creator-content'); var div = h('div.cp-form-creator-container', [ @@ -294,12 +353,9 @@ define([ return div; }; + var $container = $('#cp-app-form-container'); $container.append(makeFormCreator()); - - var sframeChan = framework._.sfCommon.getSframeChannel(); - var metadataMgr = framework._.cpNfInner.metadataMgr; - var priv = metadataMgr.getPrivateData(); - APP.isEditor = Boolean(priv.form_public); + if (!APP.isEditor) { makeFormControls(); } framework.onReady(function (isNew) { var priv = metadataMgr.getPrivateData(); @@ -340,6 +396,7 @@ define([ framework.onContentUpdate(function (newContent) { console.log(newContent); content = newContent; + updateForm(framework, content, APP.isEditor); }); framework.setContentGetter(function () { diff --git a/www/form/main.js b/www/form/main.js index be47466e3..d9eb3e660 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -63,6 +63,7 @@ define([ config.onReady = function () { cb(); // XXX + network.disconnect(); }; config.onMessage = function () { // XXX @@ -70,6 +71,31 @@ define([ CPNetflux.start(config); }); }); + sframeChan.on("Q_FORM_SUBMIT", function (data, cb) { + var box = data.mailbox; + var myKeys; + nThen(function (w) { + Cryptpad.getFormKeys(w(function (keys) { + myKeys = keys; + })); + }).nThen(function (w) { + + var keys = Utils.secret && Utils.secret.keys; + myKeys.signingKey = keys.secondarySignKey; + + var crypto = Utils.Crypto.Mailbox.createEncryptor(myKeys); + var text = JSON.stringify(data.results); + var ciphertext = crypto.encrypt(text, box.publicKey); + + var hash = ciphertext.slice(0,64); // XXX use this to recover our previous answers + Cryptpad.anonRpcMsg("WRITE_PRIVATE_MESSAGE", [ + box.channel, + ciphertext + ], function (err, response) { + cb({error: err, response: response, hash: hash}); + }); + }); + }); sframeChan.on('EV_FORM_MAILBOX', function (data) { var curvePair = Nacl.box.keyPair(); publicKey = Nacl.util.encodeBase64(curvePair.publicKey);