Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
This commit is contained in:
@@ -92,8 +92,10 @@
|
||||
* {
|
||||
max-width:100%;
|
||||
}
|
||||
iframe[type="application/pdf"] {
|
||||
max-height:50vh;
|
||||
iframe[src$=".pdf"] {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
.markdown_main();
|
||||
|
||||
101
www/common/common-credential.js
Normal file
101
www/common/common-credential.js
Normal file
@@ -0,0 +1,101 @@
|
||||
(function () {
|
||||
var factory = function (AppConfig, Scrypt) {
|
||||
var Cred = {};
|
||||
|
||||
Cred.MINIMUM_PASSWORD_LENGTH = typeof(AppConfig.minimumPasswordLength) === 'number'?
|
||||
AppConfig.minimumPasswordLength: 8;
|
||||
|
||||
// https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
|
||||
Cred.isEmail = function (email) {
|
||||
var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
return re.test(String(email).toLowerCase());
|
||||
};
|
||||
|
||||
Cred.isLongEnoughPassword = function (passwd) {
|
||||
return passwd.length >= Cred.MINIMUM_PASSWORD_LENGTH;
|
||||
};
|
||||
|
||||
var isString = Cred.isString = function (x) {
|
||||
return typeof(x) === 'string';
|
||||
};
|
||||
|
||||
Cred.isValidUsername = function (name) {
|
||||
return !!(name && isString(name));
|
||||
};
|
||||
|
||||
Cred.isValidPassword = function (passwd) {
|
||||
return !!(passwd && isString(passwd));
|
||||
};
|
||||
|
||||
Cred.passwordsMatch = function (a, b) {
|
||||
return isString(a) && isString(b) && a === b;
|
||||
};
|
||||
|
||||
Cred.customSalt = function () {
|
||||
return typeof(AppConfig.loginSalt) === 'string'?
|
||||
AppConfig.loginSalt: '';
|
||||
};
|
||||
|
||||
Cred.deriveFromPassphrase = function (username, password, len, cb) {
|
||||
Scrypt(password,
|
||||
username + Cred.customSalt(), // salt
|
||||
8, // memoryCost (n)
|
||||
1024, // block size parameter (r)
|
||||
len || 128, // dkLen
|
||||
200, // interruptStep
|
||||
cb,
|
||||
undefined); // format, could be 'base64'
|
||||
};
|
||||
|
||||
Cred.dispenser = function (bytes) {
|
||||
var entropy = {
|
||||
used: 0,
|
||||
};
|
||||
|
||||
// crypto hygeine
|
||||
var consume = function (n) {
|
||||
// explode if you run out of bytes
|
||||
if (entropy.used + n > bytes.length) {
|
||||
throw new Error('exceeded available entropy');
|
||||
}
|
||||
if (typeof(n) !== 'number') { throw new Error('expected a number'); }
|
||||
if (n <= 0) {
|
||||
throw new Error('expected to consume a positive number of bytes');
|
||||
}
|
||||
|
||||
// grab an unused slice of the entropy
|
||||
// Note: Internet Explorer doesn't support .slice on Uint8Array
|
||||
var A;
|
||||
if (bytes.slice) {
|
||||
A = bytes.slice(entropy.used, entropy.used + n);
|
||||
} else {
|
||||
A = bytes.subarray(entropy.used, entropy.used + n);
|
||||
}
|
||||
|
||||
// account for the bytes you used so you don't reuse bytes
|
||||
entropy.used += n;
|
||||
|
||||
//console.info("%s bytes of entropy remaining", bytes.length - entropy.used);
|
||||
return A;
|
||||
};
|
||||
|
||||
return consume;
|
||||
};
|
||||
|
||||
return Cred;
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
{}, //require("../../customize.dist/application_config.js"),
|
||||
require("../bower_components/scrypt-async/scrypt-async.min.js")
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/scrypt-async/scrypt-async.min.js',
|
||||
], function (AppConfig) {
|
||||
return factory(AppConfig, window.scrypt);
|
||||
});
|
||||
}
|
||||
}());
|
||||
@@ -530,7 +530,7 @@ Version 1
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl"));
|
||||
module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl/nacl-fast"));
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
window.atob = window.atob || function (str) { return Buffer.from(str, 'base64').toString('binary'); }; // jshint ignore:line
|
||||
window.btoa = window.btoa || function (str) { return new Buffer(str, 'binary').toString('base64'); }; // jshint ignore:line
|
||||
|
||||
Util.slice = function (A, start, end) {
|
||||
return Array.prototype.slice.call(A, start, end);
|
||||
};
|
||||
|
||||
Util.bake = function (f, args) {
|
||||
if (typeof(args) === 'undefined') { args = []; }
|
||||
if (!Array.isArray(args)) { args = [args]; }
|
||||
@@ -265,7 +269,7 @@
|
||||
var to;
|
||||
var g = function () {
|
||||
window.clearTimeout(to);
|
||||
to = window.setTimeout(f, ms);
|
||||
to = window.setTimeout(Util.bake(f, Util.slice(arguments)), ms);
|
||||
};
|
||||
return g;
|
||||
};
|
||||
|
||||
@@ -1016,7 +1016,7 @@ define([
|
||||
var Cred, Block, Login;
|
||||
Nthen(function (waitFor) {
|
||||
require([
|
||||
'/customize/credential.js',
|
||||
'/common/common-credential.js',
|
||||
'/common/outer/login-block.js',
|
||||
'/customize/login.js'
|
||||
], waitFor(function (_Cred, _Block, _Login) {
|
||||
|
||||
95
www/common/outer/invitation.js
Normal file
95
www/common/outer/invitation.js
Normal file
@@ -0,0 +1,95 @@
|
||||
(function () {
|
||||
var factory = function (Util, Cred, nThen) {
|
||||
nThen = nThen; // XXX
|
||||
var Invite = {};
|
||||
|
||||
/*
|
||||
TODO key derivation
|
||||
|
||||
scrypt(seed, passwd) => {
|
||||
curve: {
|
||||
private,
|
||||
public,
|
||||
},
|
||||
ed: {
|
||||
private,
|
||||
public,
|
||||
}
|
||||
cryptKey,
|
||||
channel
|
||||
}
|
||||
*/
|
||||
|
||||
var BYTES_REQUIRED = 256;
|
||||
|
||||
Invite.deriveKeys = function (seed, passwd, cb) {
|
||||
cb = cb; // XXX
|
||||
// TODO validate has cb
|
||||
// TODO onceAsync the cb
|
||||
// TODO cb with err if !(seed && passwd)
|
||||
|
||||
Cred.deriveFromPassphrase(seed, passwd, BYTES_REQUIRED, function (bytes) {
|
||||
var dispense = Cred.dispenser(bytes);
|
||||
dispense = dispense; // XXX
|
||||
|
||||
// edPriv => edPub
|
||||
// curvePriv => curvePub
|
||||
// channel
|
||||
// cryptKey
|
||||
});
|
||||
};
|
||||
|
||||
Invite.createSeed = function () {
|
||||
// XXX
|
||||
// return a seed
|
||||
};
|
||||
|
||||
Invite.create = function (cb) {
|
||||
cb = cb; // XXX
|
||||
// TODO validate has cb
|
||||
// TODO onceAsync the cb
|
||||
// TODO cb with err if !(seed && passwd)
|
||||
|
||||
|
||||
|
||||
// required
|
||||
// password
|
||||
// validateKey
|
||||
// creatorEdPublic
|
||||
// for owner
|
||||
// ephemeral
|
||||
// signingKey
|
||||
// for owner to write invitation
|
||||
// derived
|
||||
// edPriv
|
||||
// edPublic
|
||||
// for invitee ownership
|
||||
// curvePriv
|
||||
// curvePub
|
||||
// for acceptance OR
|
||||
// authenticated decline message via mailbox
|
||||
// channel
|
||||
// for owned deletion
|
||||
// for team pinning
|
||||
// cryptKey
|
||||
// for protecting channel content
|
||||
};
|
||||
|
||||
return Invite;
|
||||
};
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(
|
||||
require("../common-util"),
|
||||
require("../common-credential.js"),
|
||||
require("nthen")
|
||||
);
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-credential.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
], function (Util, Cred, nThen) {
|
||||
return factory(Util, nThen);
|
||||
});
|
||||
}
|
||||
}());
|
||||
@@ -171,6 +171,10 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
// if no role was provided, assume MEMBER
|
||||
if (typeof(data.role) !== 'string') { data.role = 'MEMBER'; }
|
||||
|
||||
if (!canAddRole(author, data.role, members)) {
|
||||
throw new Error("INSUFFICIENT_PERMISSIONS");
|
||||
}
|
||||
|
||||
if (typeof(data.displayName) !== 'string') { throw new Error("DISPLAYNAME_REQUIRED"); }
|
||||
if (typeof(data.notifications) !== 'string') { throw new Error("NOTIFICATIONS_REQUIRED"); }
|
||||
});
|
||||
@@ -178,12 +182,9 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
var changed = false;
|
||||
// then iterate again and apply it
|
||||
Object.keys(args).forEach(function (curve) {
|
||||
var data = args[curve];
|
||||
if (!canAddRole(author, data.role, members)) { return; }
|
||||
|
||||
// this will result in a change
|
||||
changed = true;
|
||||
members[curve] = data;
|
||||
members[curve] = args[curve];
|
||||
});
|
||||
|
||||
return changed;
|
||||
@@ -241,11 +242,26 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
|
||||
var current = Util.clone(members[curve]);
|
||||
|
||||
if (typeof(data.role) === 'string') { // they're trying to change the role...
|
||||
// throw if they're trying to upgrade to something greater
|
||||
if (!canAddRole(author, data.role, members)) { throw new Error("INSUFFICIENT_PERMISSIONS"); }
|
||||
}
|
||||
// DESCRIBE commands must initialize a displayName if it isn't already present
|
||||
if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') { throw new Error('DISPLAYNAME_REQUIRED'); }
|
||||
if (typeof(current.displayName) !== 'string' && typeof(data.displayName) !== 'string') {
|
||||
throw new Error('DISPLAYNAME_REQUIRED');
|
||||
}
|
||||
|
||||
if (['undefined', 'string'].indexOf(typeof(data.displayName)) === -1) {
|
||||
throw new Error("INVALID_DISPLAYNAME");
|
||||
}
|
||||
|
||||
// DESCRIBE commands must initialize a mailbox channel if it isn't already present
|
||||
if (typeof(current.notifications) !== 'string' && typeof(data.displayName) !== 'string') { throw new Error('NOTIFICATIONS_REQUIRED'); }
|
||||
if (typeof(current.notifications) !== 'string' && typeof(data.notifications) !== 'string') {
|
||||
throw new Error('NOTIFICATIONS_REQUIRED');
|
||||
}
|
||||
if (['undefined', 'string'].indexOf(typeof(data.notifications)) === -1) {
|
||||
throw new Error("INVALID_NOTIFICATIONS");
|
||||
}
|
||||
});
|
||||
|
||||
var changed = false;
|
||||
@@ -256,7 +272,9 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
var data = args[curve];
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (current[key] === data[key]) { return; }
|
||||
// when null is passed as new data and it wasn't considered an invalid change
|
||||
// remove it from the map. This is how you delete things properly
|
||||
if (typeof(current[key]) !== 'undefined' && data[key] === null) { return void delete current[key]; }
|
||||
current[key] = data[key];
|
||||
});
|
||||
|
||||
@@ -305,6 +323,12 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
return true;
|
||||
};
|
||||
|
||||
var MANDATORY_METADATA_FIELDS = [
|
||||
'avatar',
|
||||
'name',
|
||||
'topic',
|
||||
];
|
||||
|
||||
// only admin/owner can change group metadata
|
||||
commands.METADATA = function (args, author, roster) {
|
||||
if (!isMap(args)) { throw new Error("INVALID_ARGS"); }
|
||||
@@ -313,6 +337,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
|
||||
// validate inputs
|
||||
Object.keys(args).forEach(function (k) {
|
||||
if (args[k] === null) {
|
||||
if (MANDATORY_METADATA_FIELDS.indexOf(k) === -1) { return; }
|
||||
throw new Error('CANNOT_REMOVE_MANDATORY_METADATA');
|
||||
}
|
||||
|
||||
// can't set metadata to anything other than strings
|
||||
// use empty string to unset a value if you must
|
||||
if (typeof(args[k]) !== 'string') { throw new Error("INVALID_ARGUMENTS"); }
|
||||
@@ -321,6 +350,11 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
var changed = false;
|
||||
// {topic, name, avatar} are all strings...
|
||||
Object.keys(args).forEach(function (k) {
|
||||
if (typeof(roster.state.metadata[k]) !== 'undefined' && args[k] === null) {
|
||||
changed = true;
|
||||
delete roster.state.metadata[k];
|
||||
}
|
||||
|
||||
// ignore things that won't cause changes
|
||||
if (args[k] === roster.state.metadata[k]) { return; }
|
||||
|
||||
@@ -608,14 +642,19 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
|
||||
if (!isMap(_data)) { return void cb("INVALID_ARGUMENTS"); }
|
||||
var data = Util.clone(_data);
|
||||
|
||||
Object.keys(data).forEach(function (curve) {
|
||||
if (Object.keys(data).some(function (curve) {
|
||||
var member = data[curve];
|
||||
if (!isMap(member)) { delete data[curve]; }
|
||||
// validate that you're trying to describe a user that is present
|
||||
if (!isMap(state.members[curve])) { return true; }
|
||||
// don't send fields that won't result in a change
|
||||
Object.keys(member).forEach(function (k) {
|
||||
if (member[k] === state.members[curve][k]) { delete member[k]; }
|
||||
});
|
||||
});
|
||||
})) {
|
||||
// returning true in the above loop indicates that something was invalid
|
||||
return void cb("INVALID_ARGUMENTS");
|
||||
}
|
||||
|
||||
send(['DESCRIBE', data], cb);
|
||||
};
|
||||
|
||||
@@ -404,7 +404,7 @@ var factory = function (Util, Nacl) {
|
||||
};
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory(require("./common-util"), require("tweetnacl"));
|
||||
module.exports = factory(require("./common-util"), require("tweetnacl/nacl-fast"));
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([
|
||||
'/common/common-util.js',
|
||||
|
||||
@@ -3,7 +3,7 @@ define([
|
||||
'/customize/login.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/test.js',
|
||||
'/customize/credential.js', // preloaded for login.js
|
||||
'/common/common-credential.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-realtime.js',
|
||||
|
||||
@@ -9,7 +9,7 @@ define([
|
||||
'/common/common-hash.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/credential.js',
|
||||
'/common/common-credential.js',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/common/make-backup.js',
|
||||
|
||||
@@ -236,6 +236,10 @@ define([
|
||||
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
|
||||
var sFrameChan = common.getSframeChannel();
|
||||
sFrameChan.onReady(waitFor());
|
||||
common.getPinUsage(null, waitFor(function (err, data) {
|
||||
if (err) { return void console.error(err); }
|
||||
APP.pinUsage = data;
|
||||
}));
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
createToolbar();
|
||||
metadataMgr = common.getMetadataMgr();
|
||||
@@ -244,7 +248,7 @@ define([
|
||||
|
||||
APP.origin = privateData.origin;
|
||||
APP.readOnly = privateData.readOnly;
|
||||
APP.support = Support.create(common, false);
|
||||
APP.support = Support.create(common, false, APP.pinUsage);
|
||||
|
||||
// Content
|
||||
var $rightside = APP.$rightside;
|
||||
|
||||
@@ -25,6 +25,14 @@ define([
|
||||
edPublic: privateData.edPublic,
|
||||
notifications: user.notifications,
|
||||
};
|
||||
|
||||
if (typeof(ctx.pinUsage) === 'object') {
|
||||
// pass pin.usage, pin.limit, and pin.plan if supplied
|
||||
Object.keys(ctx.pinUsage).forEach(function (k) {
|
||||
data.sender[k] = ctx.pinUsage[k];
|
||||
});
|
||||
}
|
||||
|
||||
data.id = id;
|
||||
data.time = +new Date();
|
||||
|
||||
@@ -169,6 +177,8 @@ define([
|
||||
]);
|
||||
$(userData).click(function () {
|
||||
$(userData).find('pre').toggle();
|
||||
}).find('pre').click(function (ev) {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
|
||||
var name = Util.fixHTML(content.sender.name) || Messages.anonymous;
|
||||
@@ -202,11 +212,12 @@ define([
|
||||
]);
|
||||
};
|
||||
|
||||
var create = function (common, isAdmin) {
|
||||
var create = function (common, isAdmin, pinUsage) {
|
||||
var ui = {};
|
||||
var ctx = {
|
||||
common: common,
|
||||
isAdmin: isAdmin
|
||||
isAdmin: isAdmin,
|
||||
pinUsage: pinUsage || false,
|
||||
};
|
||||
|
||||
ui.sendForm = function (id, form, dest) {
|
||||
|
||||
Reference in New Issue
Block a user