diff --git a/bower.json b/bower.json
index 809fd5323..8e6d5cb79 100644
--- a/bower.json
+++ b/bower.json
@@ -29,7 +29,7 @@
"json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0",
- "chainpad-crypto": "^0.1.8",
+ "chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.5.0",
"chainpad": "^5.1.0",
"chainpad-netflux": "^0.7.0",
diff --git a/customize.dist/loading.js b/customize.dist/loading.js
index a2c1dcdfc..c08b82681 100644
--- a/customize.dist/loading.js
+++ b/customize.dist/loading.js
@@ -69,8 +69,9 @@ define([], function () {
height: auto;
margin-bottom: 2em;
}
-@media screen and (max-height: 450px) {
- #cp-loading .cp-loading-cryptofist {
+@media screen and (max-height: 500px) {
+ #cp-loading .cp-loading-logo {
+ display: none;
}
}
#cp-loading-message {
@@ -81,6 +82,43 @@ define([], function () {
text-align: center;
display: none;
}
+#cp-loading-password-prompt {
+ font-size: 18px;
+}
+#cp-loading-password-prompt .cp-password-error {
+ color: white;
+ background: #9e0000;
+ padding: 5px;
+ margin-bottom: 15px;
+}
+#cp-loading-password-prompt .cp-password-info {
+ text-align: left;
+ margin-bottom: 15px;
+}
+#cp-loading-password-prompt .cp-password-form {
+ display: flex;
+ justify-content: space-around;
+ flex-wrap: wrap;
+}
+#cp-loading-password-prompt .cp-password-form button,
+#cp-loading-password-prompt .cp-password-form .cp-password-input {
+ background-color: #4591c4;
+ color: white;
+ border: 1px solid #4591c4;
+}
+#cp-loading-password-prompt .cp-password-form .cp-password-container {
+ flex-shrink: 1;
+ min-width: 0;
+}
+#cp-loading-password-prompt .cp-password-form input {
+ flex: 1;
+ padding: 0 5px;
+ min-width: 0;
+ text-overflow: ellipsis;
+}
+#cp-loading-password-prompt .cp-password-form button:hover {
+ background-color: #326599;
+}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
@@ -114,6 +152,24 @@ define([], function () {
max-width: 60%;
display: inline-block;
}
+.cp-loading-progress {
+ width: 100%;
+ margin: 20px;
+}
+.cp-loading-progress p {
+ margin: 5px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.cp-loading-progress-bar {
+ height: 24px;
+ background: white;
+}
+.cp-loading-progress-bar-value {
+ height: 100%;
+ background: #5cb85c;
+}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
diff --git a/customize.dist/login.js b/customize.dist/login.js
index af5a6391a..4d2855b16 100644
--- a/customize.dist/login.js
+++ b/customize.dist/login.js
@@ -154,7 +154,7 @@ define([
proxy.login_name = uname;
proxy[Constants.displayNameKey] = uname;
sessionStorage.createReadme = 1;
- if (!shouldImport) { proxy.version = 5; }
+ if (!shouldImport) { proxy.version = 6; }
Feedback.send('REGISTRATION', true);
} else {
Feedback.send('LOGIN', true);
diff --git a/customize.dist/pages.js b/customize.dist/pages.js
index 494a1926e..281bdc585 100644
--- a/customize.dist/pages.js
+++ b/customize.dist/pages.js
@@ -72,7 +72,7 @@ define([
])
])
]),
- h('div.cp-version-footer', "CryptPad v2.0.0 (Alpaca)")
+ h('div.cp-version-footer', "CryptPad v2.1.0 (Badger)")
]);
};
diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less
index 7db3f5173..57509455e 100644
--- a/customize.dist/src/less2/include/creation.less
+++ b/customize.dist/src/less2/include/creation.less
@@ -145,16 +145,18 @@
max-height: 100px;
}
}
+
+ input, select {
+ font-size: 14px;
+ border: 1px solid @colortheme_form-border;
+ height: 26px;
+ background-color: @colortheme_form-bg;
+ color: @colortheme_form-color;
+ }
+
.cp-creation-expire {
.cp-creation-expire-picker {
text-align: center;
- input, select {
- font-size: 14px;
- border: 1px solid @colortheme_form-border;
- height: 26px;
- background-color: @colortheme_form-bg;
- color: @colortheme_form-color;
- }
input {
width: 50px;
margin: 0 5px;
@@ -172,6 +174,21 @@
}
}
}
+ .cp-creation-password {
+ .cp-creation-password-picker {
+ text-align: center;
+ width: 100%;
+ .cp-password-container {
+ input {
+ width: 150px;
+ padding: 0 5px;
+ }
+ label {
+ flex: unset;
+ }
+ }
+ }
+ }
.cp-creation-settings {
button {
margin: 0;
diff --git a/customize.dist/src/less2/include/framework.less b/customize.dist/src/less2/include/framework.less
index 9dfab39f6..286870991 100644
--- a/customize.dist/src/less2/include/framework.less
+++ b/customize.dist/src/less2/include/framework.less
@@ -6,6 +6,7 @@
@import (once) './creation.less';
@import (once) './tippy.less';
@import (once) "./checkmark.less";
+@import (once) "./password-input.less";
.framework_main(@bg-color, @warn-color, @color) {
.toolbar_main(
@@ -18,11 +19,13 @@
.tokenfield_main();
.tippy_main();
.checkmark_main(20px);
+ .password_main();
.creation_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
+ font: @colortheme_app-font;
}
.framework_min_main(
@@ -39,6 +42,8 @@
.alertify_main();
.tippy_main();
.checkmark_main(20px);
+ .password_main();
+ font: @colortheme_app-font;
}
diff --git a/customize.dist/src/less2/include/password-input.less b/customize.dist/src/less2/include/password-input.less
new file mode 100644
index 000000000..8836476fd
--- /dev/null
+++ b/customize.dist/src/less2/include/password-input.less
@@ -0,0 +1,13 @@
+.password_main() {
+ .cp-password-container {
+ display: flex;
+ align-items: center;
+ input {
+ flex: 1;
+ min-width: 0;
+ }
+ label, .fa {
+ margin-left: 10px;
+ }
+ }
+}
diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js
index 3454d6242..cf7ddcf8c 100644
--- a/customize.dist/translations/messages.fr.js
+++ b/customize.dist/translations/messages.fr.js
@@ -1081,6 +1081,7 @@ define(function () {
out.creation_expireMonths = "Mois";
out.creation_expire1 = "Un pad illimité ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
out.creation_expire2 = "Un pad à durée de vie sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée.";
+ out.creation_password = "Ajouter un mot de passe";
out.creation_noTemplate = "Pas de modèle";
out.creation_newTemplate = "Nouveau modèle";
out.creation_create = "Créer";
@@ -1092,12 +1093,20 @@ define(function () {
out.creation_ownedByOther = "Appartient à un autre utilisateur";
out.creation_noOwner = "Pas de propriétaire";
out.creation_expiration = "Date d'expiration";
+ out.creation_passwordValue = "Mot de passe";
out.creation_propertiesTitle = "Disponibilité";
out.creation_appMenuName = "Mode avancé (Ctrl + E)";
out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur Tab pour sélectionner un type et appuyer sur Entrée pour valider.";
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur Espace pour changer sa valeur.";
out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads";
+ // Password prompt on the loadind screen
+ out.password_info = "Le pad auquel vous essayez d'accéder est protégé par un mot de passe. Entrez le bon mot de passe pour accéder à son contenu.";
+ out.password_error = "Pad introuvable !
Cette erreur peut provenir de deux facteurs. Soit le mot de passe est faux, soit le pad a été supprimé du serveur.";
+ out.password_placeholder = "Tapez le mot de passe ici...";
+ out.password_submit = "Valider";
+ out.password_show = "Afficher";
+
// New share modal
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";
@@ -1111,5 +1120,12 @@ define(function () {
out.share_embedCategory = "Intégration";
out.share_mediatagCopy = "Copier le mediatag";
+ // Loading info
+ out.loading_pad_1 = "Initialisation du pad";
+ out.loading_pad_2 = "Chargement du contenu du pad";
+ out.loading_drive_1 = "Chargement des données";
+ out.loading_drive_2 = "Mise à jour du format des données";
+ out.loading_drive_3 = "Vérification de l'intégrité des données";
+
return out;
});
diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js
index ddaa1158f..18e295a4c 100644
--- a/customize.dist/translations/messages.js
+++ b/customize.dist/translations/messages.js
@@ -1127,6 +1127,7 @@ define(function () {
out.creation_expireMonths = "Month(s)";
out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it.";
out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
+ out.creation_password = "Add a password";
out.creation_noTemplate = "No template";
out.creation_newTemplate = "New template";
out.creation_create = "Create";
@@ -1138,12 +1139,20 @@ define(function () {
out.creation_ownedByOther = "Owned by another user";
out.creation_noOwner = "No owner";
out.creation_expiration = "Expiration time";
+ out.creation_passwordValue = "Password";
out.creation_propertiesTitle = "Availability";
out.creation_appMenuName = "Advanced mode (Ctrl + E)";
out.creation_newPadModalDescription = "Click on a pad type to create it. You can also press Tab to select the type and press Enter to confirm.";
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
+ // Password prompt on the loadind screen
+ out.password_info = "The pad you're tyring to open is protected with a password. Enter the correct password to access its content.";
+ out.password_error = "Pad not found!
This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server.";
+ out.password_placeholder = "Type the password here...";
+ out.password_submit = "Submit";
+ out.password_show = "Show";
+
// New share modal
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";
@@ -1157,6 +1166,12 @@ define(function () {
out.share_embedCategory = "Embed";
out.share_mediatagCopy = "Copy mediatag to clipboard";
+ // Loading info
+ out.loading_pad_1 = "Initializing pad";
+ out.loading_pad_2 = "Loading pad content";
+ out.loading_drive_1 = "Loading data";
+ out.loading_drive_2 = "Updating data format";
+ out.loading_drive_3 = "Verifying data integrity";
return out;
});
diff --git a/package.json b/package.json
index c58111e31..b74dff06d 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
- "version": "2.0.0",
+ "version": "2.1.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
"chainpad-server": "^2.0.0",
diff --git a/www/assert/main.js b/www/assert/main.js
index eb470eb35..657488b81 100644
--- a/www/assert/main.js
+++ b/www/assert/main.js
@@ -132,6 +132,40 @@ define([
strungJSON(orig);
});
+ HTML_list.forEach(function (sel) {
+ var el = $(sel)[0];
+
+ var pred = function (el) {
+ if (el.nodeName === 'DIV') {
+ return true;
+ }
+ };
+
+ var filter = function (x) {
+ console.log(x);
+ if (x[1]['class']) {
+ x[1]['class'] = x[1]['class'].replace(/cke/g, '');
+ }
+ return x;
+ };
+
+ assert(function (cb) {
+ // FlatDOM output
+ var map = Flat.fromDOM(el, pred, filter);
+
+ // Hyperjson output
+ var hj = Hyperjson.fromDOM(el, pred, filter);
+
+ var x = Flat.toDOM(map);
+ var y = Hyperjson.toDOM(hj);
+
+ console.error(x.outerHTML);
+ console.error(y.outerHTML);
+
+ cb(x.outerHTML === y.outerHTML);
+ }, "Test equality of FlatDOM and HyperJSON");
+ });
+
// check that old hashes parse correctly
assert(function (cb) {
//if (1) { return cb(true); } // TODO(cjd): This is a test failure which is a known bug
@@ -223,6 +257,33 @@ define([
hd.type === 'invite');
}, "test support for invite urls");
+ // test support for V2
+ assert(function (cb) {
+ var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
+ var secret = Hash.getSecrets('pad', '/2/pad/edit/oRE0oLCtEXusRDyin7GyLGcS/');
+ return cb(parsed.hashData.version === 2 &&
+ parsed.hashData.mode === "edit" &&
+ parsed.hashData.type === "pad" &&
+ parsed.hashData.key === "oRE0oLCtEXusRDyin7GyLGcS" &&
+ secret.channel === "d8d51b4aea863f3f050f47f8ad261753" &&
+ window.nacl.util.encodeBase64(secret.keys.cryptKey) === "0Ts1M6VVEozErV2Nx/LTv6Im5SCD7io2LlhasyyBPQo=" &&
+ secret.keys.validateKey === "f5A1FM9Gp55tnOcM75RyHD1oxBG9ZPh9WDA7qe2Fvps=" &&
+ !parsed.hashData.present);
+ }, "test support for version 2 hash failed to parse");
+ assert(function (cb) {
+ var parsed = Hash.parsePadUrl('/pad/#/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed');
+ var secret = Hash.getSecrets('pad', '/2/pad/edit/HGu0tK2od-2BBnwAz2ZNS-t4/p/embed', 'pewpew');
+ return cb(parsed.hashData.version === 2 &&
+ parsed.hashData.mode === "edit" &&
+ parsed.hashData.type === "pad" &&
+ parsed.hashData.key === "HGu0tK2od-2BBnwAz2ZNS-t4" &&
+ secret.channel === "3fb6dc93807d903aff390b5f798c92c9" &&
+ window.nacl.util.encodeBase64(secret.keys.cryptKey) === "EeCkGJra8eJgVu7v4Yl2Hc3yUjrgpKpxr0Lcc3bSWVs=" &&
+ secret.keys.validateKey === "WGkBczJf2V6vQZfAScz8V1KY6jKdoxUCckrD+E75gGE=" &&
+ parsed.hashData.embed &&
+ parsed.hashData.password);
+ }, "test support for password in version 2 hash failed to parse");
+
assert(function (cb) {
var url = '/pad/?utm_campaign=new_comment&utm_medium=email&utm_source=thread_mailer#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI/';
var secret = Hash.parsePadUrl(url);
diff --git a/www/code/inner.js b/www/code/inner.js
index c2e5851bf..0a6b11abc 100644
--- a/www/code/inner.js
+++ b/www/code/inner.js
@@ -334,6 +334,7 @@ define([
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
+ // PASSWORD_FILES
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
diff --git a/www/common/common-hash.js b/www/common/common-hash.js
index 870b19dfe..5ba9ec5b3 100644
--- a/www/common/common-hash.js
+++ b/www/common/common-hash.js
@@ -19,7 +19,50 @@ define([
.decodeUTF8(JSON.stringify(list))));
};
- var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
+ var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) {
+ return secret.channel + secret.key;
+ }
+ if (version === 1) {
+ if (!data.editKeyStr) { return; }
+ return '/1/edit/' + hexToBase64(secret.channel) +
+ '/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
+ }
+ if (version === 2) {
+ if (!data.editKeyStr) { return; }
+ var pass = secret.password ? 'p/' : '';
+ return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
+ }
+ };
+ var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) { return; }
+ if (version === 1) {
+ if (!data.viewKeyStr) { return; }
+ return '/1/view/' + hexToBase64(secret.channel) +
+ '/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
+ }
+ if (version === 2) {
+ if (!data.viewKeyStr) { return; }
+ var pass = secret.password ? 'p/' : '';
+ return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
+ }
+ };
+ var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
+ var version = secret.version;
+ var data = secret.keys;
+ if (version === 0) { return; }
+ if (version === 1) {
+ return '/1/' + hexToBase64(secret.channel) + '/' +
+ Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
+ }
+ };
+
+ // V1
+ /*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
}
@@ -34,7 +77,7 @@ define([
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
- };
+ };*/
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@@ -43,6 +86,24 @@ define([
return s.replace(/\/+/g, '/');
};
+ Hash.createChannelId = function () {
+ var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
+ if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
+ throw new Error('channel ids must consist of 32 hex characters');
+ }
+ return id;
+ };
+
+ Hash.createRandomHash = function (type, password) {
+ var cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
+ return getEditHashFromKeys({
+ password: Boolean(password),
+ version: 2,
+ type: type,
+ keys: { editKeyStr: cryptor.editKeyStr }
+ });
+ };
+
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@@ -56,25 +117,56 @@ Version 1
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
- if (hash.slice(0,1) !== '/' && hash.length >= 56) {
+ if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
+ parsed.getHash = function () { return hash; };
return parsed;
}
- if (hashArr[1] && hashArr[1] === '1') {
+ var options;
+ if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
- parsed.key = hashArr[4].replace(/-/g, '/');
- var options = hashArr.slice(5);
+ parsed.key = Crypto.b64AddSlashes(hashArr[4]);
+
+ options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
+
+ parsed.getHash = function (opts) {
+ var hash = hashArr.slice(0, 5).join('/') + '/';
+ if (opts.embed) { hash += 'embed/'; }
+ if (opts.present) { hash += 'present/'; }
+ return hash;
+ };
+ return parsed;
+ }
+ if (hashArr[1] && hashArr[1] === '2') { // Version 2
+ parsed.version = 2;
+ parsed.app = hashArr[2];
+ parsed.mode = hashArr[3];
+ parsed.key = hashArr[4];
+
+ options = hashArr.slice(5);
+ parsed.password = options.indexOf('p') !== -1;
+ parsed.present = options.indexOf('present') !== -1;
+ parsed.embed = options.indexOf('embed') !== -1;
+
+ parsed.getHash = function (opts) {
+ var hash = hashArr.slice(0, 5).join('/') + '/';
+ if (parsed.password) { hash += 'p/'; }
+ if (opts.embed) { hash += 'embed/'; }
+ if (opts.present) { hash += 'present/'; }
+ return hash;
+ };
return parsed;
}
return parsed;
}
+ parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@@ -125,17 +217,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
- if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
- url += '#/' + ret.hashData.version +
- '/' + ret.hashData.mode +
- '/' + ret.hashData.channel.replace(/\//g, '-') +
- '/' + ret.hashData.key.replace(/\//g, '-') +'/';
- if (options.embed) {
- url += 'embed/';
- }
- if (options.present) {
- url += 'present/';
- }
+ if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
+ var hash = ret.hashData.getHash(options);
+ url += '#' + hash;
return url;
};
@@ -153,12 +237,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
+ if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
- var getRelativeHref = Hash.getRelativeHref = function (href) {
+ Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@@ -170,11 +255,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
- Hash.getSecrets = function (type, secretHash) {
+ Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
- secret.keys = Crypto.createEditCryptor();
- secret.key = Crypto.createEditCryptor().editKeyStr;
+ secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ secret.version = 2;
+ secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@@ -191,7 +278,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
- //var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@@ -203,9 +289,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
- }
- else if (parsed.version === 1) {
+ secret.version = 0;
+ } else if (parsed.version === 1) {
// New hash
+ secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@@ -229,49 +316,63 @@ Version 1
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
+ } else if (parsed.version === 2) {
+ // New hash
+ secret.version = 2;
+ secret.type = type;
+ secret.password = password;
+ if (parsed.type === "pad") {
+ if (parsed.mode === 'edit') {
+ secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ secret.key = secret.keys.editKeyStr;
+ if (secret.channel.length !== 32 || secret.key.length !== 24) {
+ throw new Error("The channel key and/or the encryption key is invalid");
+ }
+ }
+ else if (parsed.mode === 'view') {
+ secret.keys = Crypto.createViewCryptor2(parsed.key, password);
+ secret.channel = base64ToHex(secret.keys.chanId);
+ if (secret.channel.length !== 32) {
+ throw new Error("The channel key is invalid");
+ }
+ }
+ } else if (parsed.type === "file") {
+ throw new Error("File hashes should be version 1");
+ } else if (parsed.type === "user") {
+ throw new Error("User hashes can't be opened (yet)");
+ }
}
}
return secret;
};
- Hash.getHashes = function (channel, secret) {
+ Hash.getHashes = function (secret) {
var hashes = {};
- if (!secret.keys) {
+ secret = JSON.parse(JSON.stringify(secret));
+
+ if (!secret.keys && !secret.key) {
console.error('e');
return hashes;
+ } else if (!secret.keys) {
+ secret.keys = {};
}
- if (secret.keys.editKeyStr) {
- hashes.editHash = getEditHashFromKeys(channel, secret.keys);
+
+ if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
+ hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
- hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
+ hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
- hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
+ hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
- var createChannelId = Hash.createChannelId = function () {
- var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
- if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
- throw new Error('channel ids must consist of 32 hex characters');
- }
- return id;
- };
-
- Hash.createRandomHash = function () {
- // 16 byte channel Id
- var channelId = Util.hexToBase64(createChannelId());
- // 18 byte encryption key
- var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
- return '/1/edit/' + [channelId, key].join('/') + '/';
- };
-
// STORAGE
- Hash.findWeaker = function (href, recents) {
- var rHref = href || getRelativeHref(window.location.href);
- var parsed = parsePadUrl(rHref);
+ Hash.findWeaker = function (href, channel, recents) {
+ var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
var weaker;
Object.keys(recents).some(function (id) {
@@ -279,6 +380,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
+ if (channel !== pad.channel) { return; } // Not the same channel
+
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -287,18 +390,16 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
- if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
- weaker = pad.href;
+ weaker = pad;
return true;
}
return;
});
return weaker;
};
- var findStronger = Hash.findStronger = function (href, recents) {
- var rHref = href || getRelativeHref(window.location.href);
- var parsed = parsePadUrl(rHref);
+ Hash.findStronger = function (href, channel, recents) {
+ var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
@@ -308,6 +409,8 @@ Version 1
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
+ if (channel !== pad.channel) { return; } // Not the same channel
+
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -316,37 +419,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
- if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
- stronger = pad.href;
+ stronger = pad;
return true;
}
return;
});
return stronger;
};
- Hash.isNotStrongestStored = function (href, recents) {
- return findStronger(href, recents);
- };
- Hash.hrefToHexChannelId = function (href) {
+ Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
-
- parsed = parsed.hashData;
- if (parsed.version === 0) {
- return parsed.channel;
- } else if (parsed.version !== 1 && parsed.version !== 2) {
- console.error("parsed href had no version");
- console.error(parsed);
- return;
- }
-
- var channel = parsed.channel;
- if (!channel) { return; }
-
- var hex = base64ToHex(channel);
- return hex;
+ var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
+ return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {
diff --git a/www/common/common-interface.js b/www/common/common-interface.js
index ea2773e37..7f318dfae 100644
--- a/www/common/common-interface.js
+++ b/www/common/common-interface.js
@@ -513,6 +513,50 @@ define([
Alertify.error(Util.fixHTML(msg));
};
+ UI.passwordInput = function (opts, displayEye) {
+ opts = opts || {};
+ var attributes = merge({
+ type: 'password'
+ }, opts);
+
+ var input = h('input.cp-password-input', attributes);
+ var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
+ var eye = h('span.fa.fa-eye.cp-password-reveal');
+
+ $(reveal).find('input').on('change', function () {
+ if($(this).is(':checked')) {
+ $(input).prop('type', 'text');
+ $(input).focus();
+ return;
+ }
+ $(input).prop('type', 'password');
+ $(input).focus();
+ });
+
+ $(eye).mousedown(function () {
+ $(input).prop('type', 'text');
+ $(input).focus();
+ }).mouseup(function(){
+ $(input).prop('type', 'password');
+ $(input).focus();
+ }).mouseout(function(){
+ $(input).prop('type', 'password');
+ $(input).focus();
+ });
+ if (displayEye) {
+ $(reveal).hide();
+ } else {
+ $(eye).hide();
+ }
+
+ return h('span.cp-password-container', [
+ input,
+ reveal,
+ eye
+ ]);
+ };
+
+
/*
* spinner
*/
@@ -546,6 +590,11 @@ define([
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};*/
+ var loading = {
+ error: false,
+ driveState: 0,
+ padState: 0
+ };
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
@@ -554,11 +603,21 @@ define([
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
+ if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
+ var progress = h('div.cp-loading-progress', [
+ h('p.cp-loading-progress-drive'),
+ h('p.cp-loading-progress-pad')
+ ]);
+ $loading.find('.cp-loading-container').append(progress);
+ } else if (config.noProgress) {
+ $loading.find('.cp-loading-progress').remove();
+ }
if (loadingText) {
- $('#' + LOADING).find('p').show().text(loadingText);
+ $('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else {
- $('#' + LOADING).find('p').hide().text('');
+ $('#' + LOADING).find('#cp-loading-message').hide().text('');
}
+ loading.error = false;
};
if ($('#' + LOADING).length) {
todo();
@@ -567,6 +626,58 @@ define([
todo();
}
};
+ UI.updateLoadingProgress = function (data, isDrive) {
+ var $loading = $('#' + LOADING);
+ if (!$loading.length || loading.error) { return; }
+ var $progress;
+ if (isDrive) {
+ // Drive state
+ if (loading.driveState === -1) { return; } // Already loaded
+ $progress = $loading.find('.cp-loading-progress-drive');
+ if (!$progress.length) { return; } // Can't find the box to display data
+
+ // If state is -1, remove the box, drive is loaded
+ if (data.state === -1) {
+ loading.driveState = -1;
+ $progress.remove();
+ } else {
+ if (data.state < loading.driveState) { return; } // We should not display old data
+ // Update the current state
+ loading.driveState = data.state;
+ data.progress = data.progress || 100;
+ data.msg = Messages['loading_drive_'+data.state] || '';
+ $progress.html(data.msg);
+ if (data.progress) {
+ $progress.append(h('div.cp-loading-progress-bar', [
+ h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
+ ]));
+ }
+ }
+ } else {
+ // Pad state
+ if (loading.padState === -1) { return; } // Already loaded
+ $progress = $loading.find('.cp-loading-progress-pad');
+ if (!$progress.length) { return; } // Can't find the box to display data
+
+ // If state is -1, remove the box, pad is loaded
+ if (data.state === -1) {
+ loading.padState = -1;
+ $progress.remove();
+ } else {
+ if (data.state < loading.padState) { return; } // We should not display old data
+ // Update the current state
+ loading.padState = data.state;
+ data.progress = data.progress || 100;
+ data.msg = Messages['loading_pad_'+data.state] || '';
+ $progress.html(data.msg);
+ if (data.progress) {
+ $progress.append(h('div.cp-loading-progress-bar', [
+ h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
+ ]));
+ }
+ }
+ }
+ };
UI.removeLoadingScreen = function (cb) {
// Release the test blocker, hopefully every test has been registered.
// This test is created in sframe-boot2.js
@@ -575,6 +686,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
+ loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@@ -588,18 +700,27 @@ define([
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
- if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
+ var $loading = $('#' + LOADING);
+ if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
+ loading.error = true;
+ $loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
- if (transparent) { $('#' + LOADING).css('opacity', 0.9); }
- $('#' + LOADING).find('p').show().html(error || Messages.error);
+ if (transparent) { $loading.css('opacity', 0.9); }
+ var $error = $loading.find('#cp-loading-message').show();
+ if (error instanceof Element) {
+ $error.html('').append(error);
+ } else {
+ $error.html(error || Messages.error);
+ }
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
- $('#' + LOADING).hide();
+ $loading.hide();
+ loading.error = false;
if (typeof(exitable) === "function") { exitable(); }
}
});
@@ -659,12 +780,14 @@ define([
}
},
//arrowType: 'round',
+ dynamicTitle: true,
arrowTransform: 'scale(2)',
zIndex: 100000001
});
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var addTippy = function (i, el) {
+ if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
var opts = {
distance: 15
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index 13d132219..b5a12da0b 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -99,7 +99,7 @@ define([
try {
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
- var chan = parsed.hashData.channel;
+ var chan = Hash.hrefToHexChannelId(window.location.href);
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
@@ -113,7 +113,7 @@ define([
if (!decryptMsg) { return; }
// Parse
msg = JSON.parse(decryptMsg);
- if (msg[1] !== parsed.hashData.channel) { return; }
+ if (msg[1] !== chan) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
@@ -199,7 +199,7 @@ define([
var parsed = Hash.parsePadUrl(data.href);
if (!parsed.hashData) { return; }
// Message
- var chan = parsed.hashData.channel;
+ var chan = Hash.hrefToHexChannelId(data.href);
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js
index 455b095c9..ab6d3e6d4 100644
--- a/www/common/common-thumbnail.js
+++ b/www/common/common-thumbnail.js
@@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
- Thumb.setPadThumbnail(common, opts.href, b64);
+ Thumb.setPadThumbnail(common, opts.href, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@@ -240,20 +240,22 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
- var getKey = function (href) {
- var parsed = Hash.parsePadUrl(href);
- return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
+ var getKey = function (type, channel) {
+ return 'thumbnail-' + type + '-' + channel;
};
- Thumb.setPadThumbnail = function (common, href, b64, cb) {
+ Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {};
- var k = getKey(href);
+ var parsed = Hash.parsePadUrl(href);
+ channel = channel || common.getMetadataMgr().getPrivateData().channel;
+ var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb);
};
- Thumb.displayThumbnail = function (common, href, $container, cb) {
+ Thumb.displayThumbnail = function (common, href, channel, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
- var k = getKey(href);
+ var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
+ // PASSWORD_FILES
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var src = Hash.getBlobPathFromHex(hexFileName);
@@ -270,7 +272,7 @@ define([
if (!v) {
v = 'EMPTY';
}
- Thumb.setPadThumbnail(common, href, v, function (err) {
+ Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 2b9487a73..e085ea3a3 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -60,6 +60,10 @@ define([
var getPropertiesData = function (common, cb) {
var data = {};
NThen(function (waitFor) {
+ common.getPadAttribute('password', waitFor(function (err, val) {
+ data.password = val;
+ }));
+ }).nThen(function (waitFor) {
common.getPadAttribute('href', waitFor(function (err, val) {
var base = common.getMetadataMgr().getPrivateData().origin;
@@ -71,15 +75,19 @@ define([
// We're not in a read-only pad
data.href = base + val;
+
// Get Read-only href
if (parsed.hashData.type !== "pad") { return; }
var i = data.href.indexOf('#') + 1;
var hBase = data.href.slice(0, i);
- var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash);
+ var hrefsecret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
if (!hrefsecret.keys) { return; }
- var viewHash = Hash.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
+ var viewHash = Hash.getViewHashFromKeys(hrefsecret);
data.roHref = hBase + viewHash;
}));
+ common.getPadAttribute('channel', waitFor(function (err, val) {
+ data.channel = val;
+ }));
common.getPadAttribute('atime', waitFor(function (err, val) {
data.atime = val;
}));
@@ -135,6 +143,22 @@ define([
$d.append(UI.dialog.selectable(expire, {
id: 'cp-app-prop-expire',
}));
+
+ if (typeof data.password !== "undefined") {
+ $('