Merge branch 'restricted-registration' into staging

pull/1/head
ansuz 4 years ago
commit 885d6c4dc7

@ -392,7 +392,7 @@ define([
// send an RPC to store the block which you created.
console.log("initializing rpc interface");
Pinpad.create(RT.network, RT.proxy, waitFor(function (e, _rpc) {
Pinpad.create(RT.network, Block.keysToRPCFormat(res.opt.blockKeys), waitFor(function (e, _rpc) {
if (e) {
waitFor.abort();
console.error(e); // INVALID_KEYS
@ -414,7 +414,10 @@ define([
var blockRequest = Block.serialize(JSON.stringify(toPublish), res.opt.blockKeys);
rpc.writeLoginBlock(blockRequest, waitFor(function (e) {
if (e) { return void console.error(e); }
if (e) {
console.error(e);
return void cb(e);
}
console.log("blockInfo available at:", blockHash);
LocalStore.setBlockHash(blockHash);
@ -531,6 +534,9 @@ define([
});
});
break;
case 'E_RESTRICTED':
UI.errorLoadingScreen(Messages.register_registrationIsClosed);
break;
default: // UNHANDLED ERROR
hashing = false;
UI.errorLoadingScreen(Messages.login_unhandledError);

@ -4,7 +4,8 @@ define([
'/customize/application_config.js',
'/customize/messages.js',
'jquery',
], function (h, Language, AppConfig, Msg, $) {
'/api/config',
], function (h, Language, AppConfig, Msg, $, ApiConfig) {
var Pages = {};
Pages.setHTML = function (e, html) {
@ -156,10 +157,16 @@ define([
Pages.infopageTopbar = function () {
var rightLinks;
var username = window.localStorage.getItem('User_name');
var registerLink;
if (!ApiConfig.restrictRegistration) {
registerLink = h('a.nav-item.nav-link.cp-register-btn', { href: '/register/'}, Msg.login_register);
}
if (username === null) {
rightLinks = [
h('a.nav-item.nav-link.cp-login-btn', { href: '/login/'}, Msg.login_login),
h('a.nav-item.nav-link.cp-register-btn', { href: '/register/'}, Msg.login_register)
registerLink,
];
} else {
rightLinks = h('a.nav-item.nav-link.cp-user-btn', { href: '/drive/' }, [

@ -146,7 +146,7 @@ define([
]),
]);
var availableFeatures =
(Config.allowSubscriptions && accounts.upgradeURL) ?
(Config.allowSubscriptions && accounts.upgradeURL && !Config.restrictRegistration) ?
[anonymousFeatures, registeredFeatures, premiumFeatures] :
[anonymousFeatures, registeredFeatures];

@ -2,8 +2,9 @@ define([
'/common/hyperscript.js',
'/common/common-interface.js',
'/customize/messages.js',
'/customize/pages.js'
], function (h, UI, Msg, Pages) {
'/customize/pages.js',
'/api/config',
], function (h, UI, Msg, Pages, Config) {
return function () {
return [h('div#cp-main', [
Pages.infopageTopbar(),
@ -32,7 +33,10 @@ define([
]),
h('div.extra', [
h('button.login', Msg.login_login),
h('button#register.cp-secondary', Msg.login_register)
(Config.restrictRegistration?
undefined:
h('button#register.cp-secondary', Msg.login_register)
)
])
]),
h('div.col-md-3')

@ -16,11 +16,30 @@ define([
tabindex: '-1',
});
return [h('div#cp-main', [
Pages.infopageTopbar(),
h('div.container.cp-container', [
h('div.row.cp-page-title', h('h1', Msg.register_header)),
h('div.row.cp-register-det', [
var frame = function (content) {
return [
h('div#cp-main', [
Pages.infopageTopbar(),
h('div.container.cp-container', [
h('div.row.cp-page-title', h('h1', Msg.register_header)),
//h('div.row.cp-register-det', content),
].concat(content)),
]),
Pages.infopageFooter(),
];
};
if (Config.restrictRegistration) {
return frame([
h('div.cp-restricted-registration', [
h('p', Msg.register_registrationIsClosed),
])
]);
}
return frame([
h('div.row.cp-register-det', [
h('div#data.hidden.col-md-6', [
h('h2', Msg.register_notes_title),
Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
@ -59,11 +78,8 @@ define([
h('button#register', Msg.login_register)
])
]),
]),
]),
Pages.infopageFooter(),
])];
])
]);
};
});

@ -52,6 +52,9 @@
}
}
.cp-restricted-registration {
text-align: center !important;
}
.cp-register-det {
#data {

@ -50,7 +50,6 @@ $(function () {
} else if (/^\/login\//.test(pathname)) {
require([ '/login/main.js' ], function () {});
} else if (/^\/($|^\/index\.html$)/.test(pathname)) {
// TODO use different top bar
require([ '/customize/main.js', ], function () {});
} else {
require([ '/customize/main.js', ], function () {});

@ -86,47 +86,122 @@ var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
};
Block.writeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
Block.validateAncestorProof = function (Env, proof, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
/* prove that you own an existing block by signing for its publicKey */
try {
var parsed = JSON.parse(proof);
var pub = parsed[0];
var u8_pub = Nacl.util.decodeBase64(pub);
var sig = parsed[1];
var u8_sig = Nacl.util.decodeBase64(sig);
var valid = false;
nThen(function (w) {
valid = Nacl.sign.detached.verify(u8_pub, u8_sig, u8_pub);
if (!valid) {
w.abort();
return void cb('E_INVALID_ANCESTOR_PROOF');
}
// else fall through to next step
}).nThen(function (w) {
var path = createLoginBlockPath(Env, pub);
Fs.access(path, Fs.constants.F_OK, w(function (err) {
if (!err) { return; }
w.abort(); // else
return void cb("E_MISSING_ANCESTOR");
}));
}).nThen(function () {
cb(void 0, pub);
});
} catch (err) {
return void cb(err);
}
};
Block.writeLoginBlock = function (Env, safeKey, msg, _cb) { // FIXME BLOCKS
var cb = Util.once(Util.mkAsync(_cb));
//console.log(msg);
var publicKey = msg[0];
var signature = msg[1];
var block = msg[2];
validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) {
if (e) { return void cb(e); }
if (!(validatedBlock instanceof Uint8Array)) { return void cb('E_INVALID_BLOCK'); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
var registrationProof = msg[3];
var previousKey;
var validatedBlock, parsed, path;
nThen(function (w) {
if (Util.escapeKeyCharacters(publicKey) !== safeKey) {
w.abort();
return void cb("INCORRECT_KEY");
}
var parsed = Path.parse(path);
if (!parsed || typeof(parsed.dir) !== 'string') {
return void cb("E_INVALID_BLOCK_PATH_2");
}).nThen(function (w) {
if (!Env.restrictRegistration) { return; }
if (!registrationProof) {
// we allow users with existing blocks to create new ones
// call back with error if registration is restricted and no proof of an existing block was provided
w.abort();
Env.Log.info("BLOCK_REJECTED_REGISTRATION", {
safeKey: safeKey,
publicKey: publicKey,
});
return cb("E_RESTRICTED");
}
nThen(function (w) {
// make sure the path to the file exists
Fse.mkdirp(parsed.dir, w(function (e) {
if (e) {
w.abort();
cb(e);
}
}));
}).nThen(function () {
// actually write the block
// flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(validatedBlock) === 'undefined') { throw new Error('should never happen'); } */
/*:: if (typeof(path) === 'undefined') { throw new Error('should never happen'); } */
Fs.writeFile(path, Buffer.from(validatedBlock), { encoding: "binary", }, function (err) {
if (err) { return void cb(err); }
cb();
Env.validateAncestorProof(registrationProof, w(function (err, provenKey) {
if (err || !provenKey) { // double check that a key was validated
w.abort();
Env.Log.warn('BLOCK_REJECTED_INVALID_ANCESTOR', {
error: err,
});
return void cb("E_RESTRICTED");
}
previousKey = provenKey;
}));
}).nThen(function (w) {
validateLoginBlock(Env, publicKey, signature, block, w(function (e, _validatedBlock) {
if (e) {
w.abort();
return void cb(e);
}
if (!(_validatedBlock instanceof Uint8Array)) {
w.abort();
return void cb('E_INVALID_BLOCK');
}
validatedBlock = _validatedBlock;
// derive the filepath
path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
parsed = Path.parse(path);
if (!parsed || typeof(parsed.dir) !== 'string') {
w.abort();
return void cb("E_INVALID_BLOCK_PATH_2");
}
}));
}).nThen(function (w) {
// make sure the path to the file exists
Fse.mkdirp(parsed.dir, w(function (e) {
if (e) {
w.abort();
cb(e);
}
}));
}).nThen(function () {
// actually write the block
Fs.writeFile(path, Buffer.from(validatedBlock), { encoding: "binary", }, function (err) {
if (err) { return void cb(err); }
Env.Log.info('BLOCK_WRITE_BY_OWNER', {
safeKey: safeKey,
blockId: publicKey,
isChange: Boolean(registrationProof),
previousKey: previousKey,
path: path,
});
cb();
});
});
};
@ -146,26 +221,33 @@ Block.removeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) {
if (e) { return void cb(e); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
nThen(function (w) {
if (Util.escapeKeyCharacters(publicKey) !== safeKey) {
w.abort();
return void cb("INCORRECT_KEY");
}
}).nThen(function () {
validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) {
if (e) { return void cb(e); }
// derive the filepath
var path = createLoginBlockPath(Env, publicKey);
// make sure the path is valid
if (typeof(path) !== 'string') {
return void cb('E_INVALID_BLOCK_PATH');
}
// FIXME COLDSTORAGE
Fs.unlink(path, function (err) {
Env.Log.info('DELETION_BLOCK_BY_OWNER_RPC', {
publicKey: publicKey,
path: path,
status: err? String(err): 'SUCCESS',
});
// FIXME COLDSTORAGE
Fs.unlink(path, function (err) {
Env.Log.info('DELETION_BLOCK_BY_OWNER_RPC', {
publicKey: publicKey,
path: path,
status: err? String(err): 'SUCCESS',
if (err) { return void cb(err); }
cb();
});
if (err) { return void cb(err); }
cb();
});
});
};

@ -89,6 +89,7 @@ module.exports.create = function (config) {
}
},
restrictRegistration: false,
allowSubscriptions: config.allowSubscriptions === true,
blockDailyCheck: config.blockDailyCheck === true,

@ -158,6 +158,7 @@ module.exports.create = function (Env, cb) {
pinPath: Env.paths.pin,
filePath: Env.paths.data,
archivePath: Env.paths.archive,
blockPath: Env.paths.block,
inactiveTime: Env.inactiveTime,
archiveRetentionTime: Env.archiveRetentionTime,

@ -295,7 +295,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
// removeBlob
var remove = function (Env, blobId, cb) {
var blobPath = makeBlobPath(Env, blobId);
Fs.unlink(blobPath, cb); // TODO COLDSTORAGE
Fs.unlink(blobPath, cb);
};
// removeProof

@ -4,6 +4,7 @@
const HK = require("../hk-util");
const Store = require("../storage/file");
const BlobStore = require("../storage/blob");
const Block = require("../commands/block");
const Util = require("../common-util");
const nThen = require("nthen");
const Meta = require("../metadata");
@ -47,6 +48,7 @@ const init = function (config, _cb) {
Env.paths = {
pin: config.pinPath,
block: config.blockPath,
};
Env.inactiveTime = config.inactiveTime;
@ -688,6 +690,10 @@ COMMANDS.HASH_CHANNEL_LIST = function (data, cb) {
cb(void 0, hash);
};
COMMANDS.VALIDATE_ANCESTOR_PROOF = function (data, cb) {
Block.validateAncestorProof(Env, data && data.proof, cb);
};
process.on('message', function (data) {
if (!data || !data.txid || !data.pid) {
return void process.send({

@ -444,6 +444,13 @@ Workers.initialize = function (Env, config, _cb) {
}, cb);
};
Env.validateAncestorProof = function (proof, cb) {
sendCommand({
command: 'VALIDATE_ANCESTOR_PROOF',
proof: proof,
}, cb);
};
cb(void 0);
});
};

@ -280,6 +280,7 @@ var serveConfig = makeRouteCache(function (host) {
defaultStorageLimit: Env.defaultStorageLimit,
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
}, null, '\t'),
'obj.httpSafeOrigin = ' + (function () {
if (config.httpSafeOrigin) { return '"' + config.httpSafeOrigin + '"'; }

@ -53,7 +53,7 @@ define([
'cp-admin-update-limit',
'cp-admin-archive',
'cp-admin-unarchive',
// 'cp-admin-registration',
'cp-admin-registration',
],
'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit',

@ -243,7 +243,7 @@ define([
opt.keys = secret.keys;
opt.channelHex = secret.channel;
var RT, rpc, exists;
var RT, rpc, exists, restricted;
nThen(function (waitFor) {
Util.fetch(blockUrl, waitFor(function (err) {
@ -285,6 +285,12 @@ define([
// Write block
if (exists) { return; }
rpc.writeLoginBlock(blockRequest, waitFor(function (e) {
// we should tolerate restricted registration
// and proceed to clean up after any data we've created
if (e === 'E_RESTRICTED') {
restricted = true;
return void cb(true);
}
if (e) {
waitFor.abort();
console.error("Can't write login block", e);
@ -292,6 +298,7 @@ define([
}
}));
}).nThen(function (waitFor) {
if (restricted) { return; }
// Read block
Util.fetch(blockUrl, waitFor(function (e) {
if (e) {
@ -303,6 +310,7 @@ define([
}).nThen(function (waitFor) {
// Remove block
rpc.removeLoginBlock(removeRequest, waitFor(function (e) {
if (restricted) { return; } // an ENOENT is expected in the case of restricted registration, but we call this anyway to clean up any mess from previous tests.
if (e) {
waitFor.abort();
console.error("Can't remove login block", e);

@ -1873,14 +1873,16 @@ define([
Common.setLoginRedirect('login');
},
});
options.push({
tag: 'a',
attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'},
content: h('span', Messages.login_register),
action: function () {
Common.setLoginRedirect('register');
},
});
if (!Config.restrictRegistration) {
options.push({
tag: 'a',
attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'},
content: h('span', Messages.login_register),
action: function () {
Common.setLoginRedirect('register');
},
});
}
}
var $icon = $('<span>', {'class': 'fa fa-user-secret'});
//var $userbig = $('<span>', {'class': 'big'}).append($displayedName.clone());

@ -1735,6 +1735,7 @@ define([
var removeData = obj.Block.remove(blockKeys);
postMessage("DELETE_ACCOUNT", {
keys: Block.keysToRPCFormat(blockKeys),
removeData: removeData
}, function (obj) {
if (obj.state) {
@ -1855,9 +1856,16 @@ define([
};
var content = Block.serialize(JSON.stringify(temp), blockKeys);
console.error("OLD AND NEW BLOCK KEYS", oldBlockKeys, blockKeys);
content.registrationProof = Block.proveAncestor(oldBlockKeys);
console.log("writing new login block");
common.writeLoginBlock(content, waitFor(function (obj) {
var data = {
keys: Block.keysToRPCFormat(blockKeys),
content: content,
};
common.writeLoginBlock(data, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
return void cb(obj);
@ -1875,8 +1883,11 @@ define([
// Remove block hash
if (blockHash) {
console.log('removing old login block');
var removeData = Block.remove(oldBlockKeys);
common.removeLoginBlock(removeData, waitFor(function (obj) {
var data = {
keys: Block.keysToRPCFormat(oldBlockKeys), // { edPrivate, edPublic }
content: Block.remove(oldBlockKeys),
};
common.removeLoginBlock(data, waitFor(function (obj) {
if (obj && obj.error) { return void console.error(obj.error); }
}));
}

@ -451,19 +451,33 @@ define([
};
Store.writeLoginBlock = function (clientId, data, cb) {
store.rpc.writeLoginBlock(data, function (e, res) {
cb({
error: e,
data: res
Pinpad.create(store.network, data && data.keys, function (err, rpc) {
if (err) {
return void cb({
error: err,
});
}
rpc.writeLoginBlock(data && data.content, function (e, res) {
cb({
error: e,
data: res
});
});
});
};
Store.removeLoginBlock = function (clientId, data, cb) {
store.rpc.removeLoginBlock(data, function (e, res) {
cb({
error: e,
data: res
Pinpad.create(store.network, data && data.keys, function (err, rpc) {
if (err) {
return void cb({
error: err,
});
}
rpc.removeLoginBlock(data && data.content, function (e, res) {
cb({
error: e,
data: res
});
});
});
};
@ -815,6 +829,7 @@ define([
Store.deleteAccount = function (clientId, data, cb) {
var edPublic = store.proxy.edPublic;
var removeData = data && data.removeData;
var rpcKeys = data && data.keys;
Store.anonRpcMsg(clientId, {
msg: 'GET_METADATA',
data: store.driveChannel
@ -845,8 +860,15 @@ define([
}, waitFor());
}).nThen(function (waitFor) {
if (!removeData) { return; }
// Delete the block. Don't abort if it fails, it doesn't leak any data.
store.rpc.removeLoginBlock(removeData, waitFor());
var done = waitFor();
Pinpad.create(store.network, rpcKeys, function (err, rpc) {
if (err) {
console.error(err);
return void done();
}
// Delete the block. Don't abort if it fails, it doesn't leak any data.
rpc.removeLoginBlock(removeData, done);
});
}).nThen(function () {
// Log out current worker
postMessage(clientId, "DELETE_ACCOUNT", token, function () {});

@ -40,6 +40,19 @@ define([
};
};
Block.keysToRPCFormat = function (keys) {
try {
var sign = keys.sign;
return {
edPrivate: Nacl.util.encodeBase64(sign.secretKey),
edPublic: Nacl.util.encodeBase64(sign.publicKey),
};
} catch (err) {
console.error(err);
return;
}
};
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Nacl.util.decodeUTF8(content);
@ -86,6 +99,20 @@ define([
};
};
Block.proveAncestor = function (O /* oldBlockKeys */, N /* newBlockKeys */) {
N = N;
var u8_pub = Util.find(O, ['sign', 'publicKey']);
var u8_secret = Util.find(O, ['sign', 'secretKey']);
try {
// sign your old publicKey with your old privateKey
var u8_sig = Nacl.sign.detached(u8_pub, u8_secret);
// return an array with the sig and the pubkey
return JSON.stringify([u8_pub, u8_sig].map(Nacl.util.encodeBase64));
} catch (err) {
return void console.error(err);
}
};
Block.remove = function (keys) {
// sign the hash of the text 'DELETE_BLOCK'
var sig = Nacl.sign.detached(Nacl.hash(

@ -226,11 +226,15 @@ var factory = function (Util, Rpc) {
console.log(data);
return void cb("MISSING_PARAMETERS");
}
if (['string', 'undefined'].indexOf(typeof(data.registrationProof)) === -1) {
return void cb("INVALID_REGISTRATION_PROOF");
}
rpc.send('WRITE_LOGIN_BLOCK', [
data.publicKey,
data.signature,
data.ciphertext
data.ciphertext,
data.registrationProof || undefined,
], function (e) {
cb(e);
});

@ -11,21 +11,11 @@ define([
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Cryptpad, Login, UI, Realtime, Feedback, LocalStore, Test) {
$(function () {
var $main = $('#mainBlock');
var $checkImport = $('#import-recent');
// main block is hidden in case javascript is disabled
$main.removeClass('hidden');
// Make sure we don't display non-translated content (empty button)
$main.find('#data').removeClass('hidden');
if (LocalStore.isLoggedIn()) {
// already logged in, redirect to drive
document.location.href = '/drive/';
return;
} else {
$main.find('#userForm').removeClass('hidden');
}
/* Log in UI */

@ -15,22 +15,11 @@ define([
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function ($, Login, Cryptpad, Test, Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h) {
var Messages = Cryptpad.Messages;
$(function () {
var $main = $('#mainBlock');
// main block is hidden in case javascript is disabled
$main.removeClass('hidden');
// Make sure we don't display non-translated content (empty button)
$main.find('#data').removeClass('hidden');
if (LocalStore.isLoggedIn()) {
// already logged in, redirect to drive
document.location.href = '/drive/';
return;
} else {
$main.find('#userForm').removeClass('hidden');
}
// text and password input fields

Loading…
Cancel
Save