From 0a0b018df0fa25c71eb912539d796afcab5b2757 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 8 Nov 2024 11:35:12 +0100 Subject: [PATCH 01/22] Form answers download in Drive zip - WIP --- www/common/make-backup.js | 4 + www/common/sframe-common-outer.js | 158 ++++++++++++++++++++++++++++++ www/form/main.js | 6 ++ 3 files changed, 168 insertions(+) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 4e1d2b205..405427020 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -239,6 +239,10 @@ define([ transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } + ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", JSON.parse(val)["answers"], function (err, obj) { + var answers = obj && obj.results; + console.log("ANSWERS", answers) + }); var fileName = getUnique(sanitize(rawName), res.ext, existingNames); existingNames.push(fileName.toLowerCase()); zip.file(fileName, res.data, opts); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b4031d155..3f6d9aeae 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -9,7 +9,10 @@ define([ '/common/requireconfig.js', '/customize/messages.js', 'jquery', + '/components/tweetnacl/nacl-fast.min.js', + ], function (nThen, ApiConfig, RequireConfig, Messages, $) { + var Nacl = window.nacl; var common = {}; var embeddableApps = [ @@ -2429,6 +2432,161 @@ define([ Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive)); }); + sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { + var cb = Utils.Util.once(_cb); + var myKeys = {}; + var myFormKeys; + var accessKeys; + var CPNetflux, Pinpad; + var network; + var noDriveAnswered = false; + nThen(function (w) { + require([ + 'chainpad-netflux', + '/common/pinpad.js', + ], w(function (_CPNetflux, _Pinpad) { + CPNetflux = _CPNetflux; + Pinpad = _Pinpad; + })); + var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; + Cryptpad.getAccessKeys(w(function (_keys) { + if (!Array.isArray(_keys)) { return; } + accessKeys = _keys; + + _keys.some(function (_k) { + if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { + myKeys = _k; + return true; + } + }); + })); + Cryptpad.getFormKeys(w(function (keys) { + if (!keys.curvePublic && !keys.formSeed) { + // No drive mode + var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); + noDriveAnswered = answered.indexOf(data.channel) !== -1; + } + myFormKeys = keys; + })); + Cryptpad.makeNetwork(w(function (err, nw) { + network = nw; + })); + Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { + if (md && md.deleteLines) { deleteLines = true; } + })); + }).nThen(function () { + if (!network) { return void cb({error: "E_CONNECT"}); } + var getAnonymousKeys = function (formSeed, channel) { + var array = Nacl.util.decodeBase64(formSeed + channel); + var hash = Nacl.hash(array); + var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); + var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); + return { + curvePrivate: secretKey, + curvePublic: publicKey, + }; + }; + if (myFormKeys.formSeed) { + myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); + } + + var keys = Utils.secret && Utils.secret.keys; + + var formData = Utils.Hash.getFormData(Utils.secret); + console.log("SECRET", Utils.secret) + console.log("FORMDATA", formData) + privateKey = formData.form_private; + publicKey = formData.form_public; + + var curvePrivate = privateKey || data.privateKey; + if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } + var crypto = Utils.Crypto.Mailbox.createEncryptor({ + curvePrivate: curvePrivate, + curvePublic: publicKey || data.publicKey, + validateKey: data.validateKey + }); + + console.log("PRIVATE", curvePrivate) + console.log("PUBLIC", publicKey, data.publicKey) + console.log("VALIDATE", data.validateKey) + + var config = { + network: network, + channel: data.channel, + noChainPad: true, + validateKey: keys.secondaryValidateKey, + owners: [myKeys.edPublic], + crypto: crypto, + metadata: { + deleteLines: true + } + //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers + }; + var results = {}; + config.onError = function (info) { + cb({ error: info.type }); + }; + config.onRejected = function (data, cb) { + if (!Array.isArray(data) || !data.length || data[0].length !== 16) { + return void cb(true); + } + if (!Array.isArray(accessKeys)) { return void cb(true); } + network.historyKeeper = data[0]; + nThen(function (waitFor) { + accessKeys.forEach(function (obj) { + Pinpad.create(network, obj, waitFor(function (e) { + if (e) { console.error(e); } + })); + }); + }).nThen(function () { + cb(); + }); + }; + config.onReady = function () { + var myKey; + // If we have submitted an anonymous answer, retrieve it + if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { + myKey = myFormKeys.curvePublic; + } + cb({ + noDriveAnswered: noDriveAnswered, + myKey: myKey, + results: results + }); + network.disconnect(); + }; + config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { + var parsed = Utils.Util.tryParse(msg); + if (!parsed) { return; } + var uid = parsed._uid || '000'; + + // If we have a "non-anonymous" answer, it may be the edition of a + // previous anonymous answer. Check if a previous anonymous answer exists + // with the same uid and delete it. + if (parsed._proof) { + var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); + var theirAnonKey = parsed._proof.key; + if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { + delete results[theirAnonKey][uid]; + } + } + + parsed._time = cfg && cfg.time; + if (deleteLines) { parsed._hash = hash; } + + if (data.cantEdit && results[senderCurve] + && results[senderCurve][uid]) { return; } + results[senderCurve] = results[senderCurve] || {}; + results[senderCurve][uid] = { + msg: parsed, + hash: hash, + time: cfg && cfg.time + }; + }; + CPNetflux.start(config); + }); + }); + sframeChan.ready(); Utils.Feedback.reportAppUsage(); diff --git a/www/form/main.js b/www/form/main.js index 05a4536dd..5efc81aab 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -36,6 +36,8 @@ define([ } var formData = Utils.Hash.getFormData(Utils.secret); + console.log("SECRET", Utils.secret) + console.log("FORMDATA", formData) if (!formData) { return; } var validateKey = keys.secondaryValidateKey; @@ -187,6 +189,10 @@ define([ curvePublic: publicKey || data.publicKey, validateKey: data.validateKey }); + + console.log("PRIVATE", curvePrivate) + console.log("PUBLIC", publicKey, data.publicKey) + console.log("VALIDATE", data.validateKey) var config = { network: network, channel: data.channel, From af9d7646f78488fee935a7d57ddb85d6edda311b Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 11 Nov 2024 23:10:51 +0100 Subject: [PATCH 02/22] Moved FORM_FETCH_ANSWERS to sframe-common-outer.js --- www/common/make-backup.js | 21 +++++++++++--- www/common/sframe-common-outer.js | 47 +++++++++++++++++++++++-------- www/form/inner.js | 5 +++- www/form/main.js | 3 ++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 405427020..fe9ac110f 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -239,10 +239,23 @@ define([ transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } - ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", JSON.parse(val)["answers"], function (err, obj) { - var answers = obj && obj.results; - console.log("ANSWERS", answers) - }); + var data = JSON.parse(val) + var _answers = data["answers"] + if (data.form) { + _answers['href'] = parsed.hash + _answers['password'] = fData.password + _answers['drive'] = true + var answers + ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { + answers = obj && obj.results; + console.log("ANSWERS", answers) + }); + // var opts = { + // binary: true, + // }; + // zip.file(fileName, answers, opts); + } + var fileName = getUnique(sanitize(rawName), res.ext, existingNames); existingNames.push(fileName.toLowerCase()); zip.file(fileName, res.data, opts); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 3f6d9aeae..54ad6b92c 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2433,6 +2433,7 @@ define([ }); sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { + var formHref = data.href var cb = Utils.Util.once(_cb); var myKeys = {}; var myFormKeys; @@ -2489,14 +2490,18 @@ define([ if (myFormKeys.formSeed) { myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); } - - var keys = Utils.secret && Utils.secret.keys; - - var formData = Utils.Hash.getFormData(Utils.secret); - console.log("SECRET", Utils.secret) - console.log("FORMDATA", formData) - privateKey = formData.form_private; - publicKey = formData.form_public; + var keys; + var privateKey, publicKey; + if (data.drive) { + var secret = Utils.Hash.getSecrets('form', formHref, data.password); + keys = secret && secret.keys; + var formData = Utils.Hash.getFormData(secret); + privateKey = formData.form_private; + publicKey = formData.form_public; + + } else { + keys = Utils.secret && Utils.secret.keys; + } var curvePrivate = privateKey || data.privateKey; if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } @@ -2506,10 +2511,6 @@ define([ validateKey: data.validateKey }); - console.log("PRIVATE", curvePrivate) - console.log("PUBLIC", publicKey, data.publicKey) - console.log("VALIDATE", data.validateKey) - var config = { network: network, channel: data.channel, @@ -2563,6 +2564,28 @@ define([ // If we have a "non-anonymous" answer, it may be the edition of a // previous anonymous answer. Check if a previous anonymous answer exists // with the same uid and delete it. + var u8_slice = function (A, start, end) { + return new Uint8Array(Array.prototype.slice.call(A, start, end)); + }; + var checkAnonProof = function (proofObj, channel, curvePrivate) { + var pub = proofObj.key; + var proofTxt = proofObj.proof; + try { + var u8_bundle = Nacl.util.decodeBase64(proofTxt); + var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); + var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); + var u8_plain = Nacl.box.open( + u8_cipher, + u8_nonce, + Nacl.util.decodeBase64(pub), + Nacl.util.decodeBase64(curvePrivate) + ); + return channel === Nacl.util.encodeUTF8(u8_plain); + } catch (e) { + console.error(e); + return false; + } + }; if (parsed._proof) { var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); var theirAnonKey = parsed._proof.key; diff --git a/www/form/inner.js b/www/form/inner.js index 3428d98b6..b14c932a3 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -50,6 +50,7 @@ define([ 'css!/lib/datepicker/flatpickr.min.css', 'css!/components/components-font-awesome/css/font-awesome.min.css', 'less!/form/app-form.less', + '/common/sframe-common-outer.js' ], function ( $, Sortify, @@ -78,7 +79,8 @@ define([ DatePicker, Share, Access, Properties, Flatpickr, - Sortable + Sortable, + SFCommonO ) { @@ -5375,6 +5377,7 @@ define([ } var getMyAnswers = APP.getMyAnswers = function () { + var sframeChan = framework._.sfCommon.getSframeChannel(); sframeChan.query("Q_FETCH_MY_ANSWERS", { channel: content.answers.channel, validateKey: content.answers.validateKey, diff --git a/www/form/main.js b/www/form/main.js index 5efc81aab..6d8a02bf7 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -129,6 +129,9 @@ define([ return false; } }; + var beep = function () { + + } var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { From cd832156a02ec6fdb3b9e34f687ef0f20c564c8b Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 10:25:06 +0100 Subject: [PATCH 03/22] Refactoring download function --- www/common/make-backup.js | 16 ++++++++++------ www/form/main.js | 3 --- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index fe9ac110f..41894479e 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -240,20 +240,24 @@ define([ if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } var data = JSON.parse(val) - var _answers = data["answers"] + if (data.form) { + var _answers = data["answers"] _answers['href'] = parsed.hash _answers['password'] = fData.password _answers['drive'] = true - var answers + var answers; ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; console.log("ANSWERS", answers) }); - // var opts = { - // binary: true, - // }; - // zip.file(fileName, answers, opts); + var opts = { + binary: true, + }; + var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); + existingNames.push(fileName.toLowerCase()); + var content = new Blob([answers], { type : "application/json" }); + zip.file(fileName, content, opts); } var fileName = getUnique(sanitize(rawName), res.ext, existingNames); diff --git a/www/form/main.js b/www/form/main.js index 6d8a02bf7..5efc81aab 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -129,9 +129,6 @@ define([ return false; } }; - var beep = function () { - - } var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { From 96e6e8dc64750b61fe3d8e44526fbda11c1f397b Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 14:33:53 +0100 Subject: [PATCH 04/22] Made command compatible with both Drive + Form apps, cleanup --- www/common/make-backup.js | 37 +++++-- www/common/sframe-common-outer.js | 13 +-- www/form/main.js | 164 ------------------------------ 3 files changed, 35 insertions(+), 179 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 41894479e..f0ae62da0 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -15,8 +15,9 @@ define([ '/components/nthen/index.js', '/components/saferphore/index.js', '/components/jszip/dist/jszip.min.js', + '/form/export.js', ], function ($, FileCrypto, Hash, Util, UI, h, Feedback, - Cache, Messages, nThen, Saferphore, JsZip) { + Cache, Messages, nThen, Saferphore, JsZip, Exporter) { var saveAs = window.saveAs; var sanitize = function (str) { @@ -249,15 +250,33 @@ define([ var answers; ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; - console.log("ANSWERS", answers) + var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); + existingNames.push(fileName.toLowerCase()); + var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}} + var getFullOrder = function (content) { + var order = content.order.slice(); + var getSections = function (content) { + var uids = Object.keys(content.form).filter(function (uid) { + return content.form[uid].type === 'section'; + }); + return uids; + }; + getSections(content).forEach(function (uid) { + var block = content.form[uid]; + if (!block.opts || !Array.isArray(block.opts.questions)) { return; } + var idx = order.indexOf(uid); + if (idx === -1) { return; } + idx++; + block.opts.questions.forEach(function (el, i) { + order.splice(idx+i, 0, el); + }); + }); + return order; + }; + var arr = Exporter.results(data, answers, types, getFullOrder(data), "json"); + var content = new Blob([arr], { type : "application/json" }); + zip.file(fileName, content, opts); }); - var opts = { - binary: true, - }; - var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); - existingNames.push(fileName.toLowerCase()); - var content = new Blob([answers], { type : "application/json" }); - zip.file(fileName, content, opts); } var fileName = getUnique(sanitize(rawName), res.ext, existingNames); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 54ad6b92c..e7c6ab93e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2432,6 +2432,7 @@ define([ Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive)); }); + var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { var formHref = data.href var cb = Utils.Util.once(_cb); @@ -2477,7 +2478,7 @@ define([ })); }).nThen(function () { if (!network) { return void cb({error: "E_CONNECT"}); } - var getAnonymousKeys = function (formSeed, channel) { + var getAnonymousKeys = function (formSeed, channel) { var array = Nacl.util.decodeBase64(formSeed + channel); var hash = Nacl.hash(array); var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); @@ -2492,17 +2493,17 @@ define([ } var keys; var privateKey, publicKey; + var formData; if (data.drive) { var secret = Utils.Hash.getSecrets('form', formHref, data.password); keys = secret && secret.keys; - var formData = Utils.Hash.getFormData(secret); - privateKey = formData.form_private; - publicKey = formData.form_public; - + formData = Utils.Hash.getFormData(secret); } else { + formData = Utils.Hash.getFormData(Utils.secret); keys = Utils.secret && Utils.secret.keys; } - + privateKey = formData.form_private; + publicKey = formData.form_public; var curvePrivate = privateKey || data.privateKey; if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } var crypto = Utils.Crypto.Mailbox.createEncryptor({ diff --git a/www/form/main.js b/www/form/main.js index 5efc81aab..aa6f613a0 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -36,8 +36,6 @@ define([ } var formData = Utils.Hash.getFormData(Utils.secret); - console.log("SECRET", Utils.secret) - console.log("FORMDATA", formData) if (!formData) { return; } var validateKey = keys.secondaryValidateKey; @@ -77,9 +75,6 @@ define([ curvePublic: publicKey, }; }; - var u8_slice = function (A, start, end) { - return new Uint8Array(Array.prototype.slice.call(A, start, end)); - }; var u8_concat = function (A) { var length = 0; A.forEach(function (a) { length += a.length; }); @@ -110,165 +105,6 @@ define([ proof: Nacl.util.encodeBase64(u8_bundle) }; }; - var checkAnonProof = function (proofObj, channel, curvePrivate) { - var pub = proofObj.key; - var proofTxt = proofObj.proof; - try { - var u8_bundle = Nacl.util.decodeBase64(proofTxt); - var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); - var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); - var u8_plain = Nacl.box.open( - u8_cipher, - u8_nonce, - Nacl.util.decodeBase64(pub), - Nacl.util.decodeBase64(curvePrivate) - ); - return channel === Nacl.util.encodeUTF8(u8_plain); - } catch (e) { - console.error(e); - return false; - } - }; - - var deleteLines = false; // "false" to support old forms - sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var cb = Utils.Util.once(_cb); - var myKeys = {}; - var myFormKeys; - var accessKeys; - var CPNetflux, Pinpad; - var network; - var noDriveAnswered = false; - nThen(function (w) { - require([ - 'chainpad-netflux', - '/common/pinpad.js', - ], w(function (_CPNetflux, _Pinpad) { - CPNetflux = _CPNetflux; - Pinpad = _Pinpad; - })); - var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; - Cryptpad.getAccessKeys(w(function (_keys) { - if (!Array.isArray(_keys)) { return; } - accessKeys = _keys; - - _keys.some(function (_k) { - if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { - myKeys = _k; - return true; - } - }); - })); - Cryptpad.getFormKeys(w(function (keys) { - if (!keys.curvePublic && !keys.formSeed) { - // No drive mode - var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); - noDriveAnswered = answered.indexOf(data.channel) !== -1; - } - myFormKeys = keys; - })); - Cryptpad.makeNetwork(w(function (err, nw) { - network = nw; - })); - Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { - if (md && md.deleteLines) { deleteLines = true; } - })); - }).nThen(function () { - if (!network) { return void cb({error: "E_CONNECT"}); } - - if (myFormKeys.formSeed) { - myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); - } - - var keys = Utils.secret && Utils.secret.keys; - - var curvePrivate = privateKey || data.privateKey; - if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } - var crypto = Utils.Crypto.Mailbox.createEncryptor({ - curvePrivate: curvePrivate, - curvePublic: publicKey || data.publicKey, - validateKey: data.validateKey - }); - - console.log("PRIVATE", curvePrivate) - console.log("PUBLIC", publicKey, data.publicKey) - console.log("VALIDATE", data.validateKey) - var config = { - network: network, - channel: data.channel, - noChainPad: true, - validateKey: keys.secondaryValidateKey, - owners: [myKeys.edPublic], - crypto: crypto, - metadata: { - deleteLines: true - } - //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers - }; - var results = {}; - config.onError = function (info) { - cb({ error: info.type }); - }; - config.onRejected = function (data, cb) { - if (!Array.isArray(data) || !data.length || data[0].length !== 16) { - return void cb(true); - } - if (!Array.isArray(accessKeys)) { return void cb(true); } - network.historyKeeper = data[0]; - nThen(function (waitFor) { - accessKeys.forEach(function (obj) { - Pinpad.create(network, obj, waitFor(function (e) { - if (e) { console.error(e); } - })); - }); - }).nThen(function () { - cb(); - }); - }; - config.onReady = function () { - var myKey; - // If we have submitted an anonymous answer, retrieve it - if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { - myKey = myFormKeys.curvePublic; - } - cb({ - noDriveAnswered: noDriveAnswered, - myKey: myKey, - results: results - }); - network.disconnect(); - }; - config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { - var parsed = Utils.Util.tryParse(msg); - if (!parsed) { return; } - var uid = parsed._uid || '000'; - - // If we have a "non-anonymous" answer, it may be the edition of a - // previous anonymous answer. Check if a previous anonymous answer exists - // with the same uid and delete it. - if (parsed._proof) { - var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); - var theirAnonKey = parsed._proof.key; - if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { - delete results[theirAnonKey][uid]; - } - } - - parsed._time = cfg && cfg.time; - if (deleteLines) { parsed._hash = hash; } - - if (data.cantEdit && results[senderCurve] - && results[senderCurve][uid]) { return; } - results[senderCurve] = results[senderCurve] || {}; - results[senderCurve][uid] = { - msg: parsed, - hash: hash, - time: cfg && cfg.time - }; - }; - CPNetflux.start(config); - }); - }); sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { var answers = []; var myKeys; From 6c1536b7bb05c08861b2f84066b19c92aae9a715 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 14:56:18 +0100 Subject: [PATCH 05/22] Cleanup --- www/form/inner.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/www/form/inner.js b/www/form/inner.js index b14c932a3..3428d98b6 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -50,7 +50,6 @@ define([ 'css!/lib/datepicker/flatpickr.min.css', 'css!/components/components-font-awesome/css/font-awesome.min.css', 'less!/form/app-form.less', - '/common/sframe-common-outer.js' ], function ( $, Sortify, @@ -79,8 +78,7 @@ define([ DatePicker, Share, Access, Properties, Flatpickr, - Sortable, - SFCommonO + Sortable ) { @@ -5377,7 +5375,6 @@ define([ } var getMyAnswers = APP.getMyAnswers = function () { - var sframeChan = framework._.sfCommon.getSframeChannel(); sframeChan.query("Q_FETCH_MY_ANSWERS", { channel: content.answers.channel, validateKey: content.answers.validateKey, From fdd4987004c1fa7107c24ce525030a7cef8224df Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 15:29:00 +0100 Subject: [PATCH 06/22] Linting --- www/common/make-backup.js | 13 ++++++------- www/common/sframe-common-outer.js | 2 +- www/form/main.js | 5 +---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index f0ae62da0..db11bd481 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -240,19 +240,18 @@ define([ transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } - var data = JSON.parse(val) - + var data = JSON.parse(val); if (data.form) { - var _answers = data["answers"] - _answers['href'] = parsed.hash - _answers['password'] = fData.password - _answers['drive'] = true + var _answers = data["answers"]; + _answers['href'] = parsed.hash; + _answers['password'] = fData.password; + _answers['drive'] = true; var answers; ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); existingNames.push(fileName.toLowerCase()); - var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}} + var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; var getFullOrder = function (content) { var order = content.order.slice(); var getSections = function (content) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e7c6ab93e..39eb639da 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2434,7 +2434,7 @@ define([ var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var formHref = data.href + var formHref = data.href; var cb = Utils.Util.once(_cb); var myKeys = {}; var myFormKeys; diff --git a/www/form/main.js b/www/form/main.js index aa6f613a0..41800fc63 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -21,7 +21,6 @@ define([ href = obj.href; hash = obj.hash; }).nThen(function (/*waitFor*/) { - var privateKey, publicKey; var channels = {}; var getPropChannels = function () { return channels; @@ -40,9 +39,6 @@ define([ var validateKey = keys.secondaryValidateKey; meta.form_answerValidateKey = validateKey; - - publicKey = meta.form_public = formData.form_public; - privateKey = meta.form_private = formData.form_private; meta.form_auditorHash = formData.form_auditorHash; }; var addRpc = function (sframeChan, Cryptpad, Utils) { @@ -105,6 +101,7 @@ define([ proof: Nacl.util.encodeBase64(u8_bundle) }; }; + var deleteLines = false; // "false" to support old forms sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { var answers = []; var myKeys; From 28c400e049524a4b3fd6ed92231c5e2bda1e33cd Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 15:37:55 +0100 Subject: [PATCH 07/22] Revert "Linting" This reverts commit fdd4987004c1fa7107c24ce525030a7cef8224df. --- www/common/make-backup.js | 13 +++++++------ www/common/sframe-common-outer.js | 2 +- www/form/main.js | 5 ++++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index db11bd481..f0ae62da0 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -240,18 +240,19 @@ define([ transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } - var data = JSON.parse(val); + var data = JSON.parse(val) + if (data.form) { - var _answers = data["answers"]; - _answers['href'] = parsed.hash; - _answers['password'] = fData.password; - _answers['drive'] = true; + var _answers = data["answers"] + _answers['href'] = parsed.hash + _answers['password'] = fData.password + _answers['drive'] = true var answers; ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); existingNames.push(fileName.toLowerCase()); - var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; + var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}} var getFullOrder = function (content) { var order = content.order.slice(); var getSections = function (content) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 39eb639da..e7c6ab93e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2434,7 +2434,7 @@ define([ var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var formHref = data.href; + var formHref = data.href var cb = Utils.Util.once(_cb); var myKeys = {}; var myFormKeys; diff --git a/www/form/main.js b/www/form/main.js index 41800fc63..aa6f613a0 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -21,6 +21,7 @@ define([ href = obj.href; hash = obj.hash; }).nThen(function (/*waitFor*/) { + var privateKey, publicKey; var channels = {}; var getPropChannels = function () { return channels; @@ -39,6 +40,9 @@ define([ var validateKey = keys.secondaryValidateKey; meta.form_answerValidateKey = validateKey; + + publicKey = meta.form_public = formData.form_public; + privateKey = meta.form_private = formData.form_private; meta.form_auditorHash = formData.form_auditorHash; }; var addRpc = function (sframeChan, Cryptpad, Utils) { @@ -101,7 +105,6 @@ define([ proof: Nacl.util.encodeBase64(u8_bundle) }; }; - var deleteLines = false; // "false" to support old forms sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { var answers = []; var myKeys; From 0c26b1d2731f7155f812425f6bf6b24a09d21271 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 12 Nov 2024 15:51:42 +0100 Subject: [PATCH 08/22] Linting #2 --- www/common/make-backup.js | 13 ++++++------- www/common/sframe-common-outer.js | 2 +- www/form/main.js | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index f0ae62da0..db11bd481 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -240,19 +240,18 @@ define([ transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } - var data = JSON.parse(val) - + var data = JSON.parse(val); if (data.form) { - var _answers = data["answers"] - _answers['href'] = parsed.hash - _answers['password'] = fData.password - _answers['drive'] = true + var _answers = data["answers"]; + _answers['href'] = parsed.hash; + _answers['password'] = fData.password; + _answers['drive'] = true; var answers; ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); existingNames.push(fileName.toLowerCase()); - var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}} + var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; var getFullOrder = function (content) { var order = content.order.slice(); var getSections = function (content) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e7c6ab93e..39eb639da 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2434,7 +2434,7 @@ define([ var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var formHref = data.href + var formHref = data.href; var cb = Utils.Util.once(_cb); var myKeys = {}; var myFormKeys; diff --git a/www/form/main.js b/www/form/main.js index aa6f613a0..dab04bb8f 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -21,7 +21,6 @@ define([ href = obj.href; hash = obj.hash; }).nThen(function (/*waitFor*/) { - var privateKey, publicKey; var channels = {}; var getPropChannels = function () { return channels; @@ -41,8 +40,8 @@ define([ var validateKey = keys.secondaryValidateKey; meta.form_answerValidateKey = validateKey; - publicKey = meta.form_public = formData.form_public; - privateKey = meta.form_private = formData.form_private; + meta.form_public = formData.form_public; + meta.form_private = formData.form_private; meta.form_auditorHash = formData.form_auditorHash; }; var addRpc = function (sframeChan, Cryptpad, Utils) { @@ -105,6 +104,7 @@ define([ proof: Nacl.util.encodeBase64(u8_bundle) }; }; + var deleteLines = false; // "false" to support old forms sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { var answers = []; var myKeys; From 5ba6de2354201bab6705918f20603066f298edba Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 14 Nov 2024 16:22:01 +0100 Subject: [PATCH 09/22] Refactor --- www/common/make-backup.js | 53 +---- www/common/sframe-common-outer.js | 369 +++++++++++++++--------------- www/form/export.js | 58 ++++- 3 files changed, 245 insertions(+), 235 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index db11bd481..bf73d6c3a 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -15,9 +15,8 @@ define([ '/components/nthen/index.js', '/components/saferphore/index.js', '/components/jszip/dist/jszip.min.js', - '/form/export.js', ], function ($, FileCrypto, Hash, Util, UI, h, Feedback, - Cache, Messages, nThen, Saferphore, JsZip, Exporter) { + Cache, Messages, nThen, Saferphore, JsZip) { var saveAs = window.saveAs; var sanitize = function (str) { @@ -33,7 +32,7 @@ define([ return n; }; - var transform = function (ctx, type, sjson, cb, padData) { + var transform = function (ctx, parsed, sjson, cb, padData, zip, existingNames) { var result = { data: sjson, ext: '.json', @@ -44,13 +43,13 @@ define([ } catch (e) { return void cb(result); } - var path = '/' + type + '/export.js'; + var path = '/' + parsed.type + '/export.js'; require([path], function (Exporter) { Exporter.main(json, function (data, _ext) { result.ext = _ext || Exporter.ext || ''; result.data = data; cb(result); - }, null, ctx.sframeChan, padData); + }, null, ctx.sframeChan, padData, zip, sanitize, getUnique, existingNames); }, function () { cb(result); }); @@ -141,7 +140,7 @@ define([ if (cancelled) { return; } if (err) { return; } if (!val) { return; } - transform(ctx, parsed.type, val, function (res) { + transform(ctx, parsed, val, function (res) { if (cancelled) { return; } if (!res.data) { return; } var dl = function () { @@ -237,47 +236,9 @@ define([ var opts = { binary: true, }; - transform(ctx, parsed.type, val, function (res) { + transform(ctx, parsed, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } - var data = JSON.parse(val); - if (data.form) { - var _answers = data["answers"]; - _answers['href'] = parsed.hash; - _answers['password'] = fData.password; - _answers['drive'] = true; - var answers; - ctx.sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { - answers = obj && obj.results; - var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); - existingNames.push(fileName.toLowerCase()); - var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; - var getFullOrder = function (content) { - var order = content.order.slice(); - var getSections = function (content) { - var uids = Object.keys(content.form).filter(function (uid) { - return content.form[uid].type === 'section'; - }); - return uids; - }; - getSections(content).forEach(function (uid) { - var block = content.form[uid]; - if (!block.opts || !Array.isArray(block.opts.questions)) { return; } - var idx = order.indexOf(uid); - if (idx === -1) { return; } - idx++; - block.opts.questions.forEach(function (el, i) { - order.splice(idx+i, 0, el); - }); - }); - return order; - }; - var arr = Exporter.results(data, answers, types, getFullOrder(data), "json"); - var content = new Blob([arr], { type : "application/json" }); - zip.file(fileName, content, opts); - }); - } - var fileName = getUnique(sanitize(rawName), res.ext, existingNames); existingNames.push(fileName.toLowerCase()); zip.file(fileName, res.data, opts); @@ -286,7 +247,7 @@ define([ }, { hash: parsed.hash, password: fData.password - }); + }, zip, existingNames); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 39eb639da..703b22c75 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -9,10 +9,7 @@ define([ '/common/requireconfig.js', '/customize/messages.js', 'jquery', - '/components/tweetnacl/nacl-fast.min.js', - ], function (nThen, ApiConfig, RequireConfig, Messages, $) { - var Nacl = window.nacl; var common = {}; var embeddableApps = [ @@ -160,6 +157,7 @@ define([ '/common/userObject.js', 'optional!/api/instance', '/common/pad-types.js', + '/components/tweetnacl/nacl-fast.min.js', ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, _SecureIframe, _UnsafeIframe, _OOIframe, _Notifier, _Hash, _Util, _Realtime, _Notify, _Constants, _Feedback, _LocalStore, _Block, _Cache, _AppConfig, /* _Test,*/ _UserObject, @@ -187,6 +185,7 @@ define([ Utils.Block = _Block; Utils.PadTypes = _PadTypes; AppConfig = _AppConfig; + var Nacl = window.nacl; //Test = _Test; if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) { @@ -235,7 +234,11 @@ define([ } }; + + var addFirstHandlers = () => { + + sframeChan.on('Q_SETTINGS_CHECK_PASSWORD', function (data, cb) { var blockHash = Utils.LocalStore.getBlockHash(); var userHash = Utils.LocalStore.getUserHash(); @@ -287,6 +290,184 @@ define([ console.error(err || obj); }); }); + var checkAnonProof = function (proofObj, channel, curvePrivate) { + var pub = proofObj.key; + var proofTxt = proofObj.proof; + try { + var u8_bundle = Nacl.util.decodeBase64(proofTxt); + var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); + var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); + var u8_plain = Nacl.box.open( + u8_cipher, + u8_nonce, + Nacl.util.decodeBase64(pub), + Nacl.util.decodeBase64(curvePrivate) + ); + return channel === Nacl.util.encodeUTF8(u8_plain); + } catch (e) { + console.error(e); + return false; + } + }; + var u8_slice = function (A, start, end) { + return new Uint8Array(Array.prototype.slice.call(A, start, end)); + }; + var deleteLines = false; // "false" to support old forms + sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { + var formHref = data.href; + var cb = Utils.Util.once(_cb); + var myKeys = {}; + var myFormKeys; + var accessKeys; + var CPNetflux, Pinpad; + var network; + var noDriveAnswered = false; + nThen(function (w) { + require([ + 'chainpad-netflux', + '/common/pinpad.js', + ], w(function (_CPNetflux, _Pinpad) { + CPNetflux = _CPNetflux; + Pinpad = _Pinpad; + })); + var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; + Cryptpad.getAccessKeys(w(function (_keys) { + if (!Array.isArray(_keys)) { return; } + accessKeys = _keys; + + _keys.some(function (_k) { + if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { + myKeys = _k; + return true; + } + }); + })); + Cryptpad.getFormKeys(w(function (keys) { + if (!keys.curvePublic && !keys.formSeed) { + // No drive mode + var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); + noDriveAnswered = answered.indexOf(data.channel) !== -1; + } + myFormKeys = keys; + })); + Cryptpad.makeNetwork(w(function (err, nw) { + network = nw; + })); + Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { + if (md && md.deleteLines) { deleteLines = true; } + })); + }).nThen(function () { + if (!network) { return void cb({error: "E_CONNECT"}); } + var getAnonymousKeys = function (formSeed, channel) { + var array = Nacl.util.decodeBase64(formSeed + channel); + var hash = Nacl.hash(array); + var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); + var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); + return { + curvePrivate: secretKey, + curvePublic: publicKey, + }; + }; + if (myFormKeys.formSeed) { + myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); + } + var keys; + var privateKey, publicKey; + var formData; + if (data.drive) { + var secret = Utils.Hash.getSecrets('form', formHref, data.password); + keys = secret && secret.keys; + formData = Utils.Hash.getFormData(secret); + } else { + formData = Utils.Hash.getFormData(Utils.secret); + keys = Utils.secret && Utils.secret.keys; + } + privateKey = formData.form_private; + publicKey = formData.form_public; + var curvePrivate = privateKey || data.privateKey; + if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } + var crypto = Utils.Crypto.Mailbox.createEncryptor({ + curvePrivate: curvePrivate, + curvePublic: publicKey || data.publicKey, + validateKey: data.validateKey + }); + + var config = { + network: network, + channel: data.channel, + noChainPad: true, + validateKey: keys.secondaryValidateKey, + owners: [myKeys.edPublic], + crypto: crypto, + metadata: { + deleteLines: true + } + //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers + }; + var results = {}; + config.onError = function (info) { + cb({ error: info.type }); + }; + config.onRejected = function (data, cb) { + if (!Array.isArray(data) || !data.length || data[0].length !== 16) { + return void cb(true); + } + if (!Array.isArray(accessKeys)) { return void cb(true); } + network.historyKeeper = data[0]; + nThen(function (waitFor) { + accessKeys.forEach(function (obj) { + Pinpad.create(network, obj, waitFor(function (e) { + if (e) { console.error(e); } + })); + }); + }).nThen(function () { + cb(); + }); + }; + config.onReady = function () { + var myKey; + // If we have submitted an anonymous answer, retrieve it + if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { + myKey = myFormKeys.curvePublic; + } + cb({ + noDriveAnswered: noDriveAnswered, + myKey: myKey, + results: results + }); + network.disconnect(); + }; + config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { + var parsed = Utils.Util.tryParse(msg); + if (!parsed) { return; } + var uid = parsed._uid || '000'; + + // If we have a "non-anonymous" answer, it may be the edition of a + // previous anonymous answer. Check if a previous anonymous answer exists + // with the same uid and delete it. + if (parsed._proof) { + var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); + var theirAnonKey = parsed._proof.key; + if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { + delete results[theirAnonKey][uid]; + } + } + + parsed._time = cfg && cfg.time; + if (deleteLines) { parsed._hash = hash; } + + if (data.cantEdit && results[senderCurve] + && results[senderCurve][uid]) { return; } + results[senderCurve] = results[senderCurve] || {}; + results[senderCurve][uid] = { + msg: parsed, + hash: hash, + time: cfg && cfg.time + }; + }; + CPNetflux.start(config); + }); + }); }; var whenReady = waitFor(function (msg) { @@ -897,6 +1078,7 @@ define([ sframeChan.event("EV_NEW_VERSION"); }); + // Put in the following function the RPC queries that should also work in filepicker @@ -1387,6 +1569,8 @@ define([ }); }); }); + + }; addCommonRpc(sframeChan, isSafe); @@ -2432,185 +2616,6 @@ define([ Utils.Feedback.send("BURN_AFTER_READING", Boolean(cfg.noDrive)); }); - var deleteLines = false; // "false" to support old forms - sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var formHref = data.href; - var cb = Utils.Util.once(_cb); - var myKeys = {}; - var myFormKeys; - var accessKeys; - var CPNetflux, Pinpad; - var network; - var noDriveAnswered = false; - nThen(function (w) { - require([ - 'chainpad-netflux', - '/common/pinpad.js', - ], w(function (_CPNetflux, _Pinpad) { - CPNetflux = _CPNetflux; - Pinpad = _Pinpad; - })); - var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; - Cryptpad.getAccessKeys(w(function (_keys) { - if (!Array.isArray(_keys)) { return; } - accessKeys = _keys; - - _keys.some(function (_k) { - if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { - myKeys = _k; - return true; - } - }); - })); - Cryptpad.getFormKeys(w(function (keys) { - if (!keys.curvePublic && !keys.formSeed) { - // No drive mode - var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); - noDriveAnswered = answered.indexOf(data.channel) !== -1; - } - myFormKeys = keys; - })); - Cryptpad.makeNetwork(w(function (err, nw) { - network = nw; - })); - Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { - if (md && md.deleteLines) { deleteLines = true; } - })); - }).nThen(function () { - if (!network) { return void cb({error: "E_CONNECT"}); } - var getAnonymousKeys = function (formSeed, channel) { - var array = Nacl.util.decodeBase64(formSeed + channel); - var hash = Nacl.hash(array); - var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); - var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); - return { - curvePrivate: secretKey, - curvePublic: publicKey, - }; - }; - if (myFormKeys.formSeed) { - myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); - } - var keys; - var privateKey, publicKey; - var formData; - if (data.drive) { - var secret = Utils.Hash.getSecrets('form', formHref, data.password); - keys = secret && secret.keys; - formData = Utils.Hash.getFormData(secret); - } else { - formData = Utils.Hash.getFormData(Utils.secret); - keys = Utils.secret && Utils.secret.keys; - } - privateKey = formData.form_private; - publicKey = formData.form_public; - var curvePrivate = privateKey || data.privateKey; - if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } - var crypto = Utils.Crypto.Mailbox.createEncryptor({ - curvePrivate: curvePrivate, - curvePublic: publicKey || data.publicKey, - validateKey: data.validateKey - }); - - var config = { - network: network, - channel: data.channel, - noChainPad: true, - validateKey: keys.secondaryValidateKey, - owners: [myKeys.edPublic], - crypto: crypto, - metadata: { - deleteLines: true - } - //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers - }; - var results = {}; - config.onError = function (info) { - cb({ error: info.type }); - }; - config.onRejected = function (data, cb) { - if (!Array.isArray(data) || !data.length || data[0].length !== 16) { - return void cb(true); - } - if (!Array.isArray(accessKeys)) { return void cb(true); } - network.historyKeeper = data[0]; - nThen(function (waitFor) { - accessKeys.forEach(function (obj) { - Pinpad.create(network, obj, waitFor(function (e) { - if (e) { console.error(e); } - })); - }); - }).nThen(function () { - cb(); - }); - }; - config.onReady = function () { - var myKey; - // If we have submitted an anonymous answer, retrieve it - if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { - myKey = myFormKeys.curvePublic; - } - cb({ - noDriveAnswered: noDriveAnswered, - myKey: myKey, - results: results - }); - network.disconnect(); - }; - config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { - var parsed = Utils.Util.tryParse(msg); - if (!parsed) { return; } - var uid = parsed._uid || '000'; - - // If we have a "non-anonymous" answer, it may be the edition of a - // previous anonymous answer. Check if a previous anonymous answer exists - // with the same uid and delete it. - var u8_slice = function (A, start, end) { - return new Uint8Array(Array.prototype.slice.call(A, start, end)); - }; - var checkAnonProof = function (proofObj, channel, curvePrivate) { - var pub = proofObj.key; - var proofTxt = proofObj.proof; - try { - var u8_bundle = Nacl.util.decodeBase64(proofTxt); - var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); - var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); - var u8_plain = Nacl.box.open( - u8_cipher, - u8_nonce, - Nacl.util.decodeBase64(pub), - Nacl.util.decodeBase64(curvePrivate) - ); - return channel === Nacl.util.encodeUTF8(u8_plain); - } catch (e) { - console.error(e); - return false; - } - }; - if (parsed._proof) { - var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); - var theirAnonKey = parsed._proof.key; - if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { - delete results[theirAnonKey][uid]; - } - } - - parsed._time = cfg && cfg.time; - if (deleteLines) { parsed._hash = hash; } - - if (data.cantEdit && results[senderCurve] - && results[senderCurve][uid]) { return; } - results[senderCurve] = results[senderCurve] || {}; - results[senderCurve][uid] = { - msg: parsed, - hash: hash, - time: cfg && cfg.time - }; - }; - CPNetflux.start(config); - }); - }); - sframeChan.ready(); Utils.Feedback.reportAppUsage(); diff --git a/www/form/export.js b/www/form/export.js index 9a4ac0f7a..af8e626f1 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -4,7 +4,8 @@ define([ '/common/common-util.js', - '/customize/messages.js' + '/customize/messages.js', + '' ], function (Util, Messages) { var Export = { ext: '.json' @@ -162,13 +163,56 @@ define([ return csv; }; - Export.main = function (content, cb) { - var json = Util.clone(content || {}); - delete json.answers; - cb(new Blob([JSON.stringify(json, 0, 2)], { - type: 'application/json;charset=utf-8' - })); + var getFullOrder = function (content) { + var order = content.order.slice(); + var getSections = function (content) { + var uids = Object.keys(content.form).filter(function (uid) { + return content.form[uid].type === 'section'; + }); + return uids; + }; + getSections(content).forEach(function (uid) { + var block = content.form[uid]; + if (!block.opts || !Array.isArray(block.opts.questions)) { return; } + var idx = order.indexOf(uid); + if (idx === -1) { return; } + idx++; + block.opts.questions.forEach(function (el, i) { + order.splice(idx+i, 0, el); + }); + }); + return order; }; + Export.main = function (content, cb, ext, sframeChan, parsed, zip, sanitize, getUnique, existingNames) { + if (sframeChan && content.form) { + var _answers = content["answers"]; + _answers['href'] = parsed.hash; + _answers['password'] = parsed.password; + _answers['drive'] = true; + var answers; + sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { + answers = obj && obj.results; + var rawName = content.metadata.title || 'File'; + var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); + existingNames.push(fileName.toLowerCase()); + var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; + + var arr = Export.results(content, answers, types, getFullOrder(content), "json"); + var results = new Blob([arr], { type : "application/json" }); + var opts = { + binary: true, + }; + zip.file(fileName, results, opts); + }); + } + + var json = Util.clone(content || {}); + delete json.answers; + cb(new Blob([JSON.stringify(json, 0, 2)], { + type: 'application/json;charset=utf-8' + })); + }; + return Export; }); From 915156523ab86ad8d20e8f8ef04b50eb39ae8398 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 14 Nov 2024 16:27:44 +0100 Subject: [PATCH 10/22] Cleaning --- www/common/sframe-common-outer.js | 7 ------- www/form/export.js | 1 - 2 files changed, 8 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 703b22c75..d7675d50a 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -234,11 +234,7 @@ define([ } }; - - var addFirstHandlers = () => { - - sframeChan.on('Q_SETTINGS_CHECK_PASSWORD', function (data, cb) { var blockHash = Utils.LocalStore.getBlockHash(); var userHash = Utils.LocalStore.getUserHash(); @@ -1078,7 +1074,6 @@ define([ sframeChan.event("EV_NEW_VERSION"); }); - // Put in the following function the RPC queries that should also work in filepicker @@ -1569,8 +1564,6 @@ define([ }); }); }); - - }; addCommonRpc(sframeChan, isSafe); diff --git a/www/form/export.js b/www/form/export.js index af8e626f1..4ae7fe8d4 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -5,7 +5,6 @@ define([ '/common/common-util.js', '/customize/messages.js', - '' ], function (Util, Messages) { var Export = { ext: '.json' From f253645b674da434638364fb05d15222183352ef Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 15 Nov 2024 12:40:46 +0100 Subject: [PATCH 11/22] Fixed date format --- www/form/export.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/www/form/export.js b/www/form/export.js index 4ae7fe8d4..9ced9c83b 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -59,18 +59,7 @@ define([ var type = obj.type; if (!TYPES[type]) { return; } // Ignore static types var id = `q${i++}`; - if (TYPES[type] && TYPES[type].exportCSV) { - var _obj = Util.clone(obj); - _obj.q = "tmp"; - q[id] = { - question: obj.q, - items: TYPES[type].exportCSV(false, _obj).map(function (str) { - return str.slice(6); // Remove "tmp | " - }) - }; - } else { - q[id] = obj.q || Messages.form_default; - } + q[id] = obj.q || Messages.form_default; }); sortedKeys.forEach(function (k) { @@ -84,14 +73,14 @@ define([ }; var i = 1; + order.forEach(function (key) { if (!form[key]) { return; } var type = form[key].type; if (!TYPES[type]) { return; } // Ignore static types var id = `q${i++}`; - if (TYPES[type].exportCSV) { - data[id] = TYPES[type].exportCSV(msg[key], form[key]); - return; + if (type === 'date') { + msg[key] = new Date(msg[key]).toISOString(); } data[id] = msg[key]; }); @@ -115,7 +104,6 @@ define([ var form = content.form; var questions = [Messages.form_poll_time, Messages.share_formView]; - order.forEach(function (key) { var obj = form[key]; if (!obj) { return; } @@ -196,8 +184,7 @@ define([ var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); existingNames.push(fileName.toLowerCase()); var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; - - var arr = Export.results(content, answers, types, getFullOrder(content), "json"); + var arr = Export.results(content, answers, types, getFullOrder(content), "json"); var results = new Blob([arr], { type : "application/json" }); var opts = { binary: true, From ccfe1472e66ed158cbf01699b81daa88dc97e32c Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 4 Dec 2024 13:50:26 +0100 Subject: [PATCH 12/22] lint compliance --- www/common/sframe-common-outer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 61fc87bb7..53bda5d9e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -285,6 +285,9 @@ define([ console.error(err || obj); }); }); + var u8_slice = function (A, start, end) { + return new Uint8Array(Array.prototype.slice.call(A, start, end)); + }; var checkAnonProof = function (proofObj, channel, curvePrivate) { var pub = proofObj.key; var proofTxt = proofObj.proof; @@ -304,9 +307,6 @@ define([ return false; } }; - var u8_slice = function (A, start, end) { - return new Uint8Array(Array.prototype.slice.call(A, start, end)); - }; var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { var formHref = data.href; From 9c87556332838473584f105bec92df7ea1b63cd7 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Thu, 5 Dec 2024 14:00:35 +0100 Subject: [PATCH 13/22] Refactoring - WIP --- www/common/cryptpad-common.js | 11 +++++++++++ www/common/sframe-common-outer.js | 12 +----------- www/form/export.js | 12 ++++++------ www/form/main.js | 16 +++------------- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ada56e9be..bf7ea514d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2449,6 +2449,17 @@ define([ window.RTCPeerConnection); }; + common.getAnonymousKeys = function (formSeed, channel, Utils) { + var array = window.nacl.util.decodeBase64(formSeed + channel); + var hash = window.nacl.hash(array); + var secretKey = window.nacl.util.encodeBase64(hash.subarray(32)); + var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); + return { + curvePrivate: secretKey, + curvePublic: publicKey, + }; + }; + common.ready = (function () { var env = {}; var initialized = false; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 53bda5d9e..036e7edfd 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -353,18 +353,8 @@ define([ })); }).nThen(function () { if (!network) { return void cb({error: "E_CONNECT"}); } - var getAnonymousKeys = function (formSeed, channel) { - var array = Nacl.util.decodeBase64(formSeed + channel); - var hash = Nacl.hash(array); - var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); - var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); - return { - curvePrivate: secretKey, - curvePublic: publicKey, - }; - }; if (myFormKeys.formSeed) { - myFormKeys = getAnonymousKeys(myFormKeys.formSeed, data.channel); + myFormKeys = _Cryptpad.getAnonymousKeys(myFormKeys.formSeed, data.channel, Utils); } var keys; var privateKey, publicKey; diff --git a/www/form/export.js b/www/form/export.js index 9ced9c83b..a3b3a1c3f 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -4,7 +4,7 @@ define([ '/common/common-util.js', - '/customize/messages.js', + '/customize/messages.js' ], function (Util, Messages) { var Export = { ext: '.json' @@ -194,11 +194,11 @@ define([ } var json = Util.clone(content || {}); - delete json.answers; - cb(new Blob([JSON.stringify(json, 0, 2)], { - type: 'application/json;charset=utf-8' - })); - }; + delete json.answers; + cb(new Blob([JSON.stringify(json, 0, 2)], { + type: 'application/json;charset=utf-8' + })); + }; return Export; }); diff --git a/www/form/main.js b/www/form/main.js index dab04bb8f..aa47abf73 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -64,16 +64,6 @@ define([ if (!a) { sframeChan.event('EV_POPUP_BLOCKED'); } delete sessionStorage.CP_formExportSheet; }); - var getAnonymousKeys = function (formSeed, channel) { - var array = Nacl.util.decodeBase64(formSeed + channel); - var hash = Nacl.hash(array); - var secretKey = Nacl.util.encodeBase64(hash.subarray(32)); - var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); - return { - curvePrivate: secretKey, - curvePublic: publicKey, - }; - }; var u8_concat = function (A) { var length = 0; A.forEach(function (a) { length += a.length; }); @@ -150,7 +140,7 @@ define([ console.error('ANONYMOUS_ERROR', answer); return; } - finalKeys = getAnonymousKeys(myKeys.formSeed, data.channel); + finalKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, data.channel, Utils); } Cryptpad.getHistoryRange({ channel: data.channel, @@ -209,9 +199,9 @@ define([ var myAnonymousKeys; if (data.anonymous) { if (!myKeys.formSeed) { return void cb({ error: "ANONYMOUS_ERROR" }); } - myKeys = getAnonymousKeys(myKeys.formSeed, box.channel); + myKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); } else { - myAnonymousKeys = getAnonymousKeys(myKeys.formSeed, box.channel); + myAnonymousKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); } var keys = Utils.secret && Utils.secret.keys; myKeys.signingKey = keys.secondarySignKey; From 4f14e6db4fec3e137f629fff61748e421543993e Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 17 Dec 2024 16:01:23 +0100 Subject: [PATCH 14/22] Reverting deleted code --- www/form/export.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/www/form/export.js b/www/form/export.js index a3b3a1c3f..0f48b22ef 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -59,7 +59,18 @@ define([ var type = obj.type; if (!TYPES[type]) { return; } // Ignore static types var id = `q${i++}`; - q[id] = obj.q || Messages.form_default; + if (TYPES[type] && TYPES[type].exportCSV) { + var _obj = Util.clone(obj); + _obj.q = "tmp"; + q[id] = { + question: obj.q, + items: TYPES[type].exportCSV(false, _obj).map(function (str) { + return str.slice(6); // Remove "tmp | " + }) + }; + } else { + q[id] = obj.q || Messages.form_default; + } }); sortedKeys.forEach(function (k) { @@ -73,14 +84,14 @@ define([ }; var i = 1; - order.forEach(function (key) { if (!form[key]) { return; } var type = form[key].type; if (!TYPES[type]) { return; } // Ignore static types var id = `q${i++}`; - if (type === 'date') { - msg[key] = new Date(msg[key]).toISOString(); + if (TYPES[type].exportCSV) { + data[id] = TYPES[type].exportCSV(msg[key], form[key]); + return; } data[id] = msg[key]; }); From 04cdd0a54aa42212810ba8b8eeb8c2eba228fb4a Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 18 Dec 2024 11:13:15 +0100 Subject: [PATCH 15/22] Only one (responses) file downloaded per Form --- www/common/make-backup.js | 6 +++--- www/form/export.js | 27 +++++++++++---------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index bf73d6c3a..7394be98a 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -32,7 +32,7 @@ define([ return n; }; - var transform = function (ctx, parsed, sjson, cb, padData, zip, existingNames) { + var transform = function (ctx, parsed, sjson, cb, padData) { var result = { data: sjson, ext: '.json', @@ -49,7 +49,7 @@ define([ result.ext = _ext || Exporter.ext || ''; result.data = data; cb(result); - }, null, ctx.sframeChan, padData, zip, sanitize, getUnique, existingNames); + }, null, ctx.sframeChan, padData); }, function () { cb(result); }); @@ -247,7 +247,7 @@ define([ }, { hash: parsed.hash, password: fData.password - }, zip, existingNames); + }); }); }; diff --git a/www/form/export.js b/www/form/export.js index 0f48b22ef..e4f81fb1b 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -182,7 +182,7 @@ define([ return order; }; - Export.main = function (content, cb, ext, sframeChan, parsed, zip, sanitize, getUnique, existingNames) { + Export.main = function (content, cb, ext, sframeChan, parsed) { if (sframeChan && content.form) { var _answers = content["answers"]; _answers['href'] = parsed.hash; @@ -191,24 +191,19 @@ define([ var answers; sframeChan.query("Q_FORM_FETCH_ANSWERS", _answers, function (err, obj) { answers = obj && obj.results; - var rawName = content.metadata.title || 'File'; - var fileName = getUnique(sanitize(rawName + ' (answers)'), '.json', existingNames); - existingNames.push(fileName.toLowerCase()); var types = {input: {}, textarea: {}, radio: {}, multiradio: {}, date: {}, checkbox: {}, multicheck: {}, sort: {}, poll: {}}; - var arr = Export.results(content, answers, types, getFullOrder(content), "json"); - var results = new Blob([arr], { type : "application/json" }); - var opts = { - binary: true, - }; - zip.file(fileName, results, opts); + var arr = Export.results(content, answers, types, getFullOrder(content), "json"); + cb(new Blob([arr], { + type: 'application/json;charset=utf-8' + })); }); + } else { + var json = Util.clone(content || {}); + delete json.answers; + cb(new Blob([JSON.stringify(json, 0, 2)], { + type: 'application/json;charset=utf-8' + })); } - - var json = Util.clone(content || {}); - delete json.answers; - cb(new Blob([JSON.stringify(json, 0, 2)], { - type: 'application/json;charset=utf-8' - })); }; return Export; From 53c647f7ca136690085de4240e1841e7d0a69094 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 18 Dec 2024 11:32:58 +0100 Subject: [PATCH 16/22] Date format --- www/form/export.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/form/export.js b/www/form/export.js index e4f81fb1b..792fd5dc4 100644 --- a/www/form/export.js +++ b/www/form/export.js @@ -93,7 +93,11 @@ define([ data[id] = TYPES[type].exportCSV(msg[key], form[key]); return; } - data[id] = msg[key]; + if (type === 'date') { + data[id] = new Date(msg[key]).toISOString(); + } else { + data[id] = msg[key]; + } }); r.push(data); }); From ec394257913b3b4f17134ffca835274ada69ab53 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Dec 2024 12:56:22 +0100 Subject: [PATCH 17/22] Fix encrypted edit links when downloading shared folders --- www/common/make-backup.js | 13 +++++++++++-- www/common/userObject.js | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 7394be98a..fd9330178 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -10,12 +10,13 @@ define([ '/common/common-interface.js', '/common/hyperscript.js', '/common/common-feedback.js', + '/common/userObject.js', '/common/inner/cache.js', '/customize/messages.js', '/components/nthen/index.js', '/components/saferphore/index.js', '/components/jszip/dist/jszip.min.js', -], function ($, FileCrypto, Hash, Util, UI, h, Feedback, +], function ($, FileCrypto, Hash, Util, UI, h, Feedback, UO, Cache, Messages, nThen, Saferphore, JsZip) { var saveAs = window.saveAs; @@ -170,7 +171,7 @@ define([ }); } - var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref; + let href = UO.getHref(fData, ctx.currentCryptor); var parsed = Hash.parsePadUrl(href); if (['pad', 'file'].indexOf(parsed.hashData.type) === -1) { return; } @@ -294,9 +295,17 @@ define([ if (typeof el === "object" && el.metadata !== true) { // if folder var fName = getUnique(sanitize(k), '', existingNames); existingNames.push(fName.toLowerCase()); + ctx.currentCryptor = undefined; return void makeFolder(ctx, el, zip.folder(fName), fd); } if (ctx.data.sharedFolders[el]) { // if shared folder + let obj = ctx.data.sharedFolders[el]; + let parsed = Hash.parsePadUrl(obj.href || obj.roHref); + var secret = Hash.getSecrets('drive', parsed.hash, obj.password); + let cryptor = secret.keys?.secondaryKey ? UO.createCryptor(secret.keys?.secondaryKey) + : undefined; + ctx.currentCryptor = cryptor; + var sfData = ctx.sf[el].metadata; var sfName = getUnique(sanitize((sfData && sfData.title) || 'Folder'), '', existingNames); existingNames.push(sfName.toLowerCase()); diff --git a/www/common/userObject.js b/www/common/userObject.js index aeb446487..c83cbbaa6 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -72,7 +72,7 @@ define([ // Href exists and is not encrypted: return href return pad.href; } - if (pad.href) { + if (pad.href && cryptor) { // Href exists and is encrypted var d = cryptor.decrypt(pad.href); // If we can decrypt, return the decrypted value, otherwise continue and return roHref From d5cc946f134807a2ed3f739fc219ff924277c71a Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 24 Jan 2025 14:43:18 +0100 Subject: [PATCH 18/22] Form command handlers moved to separate file --- www/common/cryptpad-common.js | 9 +- www/common/make-backup.js | 4 +- www/common/sframe-common-outer.js | 173 +------------- www/form/command-handler.js | 383 ++++++++++++++++++++++++++++++ www/form/main.js | 204 ---------------- 5 files changed, 393 insertions(+), 380 deletions(-) create mode 100644 www/form/command-handler.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index bf7ea514d..c1f4ab64c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -21,9 +21,10 @@ define([ '/customize/application_config.js', '/components/nthen/index.js', + '/components/tweetnacl/nacl-fast.min.js' ], function (Config, Messages, Util, Hash, Cache, Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, - Cred, Login, AppConfig, Nthen) { + Cred, Login, AppConfig, Nthen, Nacl) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata @@ -227,7 +228,6 @@ define([ n = n(function (waitFor) { require([ '/api/broadcast?'+ (+new Date()), - '/components/tweetnacl/nacl-fast.min.js' ], waitFor(function (Broadcast) { nacl = window.nacl; theirs = nacl.util.decodeBase64(Broadcast.curvePublic); @@ -1578,7 +1578,6 @@ define([ require([ '/common/media-tag.js', '/common/outer/upload.js', - '/components/tweetnacl/nacl-fast.min.js' ], waitFor(function (_MT, _Upload) { MediaTag = _MT; Upload = _Upload; @@ -2449,11 +2448,11 @@ define([ window.RTCPeerConnection); }; - common.getAnonymousKeys = function (formSeed, channel, Utils) { + common.getAnonymousKeys = function (formSeed, channel) { var array = window.nacl.util.decodeBase64(formSeed + channel); var hash = window.nacl.hash(array); var secretKey = window.nacl.util.encodeBase64(hash.subarray(32)); - var publicKey = Utils.Hash.getCurvePublicFromPrivate(secretKey); + var publicKey = Hash.getCurvePublicFromPrivate(secretKey); return { curvePrivate: secretKey, curvePublic: publicKey, diff --git a/www/common/make-backup.js b/www/common/make-backup.js index fd9330178..66767ab2e 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -33,7 +33,7 @@ define([ return n; }; - var transform = function (ctx, parsed, sjson, cb, padData) { + var transform = function (ctx, type, sjson, cb, padData) { var result = { data: sjson, ext: '.json', @@ -44,7 +44,7 @@ define([ } catch (e) { return void cb(result); } - var path = '/' + parsed.type + '/export.js'; + var path = '/' + type + '/export.js'; require([path], function (Exporter) { Exporter.main(json, function (data, _ext) { result.ext = _ext || Exporter.ext || ''; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 036e7edfd..5660f5d29 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -9,7 +9,8 @@ define([ '/common/requireconfig.js', '/customize/messages.js', 'jquery', -], function (nThen, ApiConfig, RequireConfig, Messages, $) { + '/form/command-handler.js' +], function (nThen, ApiConfig, RequireConfig, Messages, $, Handler) { var common = {}; var embeddableApps = [ @@ -285,174 +286,6 @@ define([ console.error(err || obj); }); }); - var u8_slice = function (A, start, end) { - return new Uint8Array(Array.prototype.slice.call(A, start, end)); - }; - var checkAnonProof = function (proofObj, channel, curvePrivate) { - var pub = proofObj.key; - var proofTxt = proofObj.proof; - try { - var u8_bundle = Nacl.util.decodeBase64(proofTxt); - var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); - var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); - var u8_plain = Nacl.box.open( - u8_cipher, - u8_nonce, - Nacl.util.decodeBase64(pub), - Nacl.util.decodeBase64(curvePrivate) - ); - return channel === Nacl.util.encodeUTF8(u8_plain); - } catch (e) { - console.error(e); - return false; - } - }; - var deleteLines = false; // "false" to support old forms - sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { - var formHref = data.href; - var cb = Utils.Util.once(_cb); - var myKeys = {}; - var myFormKeys; - var accessKeys; - var CPNetflux, Pinpad; - var network; - var noDriveAnswered = false; - nThen(function (w) { - require([ - 'chainpad-netflux', - '/common/pinpad.js', - ], w(function (_CPNetflux, _Pinpad) { - CPNetflux = _CPNetflux; - Pinpad = _Pinpad; - })); - var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; - Cryptpad.getAccessKeys(w(function (_keys) { - if (!Array.isArray(_keys)) { return; } - accessKeys = _keys; - - _keys.some(function (_k) { - if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { - myKeys = _k; - return true; - } - }); - })); - Cryptpad.getFormKeys(w(function (keys) { - if (!keys.curvePublic && !keys.formSeed) { - // No drive mode - var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); - noDriveAnswered = answered.indexOf(data.channel) !== -1; - } - myFormKeys = keys; - })); - Cryptpad.makeNetwork(w(function (err, nw) { - network = nw; - })); - Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { - if (md && md.deleteLines) { deleteLines = true; } - })); - }).nThen(function () { - if (!network) { return void cb({error: "E_CONNECT"}); } - if (myFormKeys.formSeed) { - myFormKeys = _Cryptpad.getAnonymousKeys(myFormKeys.formSeed, data.channel, Utils); - } - var keys; - var privateKey, publicKey; - var formData; - if (data.drive) { - var secret = Utils.Hash.getSecrets('form', formHref, data.password); - keys = secret && secret.keys; - formData = Utils.Hash.getFormData(secret); - } else { - formData = Utils.Hash.getFormData(Utils.secret); - keys = Utils.secret && Utils.secret.keys; - } - privateKey = formData.form_private; - publicKey = formData.form_public; - var curvePrivate = privateKey || data.privateKey; - if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } - var crypto = Utils.Crypto.Mailbox.createEncryptor({ - curvePrivate: curvePrivate, - curvePublic: publicKey || data.publicKey, - validateKey: data.validateKey - }); - - var config = { - network: network, - channel: data.channel, - noChainPad: true, - validateKey: keys.secondaryValidateKey, - owners: [myKeys.edPublic], - crypto: crypto, - metadata: { - deleteLines: true - } - //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers - }; - var results = {}; - config.onError = function (info) { - cb({ error: info.type }); - }; - config.onRejected = function (data, cb) { - if (!Array.isArray(data) || !data.length || data[0].length !== 16) { - return void cb(true); - } - if (!Array.isArray(accessKeys)) { return void cb(true); } - network.historyKeeper = data[0]; - nThen(function (waitFor) { - accessKeys.forEach(function (obj) { - Pinpad.create(network, obj, waitFor(function (e) { - if (e) { console.error(e); } - })); - }); - }).nThen(function () { - cb(); - }); - }; - config.onReady = function () { - var myKey; - // If we have submitted an anonymous answer, retrieve it - if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { - myKey = myFormKeys.curvePublic; - } - cb({ - noDriveAnswered: noDriveAnswered, - myKey: myKey, - results: results - }); - network.disconnect(); - }; - config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { - var parsed = Utils.Util.tryParse(msg); - if (!parsed) { return; } - var uid = parsed._uid || '000'; - - // If we have a "non-anonymous" answer, it may be the edition of a - // previous anonymous answer. Check if a previous anonymous answer exists - // with the same uid and delete it. - if (parsed._proof) { - var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); - var theirAnonKey = parsed._proof.key; - if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { - delete results[theirAnonKey][uid]; - } - } - - parsed._time = cfg && cfg.time; - if (deleteLines) { parsed._hash = hash; } - - if (data.cantEdit && results[senderCurve] - && results[senderCurve][uid]) { return; } - results[senderCurve] = results[senderCurve] || {}; - results[senderCurve][uid] = { - msg: parsed, - hash: hash, - time: cfg && cfg.time - }; - }; - CPNetflux.start(config); - }); - }); }; var whenReady = waitFor(function (msg) { @@ -2242,6 +2075,8 @@ define([ } }); + Handler.fetchFormAnswers(sframeChan, Utils, nThen, Cryptpad, window.nacl) + var integrationSave = function () {}; if (cfg.integration) { sframeChan.on('Q_INTEGRATION_SAVE', function (obj, cb) { diff --git a/www/form/command-handler.js b/www/form/command-handler.js new file mode 100644 index 000000000..86258007f --- /dev/null +++ b/www/form/command-handler.js @@ -0,0 +1,383 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +define([], function () { + + var Handler = {}; + + Handler.fetchFormAnswers = function(sframeChan, Utils, nThen, Cryptpad, Nacl) { + sframeChan.on('EV_EXPORT_SHEET', function (data) { + if (!data || !Array.isArray(data.content)) { return; } + sessionStorage.CP_formExportSheet = JSON.stringify(data); + var href = Utils.Hash.hashToHref('', 'sheet'); + var a = window.open(href); + if (!a) { sframeChan.event('EV_POPUP_BLOCKED'); } + delete sessionStorage.CP_formExportSheet; + }); + var u8_concat = function (A) { + var length = 0; + A.forEach(function (a) { length += a.length; }); + var total = new Uint8Array(length); + + var offset = 0; + A.forEach(function (a) { + total.set(a, offset); + offset += a.length; + }); + return total; + }; + var anonProof = function (channel, theirPub, anonKeys) { + var u8_plain = Nacl.util.decodeUTF8(channel); + var u8_nonce = Nacl.randomBytes(Nacl.box.nonceLength); + var u8_cipher = Nacl.box( + u8_plain, + u8_nonce, + Nacl.util.decodeBase64(theirPub), + Nacl.util.decodeBase64(anonKeys.curvePrivate) + ); + var u8_bundle = u8_concat([ + u8_nonce, // 24 uint8s + u8_cipher, // arbitrary length + ]); + return { + key: anonKeys.curvePublic, + proof: Nacl.util.encodeBase64(u8_bundle) + }; + }; + sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { + var answers = []; + var myKeys; + nThen(function (w) { + Cryptpad.getFormKeys(w(function (keys) { + myKeys = keys; + })); + Cryptpad.getFormAnswer({channel: data.channel}, w(function (obj) { + if (!obj || obj.error) { + if (obj && obj.error === "ENODRIVE") { + var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); + if (answered.indexOf(data.channel) !== -1) { + cb({error:'EANSWERED'}); + } else { + cb(); + } + return void w.abort(); + } + w.abort(); + return void cb(obj); + } + // Get the latest edit per uid + var temp = {}; + obj.forEach(function (ans) { + var uid = ans.uid || '000'; + temp[uid] = ans; + }); + answers = Object.values(temp); + })); + Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { + if (md && md.deleteLines) { deleteLines = true; } + })); + }).nThen(function () { + var n = nThen; + var err; + var all = {}; + answers.forEach(function (answer) { + n = n(function(waitFor) { + var finalKeys = myKeys; + if (answer.anonymous) { + if (!myKeys.formSeed) { + err = 'ANONYMOUS_ERROR'; + console.error('ANONYMOUS_ERROR', answer); + return; + } + finalKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, data.channel, Utils); + } + Cryptpad.getHistoryRange({ + channel: data.channel, + lastKnownHash: answer.hash, + toHash: answer.hash, + }, waitFor(function (obj) { + if (obj && obj.error) { err = obj.error; return; } + var messages = obj.messages; + if (!messages.length) { + // TODO delete from drive.forms? + return; + } + if (obj.lastKnownHash !== answer.hash) { return; } + try { + var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, { + validateKey: data.validateKey, + ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate), + my_private: Nacl.util.decodeBase64(finalKeys.curvePrivate), + their_public: Nacl.util.decodeBase64(data.publicKey) + }); + var parsed = JSON.parse(res.content); + parsed._isAnon = answer.anonymous; + parsed._time = messages[0].time; + if (deleteLines) { parsed._hash = answer.hash; } + var uid = parsed._uid || '000'; + if (all[uid] && !all[uid]._isAnon) { parsed._isAnon = false; } + all[uid] = parsed; + } catch (e) { + err = e; + } + })); + }).nThen; + }); + n(function () { + if (err) { return void cb({error: err}); } + cb(all); + }); + }); + }); + var u8_slice = function (A, start, end) { + return new Uint8Array(Array.prototype.slice.call(A, start, end)); + }; + var checkAnonProof = function (proofObj, channel, curvePrivate) { + var pub = proofObj.key; + var proofTxt = proofObj.proof; + try { + var u8_bundle = Nacl.util.decodeBase64(proofTxt); + var u8_nonce = u8_slice(u8_bundle, 0, Nacl.box.nonceLength); + var u8_cipher = u8_slice(u8_bundle, Nacl.box.nonceLength); + var u8_plain = Nacl.box.open( + u8_cipher, + u8_nonce, + Nacl.util.decodeBase64(pub), + Nacl.util.decodeBase64(curvePrivate) + ); + return channel === Nacl.util.encodeUTF8(u8_plain); + } catch (e) { + console.error(e); + return false; + } + }; + var deleteLines = false; // "false" to support old forms + sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { + var formHref = data.href; + var cb = Utils.Util.once(_cb); + var myKeys = {}; + var myFormKeys; + var accessKeys; + var CPNetflux, Pinpad; + var network; + var noDriveAnswered = false; + nThen(function (w) { + require([ + 'chainpad-netflux', + '/common/pinpad.js', + ], w(function (_CPNetflux, _Pinpad) { + CPNetflux = _CPNetflux; + Pinpad = _Pinpad; + })); + var personalDrive = !Cryptpad.initialTeam || Cryptpad.initialTeam === -1; + Cryptpad.getAccessKeys(w(function (_keys) { + if (!Array.isArray(_keys)) { return; } + accessKeys = _keys; + + _keys.some(function (_k) { + if ((personalDrive && !_k.id) || Cryptpad.initialTeam === Number(_k.id)) { + myKeys = _k; + return true; + } + }); + })); + Cryptpad.getFormKeys(w(function (keys) { + if (!keys.curvePublic && !keys.formSeed) { + // No drive mode + var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); + noDriveAnswered = answered.indexOf(data.channel) !== -1; + } + myFormKeys = keys; + })); + Cryptpad.makeNetwork(w(function (err, nw) { + network = nw; + })); + Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { + if (md && md.deleteLines) { deleteLines = true; } + })); + }).nThen(function () { + if (!network) { return void cb({error: "E_CONNECT"}); } + if (myFormKeys.formSeed) { + myFormKeys = Cryptpad.getAnonymousKeys(myFormKeys.formSeed, data.channel, Utils); + } + var keys; + var privateKey, publicKey; + var formData; + if (data.drive) { + var secret = Utils.Hash.getSecrets('form', formHref, data.password); + keys = secret && secret.keys; + formData = Utils.Hash.getFormData(secret); + } else { + formData = Utils.Hash.getFormData(Utils.secret); + keys = Utils.secret && Utils.secret.keys; + } + privateKey = formData.form_private; + publicKey = formData.form_public; + var curvePrivate = privateKey || data.privateKey; + if (!curvePrivate) { return void cb({error: 'EFORBIDDEN'}); } + var crypto = Utils.Crypto.Mailbox.createEncryptor({ + curvePrivate: curvePrivate, + curvePublic: publicKey || data.publicKey, + validateKey: data.validateKey + }); + + var config = { + network: network, + channel: data.channel, + noChainPad: true, + validateKey: keys.secondaryValidateKey, + owners: [myKeys.edPublic], + crypto: crypto, + metadata: { + deleteLines: true + } + //Cache: Utils.Cache // TODO enable cache for form responses when the cache stops evicting old answers + }; + var results = {}; + config.onError = function (info) { + cb({ error: info.type }); + }; + config.onRejected = function (data, cb) { + if (!Array.isArray(data) || !data.length || data[0].length !== 16) { + return void cb(true); + } + if (!Array.isArray(accessKeys)) { return void cb(true); } + network.historyKeeper = data[0]; + nThen(function (waitFor) { + accessKeys.forEach(function (obj) { + Pinpad.create(network, obj, waitFor(function (e) { + if (e) { console.error(e); } + })); + }); + }).nThen(function () { + cb(); + }); + }; + config.onReady = function () { + var myKey; + // If we have submitted an anonymous answer, retrieve it + if (myFormKeys.curvePublic && results[myFormKeys.curvePublic]) { + myKey = myFormKeys.curvePublic; + } + cb({ + noDriveAnswered: noDriveAnswered, + myKey: myKey, + results: results + }); + network.disconnect(); + }; + config.onMessage = function (msg, peer, vKey, isCp, hash, senderCurve, cfg) { + var parsed = Utils.Util.tryParse(msg); + if (!parsed) { return; } + var uid = parsed._uid || '000'; + + // If we have a "non-anonymous" answer, it may be the edition of a + // previous anonymous answer. Check if a previous anonymous answer exists + // with the same uid and delete it. + if (parsed._proof) { + var check = checkAnonProof(parsed._proof, data.channel, curvePrivate); + var theirAnonKey = parsed._proof.key; + if (check && results[theirAnonKey] && results[theirAnonKey][uid]) { + delete results[theirAnonKey][uid]; + } + } + + parsed._time = cfg && cfg.time; + if (deleteLines) { parsed._hash = hash; } + + if (data.cantEdit && results[senderCurve] + && results[senderCurve][uid]) { return; } + results[senderCurve] = results[senderCurve] || {}; + results[senderCurve][uid] = { + msg: parsed, + hash: hash, + time: cfg && cfg.time + }; + }; + CPNetflux.start(config); + }); + }); + var noDriveSeed = Utils.Hash.createChannelId(); + sframeChan.on("Q_FORM_SUBMIT", function (data, cb) { + var box = data.mailbox; + var myKeys; + nThen(function (w) { + Cryptpad.getFormKeys(w(function (keys) { + // If formSeed doesn't exists, it means we're probably in noDrive mode. + // We can create a seed in localStorage. + if (!keys.formSeed) { + // No drive mode + keys = { formSeed: noDriveSeed }; + } + myKeys = keys; + })); + }).nThen(function () { + var myAnonymousKeys; + if (data.anonymous) { + if (!myKeys.formSeed) { return void cb({ error: "ANONYMOUS_ERROR" }); } + myKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); + } else { + myAnonymousKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); + } + var keys = Utils.secret && Utils.secret.keys; + myKeys.signingKey = keys.secondarySignKey; + + var ephemeral_keypair = Nacl.box.keyPair(); + var ephemeral_private = Nacl.util.encodeBase64(ephemeral_keypair.secretKey); + myKeys.ephemeral_keypair = ephemeral_keypair; + + if (myAnonymousKeys) { + var proof = anonProof(box.channel, box.publicKey, myAnonymousKeys); + data.results._proof = proof; + } + + var crypto = Utils.Crypto.Mailbox.createEncryptor(myKeys); + var uid = data.results._uid || Utils.Util.uid(); + data.results._uid = uid; + var text = JSON.stringify(data.results); + var ciphertext = crypto.encrypt(text, box.publicKey); + + var hash = ciphertext.slice(0,64); + Cryptpad.anonRpcMsg("WRITE_PRIVATE_MESSAGE", [ + box.channel, + ciphertext + ], function (err, response) { + Cryptpad.storeFormAnswer({ + uid: uid, + channel: box.channel, + hash: hash, + curvePrivate: ephemeral_private, + anonymous: Boolean(data.anonymous) + }, function () { + var res = data.results; + res._isAnon = data.anonymous; + res._time = +new Date(); + if (deleteLines) { res._hash = hash; } + cb({ + error: err, + response: response, + results: res + }); + }); + }); + }); + }); + sframeChan.on("Q_FORM_DELETE_ALL_ANSWERS", function (data, cb) { + if (!data || !data.channel) { return void cb({error: 'EINVAL'}); } + Cryptpad.clearOwnedChannel(data, cb); + }); + sframeChan.on("Q_FORM_DELETE_ANSWER", function (data, cb) { + if (!deleteLines) { + return void cb({error: 'EFORBIDDEN'}); + } + Cryptpad.deleteFormAnswers(data, cb); + }); + sframeChan.on("Q_FORM_MUTE", function (data, cb) { + if (!Utils.secret) { return void cb({error: 'EINVAL'}); } + Cryptpad.muteChannel(Utils.secret.channel, data.muted, cb); + }); + }; + + return Handler; +}); diff --git a/www/form/main.js b/www/form/main.js index aa47abf73..5ec7bc68e 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -56,210 +56,6 @@ define([ }); }); }); - sframeChan.on('EV_EXPORT_SHEET', function (data) { - if (!data || !Array.isArray(data.content)) { return; } - sessionStorage.CP_formExportSheet = JSON.stringify(data); - var href = Utils.Hash.hashToHref('', 'sheet'); - var a = window.open(href); - if (!a) { sframeChan.event('EV_POPUP_BLOCKED'); } - delete sessionStorage.CP_formExportSheet; - }); - var u8_concat = function (A) { - var length = 0; - A.forEach(function (a) { length += a.length; }); - var total = new Uint8Array(length); - - var offset = 0; - A.forEach(function (a) { - total.set(a, offset); - offset += a.length; - }); - return total; - }; - var anonProof = function (channel, theirPub, anonKeys) { - var u8_plain = Nacl.util.decodeUTF8(channel); - var u8_nonce = Nacl.randomBytes(Nacl.box.nonceLength); - var u8_cipher = Nacl.box( - u8_plain, - u8_nonce, - Nacl.util.decodeBase64(theirPub), - Nacl.util.decodeBase64(anonKeys.curvePrivate) - ); - var u8_bundle = u8_concat([ - u8_nonce, // 24 uint8s - u8_cipher, // arbitrary length - ]); - return { - key: anonKeys.curvePublic, - proof: Nacl.util.encodeBase64(u8_bundle) - }; - }; - var deleteLines = false; // "false" to support old forms - sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { - var answers = []; - var myKeys; - nThen(function (w) { - Cryptpad.getFormKeys(w(function (keys) { - myKeys = keys; - })); - Cryptpad.getFormAnswer({channel: data.channel}, w(function (obj) { - if (!obj || obj.error) { - if (obj && obj.error === "ENODRIVE") { - var answered = JSON.parse(localStorage.CP_formAnswered || "[]"); - if (answered.indexOf(data.channel) !== -1) { - cb({error:'EANSWERED'}); - } else { - cb(); - } - return void w.abort(); - } - w.abort(); - return void cb(obj); - } - // Get the latest edit per uid - var temp = {}; - obj.forEach(function (ans) { - var uid = ans.uid || '000'; - temp[uid] = ans; - }); - answers = Object.values(temp); - })); - Cryptpad.getPadMetadata({channel: data.channel}, w(function (md) { - if (md && md.deleteLines) { deleteLines = true; } - })); - }).nThen(function () { - var n = nThen; - var err; - var all = {}; - answers.forEach(function (answer) { - n = n(function(waitFor) { - var finalKeys = myKeys; - if (answer.anonymous) { - if (!myKeys.formSeed) { - err = 'ANONYMOUS_ERROR'; - console.error('ANONYMOUS_ERROR', answer); - return; - } - finalKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, data.channel, Utils); - } - Cryptpad.getHistoryRange({ - channel: data.channel, - lastKnownHash: answer.hash, - toHash: answer.hash, - }, waitFor(function (obj) { - if (obj && obj.error) { err = obj.error; return; } - var messages = obj.messages; - if (!messages.length) { - // TODO delete from drive.forms? - return; - } - if (obj.lastKnownHash !== answer.hash) { return; } - try { - var res = Utils.Crypto.Mailbox.openOwnSecretLetter(messages[0].msg, { - validateKey: data.validateKey, - ephemeral_private: Nacl.util.decodeBase64(answer.curvePrivate), - my_private: Nacl.util.decodeBase64(finalKeys.curvePrivate), - their_public: Nacl.util.decodeBase64(data.publicKey) - }); - var parsed = JSON.parse(res.content); - parsed._isAnon = answer.anonymous; - parsed._time = messages[0].time; - if (deleteLines) { parsed._hash = answer.hash; } - var uid = parsed._uid || '000'; - if (all[uid] && !all[uid]._isAnon) { parsed._isAnon = false; } - all[uid] = parsed; - } catch (e) { - err = e; - } - })); - }).nThen; - }); - n(function () { - if (err) { return void cb({error: err}); } - cb(all); - }); - }); - - }); - var noDriveSeed = Utils.Hash.createChannelId(); - sframeChan.on("Q_FORM_SUBMIT", function (data, cb) { - var box = data.mailbox; - var myKeys; - nThen(function (w) { - Cryptpad.getFormKeys(w(function (keys) { - // If formSeed doesn't exists, it means we're probably in noDrive mode. - // We can create a seed in localStorage. - if (!keys.formSeed) { - // No drive mode - keys = { formSeed: noDriveSeed }; - } - myKeys = keys; - })); - }).nThen(function () { - var myAnonymousKeys; - if (data.anonymous) { - if (!myKeys.formSeed) { return void cb({ error: "ANONYMOUS_ERROR" }); } - myKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); - } else { - myAnonymousKeys = Cryptpad.getAnonymousKeys(myKeys.formSeed, box.channel, Utils); - } - var keys = Utils.secret && Utils.secret.keys; - myKeys.signingKey = keys.secondarySignKey; - - var ephemeral_keypair = Nacl.box.keyPair(); - var ephemeral_private = Nacl.util.encodeBase64(ephemeral_keypair.secretKey); - myKeys.ephemeral_keypair = ephemeral_keypair; - - if (myAnonymousKeys) { - var proof = anonProof(box.channel, box.publicKey, myAnonymousKeys); - data.results._proof = proof; - } - - var crypto = Utils.Crypto.Mailbox.createEncryptor(myKeys); - var uid = data.results._uid || Utils.Util.uid(); - data.results._uid = uid; - var text = JSON.stringify(data.results); - var ciphertext = crypto.encrypt(text, box.publicKey); - - var hash = ciphertext.slice(0,64); - Cryptpad.anonRpcMsg("WRITE_PRIVATE_MESSAGE", [ - box.channel, - ciphertext - ], function (err, response) { - Cryptpad.storeFormAnswer({ - uid: uid, - channel: box.channel, - hash: hash, - curvePrivate: ephemeral_private, - anonymous: Boolean(data.anonymous) - }, function () { - var res = data.results; - res._isAnon = data.anonymous; - res._time = +new Date(); - if (deleteLines) { res._hash = hash; } - cb({ - error: err, - response: response, - results: res - }); - }); - }); - }); - }); - sframeChan.on("Q_FORM_DELETE_ALL_ANSWERS", function (data, cb) { - if (!data || !data.channel) { return void cb({error: 'EINVAL'}); } - Cryptpad.clearOwnedChannel(data, cb); - }); - sframeChan.on("Q_FORM_DELETE_ANSWER", function (data, cb) { - if (!deleteLines) { - return void cb({error: 'EFORBIDDEN'}); - } - Cryptpad.deleteFormAnswers(data, cb); - }); - sframeChan.on("Q_FORM_MUTE", function (data, cb) { - if (!Utils.secret) { return void cb({error: 'EINVAL'}); } - Cryptpad.muteChannel(Utils.secret.channel, data.muted, cb); - }); }; SFCommonO.start({ addData: addData, From a3cd4526d6f7eaca852616180bddd5f21fd1a7cc Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 24 Jan 2025 16:04:41 +0100 Subject: [PATCH 19/22] Revert parsed>parsed.tyoe --- www/common/make-backup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 66767ab2e..ea62b54ca 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -141,7 +141,7 @@ define([ if (cancelled) { return; } if (err) { return; } if (!val) { return; } - transform(ctx, parsed, val, function (res) { + transform(ctx, parsed.type, val, function (res) { if (cancelled) { return; } if (!res.data) { return; } var dl = function () { @@ -237,7 +237,7 @@ define([ var opts = { binary: true, }; - transform(ctx, parsed, val, function (res) { + transform(ctx, parsed.type, val, function (res) { if (ctx.stop) { return; } if (!res.data) { return void error('EEMPTY'); } var fileName = getUnique(sanitize(rawName), res.ext, existingNames); From d618e96d6530f182a1210ae93a34ac06f9607f87 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 24 Jan 2025 16:09:12 +0100 Subject: [PATCH 20/22] Linting --- www/common/cryptpad-common.js | 3 +-- www/common/sframe-common-outer.js | 3 +-- www/form/command-handler.js | 2 +- www/form/main.js | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index c1f4ab64c..894bbad29 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -21,10 +21,9 @@ define([ '/customize/application_config.js', '/components/nthen/index.js', - '/components/tweetnacl/nacl-fast.min.js' ], function (Config, Messages, Util, Hash, Cache, Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, - Cred, Login, AppConfig, Nthen, Nacl) { + Cred, Login, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 5660f5d29..5c1017d89 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -185,7 +185,6 @@ define([ Utils.Block = _Block; Utils.PadTypes = _PadTypes; AppConfig = _AppConfig; - var Nacl = window.nacl; //Test = _Test; if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) { @@ -2075,7 +2074,7 @@ define([ } }); - Handler.fetchFormAnswers(sframeChan, Utils, nThen, Cryptpad, window.nacl) + Handler.fetchFormAnswers(sframeChan, Utils, nThen, Cryptpad, window.nacl); var integrationSave = function () {}; if (cfg.integration) { diff --git a/www/form/command-handler.js b/www/form/command-handler.js index 86258007f..0b6798c52 100644 --- a/www/form/command-handler.js +++ b/www/form/command-handler.js @@ -45,6 +45,7 @@ define([], function () { proof: Nacl.util.encodeBase64(u8_bundle) }; }; + var deleteLines = false; // "false" to support old forms sframeChan.on("Q_FETCH_MY_ANSWERS", function (data, cb) { var answers = []; var myKeys; @@ -152,7 +153,6 @@ define([], function () { return false; } }; - var deleteLines = false; // "false" to support old forms sframeChan.on('Q_FORM_FETCH_ANSWERS', function (data, _cb) { var formHref = data.href; var cb = Utils.Util.once(_cb); diff --git a/www/form/main.js b/www/form/main.js index 5ec7bc68e..3ea04adb8 100644 --- a/www/form/main.js +++ b/www/form/main.js @@ -8,9 +8,7 @@ define([ '/api/config', '/common/dom-ready.js', '/common/sframe-common-outer.js', - '/components/tweetnacl/nacl-fast.min.js', ], function (nThen, ApiConfig, DomReady, SFCommonO) { - var Nacl = window.nacl; var href, hash; // Loaded in load #2 @@ -44,7 +42,7 @@ define([ meta.form_private = formData.form_private; meta.form_auditorHash = formData.form_auditorHash; }; - var addRpc = function (sframeChan, Cryptpad, Utils) { + var addRpc = function (sframeChan, Cryptpad) { sframeChan.on('EV_FORM_PIN', function (data) { channels.answersChannel = data.channel; Cryptpad.changeMetadata(); From 16c3d7c3fa44623559b21df05438c60628ab96c4 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 24 Jan 2025 16:13:12 +0100 Subject: [PATCH 21/22] Changed function name --- www/common/sframe-common-outer.js | 2 +- www/form/command-handler.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 5c1017d89..4cf0dd086 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2074,7 +2074,7 @@ define([ } }); - Handler.fetchFormAnswers(sframeChan, Utils, nThen, Cryptpad, window.nacl); + Handler.formCommandHandlers(sframeChan, Utils, nThen, Cryptpad, window.nacl); var integrationSave = function () {}; if (cfg.integration) { diff --git a/www/form/command-handler.js b/www/form/command-handler.js index 0b6798c52..267620991 100644 --- a/www/form/command-handler.js +++ b/www/form/command-handler.js @@ -6,7 +6,7 @@ define([], function () { var Handler = {}; - Handler.fetchFormAnswers = function(sframeChan, Utils, nThen, Cryptpad, Nacl) { + Handler.formCommandHandlers = function(sframeChan, Utils, nThen, Cryptpad, Nacl) { sframeChan.on('EV_EXPORT_SHEET', function (data) { if (!data || !Array.isArray(data.content)) { return; } sessionStorage.CP_formExportSheet = JSON.stringify(data); From dc8dbc75f9a082f9ce4fd9a87b4f887960900e5d Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Fri, 24 Jan 2025 17:08:44 +0100 Subject: [PATCH 22/22] Corrections --- www/common/cryptpad-common.js | 1 + www/common/sframe-common-outer.js | 11 ++++++----- www/form/command-handler.js | 9 ++++++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 894bbad29..d1e8bad1d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -21,6 +21,7 @@ define([ '/customize/application_config.js', '/components/nthen/index.js', + '/components/tweetnacl/nacl-fast.min.js' ], function (Config, Messages, Util, Hash, Cache, Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, Cred, Login, AppConfig, Nthen) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 4cf0dd086..e345b85fd 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -9,8 +9,7 @@ define([ '/common/requireconfig.js', '/customize/messages.js', 'jquery', - '/form/command-handler.js' -], function (nThen, ApiConfig, RequireConfig, Messages, $, Handler) { +], function (nThen, ApiConfig, RequireConfig, Messages, $) { var common = {}; var embeddableApps = [ @@ -124,6 +123,7 @@ define([ var password, newPadPassword, newPadPasswordForce; var initialPathInDrive; var burnAfterReading; + var Handler; var currentPad = window.CryptPad_location = { app: '', @@ -157,11 +157,11 @@ define([ '/common/userObject.js', 'optional!/api/instance', '/common/pad-types.js', - '/components/tweetnacl/nacl-fast.min.js', + '/form/command-handler.js' ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, _SecureIframe, _UnsafeIframe, _OOIframe, _Notifier, _Hash, _Util, _Realtime, _Notify, _Constants, _Feedback, _LocalStore, _Block, _Cache, _AppConfig, /* _Test,*/ _UserObject, - _Instance, _PadTypes) { + _Instance, _PadTypes, _Handler) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; Crypto = Utils.Crypto = _Crypto; @@ -185,6 +185,7 @@ define([ Utils.Block = _Block; Utils.PadTypes = _PadTypes; AppConfig = _AppConfig; + Handler = _Handler; //Test = _Test; if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) { @@ -2074,7 +2075,7 @@ define([ } }); - Handler.formCommandHandlers(sframeChan, Utils, nThen, Cryptpad, window.nacl); + Handler.formCommandHandlers(sframeChan, Utils, nThen, Cryptpad); var integrationSave = function () {}; if (cfg.integration) { diff --git a/www/form/command-handler.js b/www/form/command-handler.js index 267620991..dc49a9e18 100644 --- a/www/form/command-handler.js +++ b/www/form/command-handler.js @@ -1,12 +1,15 @@ -// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// SPDX-FileCopyrightText: 2025 XWiki CryptPad Team and contributors // // SPDX-License-Identifier: AGPL-3.0-or-later -define([], function () { +define([ + '/components/tweetnacl/nacl-fast.min.js' +], function () { var Handler = {}; - Handler.formCommandHandlers = function(sframeChan, Utils, nThen, Cryptpad, Nacl) { + var Nacl = window.nacl; + Handler.formCommandHandlers = function(sframeChan, Utils, nThen, Cryptpad) { sframeChan.on('EV_EXPORT_SHEET', function (data) { if (!data || !Array.isArray(data.content)) { return; } sessionStorage.CP_formExportSheet = JSON.stringify(data);