resolve conflict and lint compliance

pull/1/head
ansuz 5 years ago
commit 3e06c4bfe3

@ -1,3 +1,42 @@
# L release (3.11.0)
## Goals
* major server refactor to prepare for:
* trim-history
* allow lists
## Update notes
* dropped support for retainData
* archives are on by default
* you will need a new chainpad server
## Features
* restyled corner popup
* cool new scheduler library
* operations on channels are queued
* trim-history rpc
* unified historykeeper and rpc
* more visible styles for unanswered support tickets
* hidden hashes/safe links
* new "security" tab in settings
* queue'd popups
* reconnect alert
* link to user profile in notifications
* prompt anonymous users to register when viewing a profile
* spreadsheets
* reconnecting spreadsheets
* faster spreadsheets
* don't hijack chat cursor
* friends are now "contacts"
## Bug fixes
* friend request/accept race condition
* throw errors in 'mkAsync' if no function is passed
# Kouprey release (3.10.0) # Kouprey release (3.10.0)
## Goals ## Goals

@ -2,6 +2,8 @@
const BatchRead = require("../batch-read"); const BatchRead = require("../batch-read");
const nThen = require("nthen"); const nThen = require("nthen");
const getFolderSize = require("get-folder-size"); const getFolderSize = require("get-folder-size");
const Util = require("../common-util");
var Fs = require("fs"); var Fs = require("fs");
var Admin = module.exports; var Admin = module.exports;
@ -90,9 +92,10 @@ var getDiskUsage = function (Env, cb) {
}); });
}; };
Admin.command = function (Env, Server, publicKey, data, cb) { Admin.command = function (Env, safeKey, data, cb, Server) {
var admins = Env.admins; var admins = Env.admins;
if (admins.indexOf(publicKey) === -1) { var unsafeKey = Util.unescapeKeyCharacters(safeKey);
if (admins.indexOf(unsafeKey) === -1) {
return void cb("FORBIDDEN"); return void cb("FORBIDDEN");
} }

@ -31,7 +31,7 @@ const Util = require("../common-util");
author of the block, since we assume that the block will have been author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated. encrypted with xsalsa20-poly1305 which is authenticated.
*/ */
Block.validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS
// convert the public key to a Uint8Array and validate it // convert the public key to a Uint8Array and validate it
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); } if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
@ -86,13 +86,13 @@ var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey); return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
}; };
Block.writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS Block.writeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
//console.log(msg); //console.log(msg);
var publicKey = msg[0]; var publicKey = msg[0];
var signature = msg[1]; var signature = msg[1];
var block = msg[2]; var block = msg[2];
Block.validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) { validateLoginBlock(Env, publicKey, signature, block, function (e, validatedBlock) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
if (!(validatedBlock instanceof Uint8Array)) { return void cb('E_INVALID_BLOCK'); } if (!(validatedBlock instanceof Uint8Array)) { return void cb('E_INVALID_BLOCK'); }
@ -141,12 +141,12 @@ Block.writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS
information, we can just sign some constant and use that as proof. information, we can just sign some constant and use that as proof.
*/ */
Block.removeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS Block.removeLoginBlock = function (Env, safeKey, msg, cb) { // FIXME BLOCKS
var publicKey = msg[0]; var publicKey = msg[0];
var signature = msg[1]; var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
Block.validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) { validateLoginBlock(Env, publicKey, signature, block, function (e /*::, validatedBlock */) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
// derive the filepath // derive the filepath
var path = createLoginBlockPath(Env, publicKey); var path = createLoginBlockPath(Env, publicKey);

@ -160,7 +160,7 @@ Channel.isNewChannel = function (Env, channel, cb) {
Otherwise behaves the same as sending to a channel Otherwise behaves the same as sending to a channel
*/ */
Channel.writePrivateMessage = function (Env, args, Server, cb) { Channel.writePrivateMessage = function (Env, args, cb, Server) { // XXX odd signature
var channelId = args[0]; var channelId = args[0];
var msg = args[1]; var msg = args[1];

@ -184,5 +184,7 @@ Core.isPendingOwner = function (metadata, unsafeKey) {
return metadata.pending_owners.indexOf(unsafeKey) !== -1; return metadata.pending_owners.indexOf(unsafeKey) !== -1;
}; };
Core.haveACookie = function (Env, safeKey, cb) {
cb();
};

@ -8,10 +8,12 @@ const Core = require("./core");
const Util = require("../common-util"); const Util = require("../common-util");
const batchMetadata = BatchRead("GET_METADATA"); const batchMetadata = BatchRead("GET_METADATA");
Data.getMetadata = function (Env, channel, cb) { Data.getMetadata = function (Env, channel, cb/* , Server */) {
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== 32) { return cb("INVALID_CHAN_LENGTH"); } if (channel.length !== 32) { return cb("INVALID_CHAN_LENGTH"); }
// XXX get metadata from the server cache if it is available
// Server isn't always passed, though...
batchMetadata(channel, cb, function (done) { batchMetadata(channel, cb, function (done) {
var ref = {}; var ref = {};
var lineHandler = Meta.createLineHandler(ref, Env.Log.error); var lineHandler = Meta.createLineHandler(ref, Env.Log.error);

@ -454,10 +454,10 @@ Pinning.loadChannelPins = function (Env) {
Pinning.isChannelPinned = function (Env, channel, cb) { Pinning.isChannelPinned = function (Env, channel, cb) {
Env.evPinnedPadsReady.reg(() => { Env.evPinnedPadsReady.reg(() => {
if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) { if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) {
cb(true); cb(void 0, true);
} else { } else {
delete Env.pinnedPads[channel]; delete Env.pinnedPads[channel]; // XXX WAT
cb(false); cb(void 0, false);
} }
}); });
}; };

@ -2,7 +2,6 @@
/* globals Buffer*/ /* globals Buffer*/
const Quota = module.exports; const Quota = module.exports;
const Core = require("./core");
const Util = require("../common-util"); const Util = require("../common-util");
const Package = require('../../package.json'); const Package = require('../../package.json');
const Https = require("https"); const Https = require("https");
@ -35,25 +34,12 @@ Quota.applyCustomLimits = function (Env) {
}); });
}; };
// The limits object contains storage limits for all the publicKey that have paid Quota.updateCachedLimits = function (Env, cb) {
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
// XXX maybe the use case with a publicKey should be a different command that calls this?
Quota.updateLimits = function (Env, publicKey, cb) { // FIXME BATCH?S
if (Env.adminEmail === false) { if (Env.adminEmail === false) {
Quota.applyCustomLimits(Env); Quota.applyCustomLimits(Env);
if (Env.allowSubscriptions === false) { return; } if (Env.allowSubscriptions === false) { return; }
throw new Error("allowSubscriptions must be false if adminEmail is false"); throw new Error("allowSubscriptions must be false if adminEmail is false");
} }
if (typeof cb !== "function") { cb = function () {}; }
var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'?
Env.defaultStorageLimit: Core.DEFAULT_LIMIT;
var userId;
if (publicKey) {
userId = Util.unescapeKeyCharacters(publicKey);
}
var body = JSON.stringify({ var body = JSON.stringify({
domain: Env.myDomain, domain: Env.myDomain,
@ -86,14 +72,7 @@ Quota.updateLimits = function (Env, publicKey, cb) { // FIXME BATCH?S
var json = JSON.parse(str); var json = JSON.parse(str);
Env.limits = json; Env.limits = json;
Quota.applyCustomLimits(Env); Quota.applyCustomLimits(Env);
cb(void 0);
var l;
if (userId) {
var limit = Env.limits[userId];
l = limit && typeof limit.limit === "number" ?
[limit.limit, limit.plan, limit.note] : [defaultLimit, '', ''];
}
cb(void 0, l);
} catch (e) { } catch (e) {
cb(e); cb(e);
} }
@ -109,4 +88,19 @@ Quota.updateLimits = function (Env, publicKey, cb) { // FIXME BATCH?S
req.end(body); req.end(body);
}; };
// The limits object contains storage limits for all the publicKey that have paid
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
Quota.getUpdatedLimit = function (Env, safeKey, cb) { // FIXME BATCH?S
Quota.updateCachedLimits(Env, function (err) {
if (err) { return void cb(err); }
var limit = Env.limits[safeKey];
if (limit && typeof(limit.limit) === 'number') {
return void cb(void 0, [limit.limit, limit.plan, limit.note]);
}
return void cb(void 0, [Env.defaultStorageLimit, '', '']);
});
};

@ -18,98 +18,30 @@ var RPC = module.exports;
const Store = require("../storage/file"); const Store = require("../storage/file");
const BlobStore = require("../storage/blob"); const BlobStore = require("../storage/blob");
const UNAUTHENTICATED_CALLS = [ const UNAUTHENTICATED_CALLS = {
'GET_FILE_SIZE', GET_FILE_SIZE: Pinning.getFileSize, // XXX TEST
'GET_METADATA', GET_MULTIPLE_FILE_SIZE: Pinning.getMultipleFileSize,
'GET_MULTIPLE_FILE_SIZE', GET_DELETED_PADS: Pinning.getDeletedPads,
'IS_CHANNEL_PINNED', IS_CHANNEL_PINNED: Pinning.isChannelPinned,
'IS_NEW_CHANNEL', IS_NEW_CHANNEL: Channel.isNewChannel,
'GET_DELETED_PADS', WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage,
'WRITE_PRIVATE_MESSAGE',
];
var isUnauthenticatedCall = function (call) {
return UNAUTHENTICATED_CALLS.indexOf(call) !== -1;
};
const AUTHENTICATED_CALLS = [
'COOKIE',
'RESET',
'PIN',
'UNPIN',
'GET_HASH',
'GET_TOTAL_SIZE',
'UPDATE_LIMITS',
'GET_LIMIT',
'UPLOAD_STATUS',
'UPLOAD_COMPLETE',
'OWNED_UPLOAD_COMPLETE',
'UPLOAD_CANCEL',
'EXPIRE_SESSION',
'TRIM_HISTORY',
'CLEAR_OWNED_CHANNEL',
'REMOVE_OWNED_CHANNEL',
'REMOVE_PINS',
'TRIM_PINS',
'WRITE_LOGIN_BLOCK',
'REMOVE_LOGIN_BLOCK',
'ADMIN',
'SET_METADATA'
];
var isAuthenticatedCall = function (call) {
return AUTHENTICATED_CALLS.indexOf(call) !== -1;
}; };
var isUnauthenticateMessage = function (msg) { var isUnauthenticateMessage = function (msg) {
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]); return msg && msg.length === 2 && typeof(UNAUTHENTICATED_CALLS[msg[0]]) === 'function';
}; };
var handleUnauthenticatedMessage = function (Env, msg, respond, Server) { var handleUnauthenticatedMessage = function (Env, msg, respond, Server) {
Env.Log.silly('LOG_RPC', msg[0]); Env.Log.silly('LOG_RPC', msg[0]);
switch (msg[0]) {
case 'GET_FILE_SIZE': var method = UNAUTHENTICATED_CALLS[msg[0]];
return void Pinning.getFileSize(Env, msg[1], function (e, size) { method(Env, msg[1], function (err, value) {
Env.WARN(e, msg[1]); if (err) {
respond(e, [null, size, null]); Env.WARN(err, msg[1]);
}); return void respond(err);
case 'GET_METADATA':
return void Metadata.getMetadata(Env, msg[1], function (e, data) {
Env.WARN(e, msg[1]);
respond(e, [null, data, null]);
});
case 'GET_MULTIPLE_FILE_SIZE': // XXX not actually used on the client?
return void Pinning.getMultipleFileSize(Env, msg[1], function (e, dict) {
if (e) {
Env.WARN(e, dict);
return respond(e);
}
respond(e, [null, dict, null]);
});
case 'GET_DELETED_PADS':
return void Pinning.getDeletedPads(Env, msg[1], function (e, list) {
if (e) {
Env.WARN(e, msg[1]);
return respond(e);
}
respond(e, [null, list, null]);
});
case 'IS_CHANNEL_PINNED':
return void Pinning.isChannelPinned(Env, msg[1], function (isPinned) {
respond(null, [null, isPinned, null]);
});
case 'IS_NEW_CHANNEL':
return void Channel.isNewChannel(Env, msg[1], function (e, isNew) {
respond(e, [null, isNew, null]);
});
case 'WRITE_PRIVATE_MESSAGE':
return void Channel.writePrivateMessage(Env, msg[1], Server, function (e, output) {
respond(e, output);
});
default:
Env.Log.warn("UNSUPPORTED_RPC_CALL", msg);
return respond('UNSUPPORTED_RPC_CALL', msg);
} }
respond(err, [null, value, null]);
}, Server);
}; };
const AUTHENTICATED_USER_TARGETED = { const AUTHENTICATED_USER_TARGETED = {
@ -124,24 +56,47 @@ const AUTHENTICATED_USER_TARGETED = {
UPLOAD_COMPLETE: Upload.complete, UPLOAD_COMPLETE: Upload.complete,
UPLOAD_CANCEL: Upload.cancel, UPLOAD_CANCEL: Upload.cancel,
OWNED_UPLOAD_COMPLETE: Upload.complete_owned, OWNED_UPLOAD_COMPLETE: Upload.complete_owned,
WRITE_LOGIN_BLOCK: Block.writeLoginBlock,
REMOVE_LOGIN_BLOCK: Block.removeLoginBlock,
ADMIN: Admin.command,
}; };
const AUTHENTICATED_USER_SCOPED = { const AUTHENTICATED_USER_SCOPED = {
GET_HASH: Pinning.getHash, GET_HASH: Pinning.getHash,
GET_TOTAL_SIZE: Pinning.getTotalSize, GET_TOTAL_SIZE: Pinning.getTotalSize,
UPDATE_LIMITS: Quota.updateLimits, UPDATE_LIMITS: Quota.getUpdatedLimit,
GET_LIMIT: Pinning.getLimit, GET_LIMIT: Pinning.getLimit,
EXPIRE_SESSION: Core.expireSessionAsync, EXPIRE_SESSION: Core.expireSessionAsync,
REMOVE_PINS: Pinning.removePins, REMOVE_PINS: Pinning.removePins,
TRIM_PINS: Pinning.trimPins, TRIM_PINS: Pinning.trimPins,
SET_METADATA: Metadata.setMetadata, SET_METADATA: Metadata.setMetadata,
COOKIE: Core.haveACookie,
}; };
var handleAuthenticatedMessage = function (Env, map) { var isAuthenticatedCall = function (call) {
var msg = map.msg; if (call === 'UPLOAD') { return false; }
var safeKey = map.safeKey; return typeof(AUTHENTICATED_USER_TARGETED[call] || AUTHENTICATED_USER_SCOPED[call]) === 'function';
var Respond = map.Respond; };
var Server = map.Server;
var handleAuthenticatedMessage = function (Env, unsafeKey, msg, respond, Server) {
/* If you have gotten this far, you have signed the message with the
public key which you provided.
*/
var safeKey = Util.escapeKeyCharacters(unsafeKey);
var Respond = function (e, value) {
var session = Env.Sessions[safeKey];
var token = session? session.tokens.slice(-1)[0]: '';
var cookie = Core.makeCookie(token).join('|');
respond(e ? String(e): e, [cookie].concat(typeof(value) !== 'undefined' ?value: []));
};
msg.shift();
// discard validated cookie from message
if (!msg.length) {
return void Respond('INVALID_MSG');
}
var TYPE = msg[0]; var TYPE = msg[0];
@ -151,7 +106,7 @@ var handleAuthenticatedMessage = function (Env, map) {
return void AUTHENTICATED_USER_TARGETED[TYPE](Env, safeKey, msg[1], function (e, value) { return void AUTHENTICATED_USER_TARGETED[TYPE](Env, safeKey, msg[1], function (e, value) {
Env.WARN(e, value); Env.WARN(e, value);
return void Respond(e, value); return void Respond(e, value);
}); }, Server);
} }
if (typeof(AUTHENTICATED_USER_SCOPED[TYPE]) === 'function') { if (typeof(AUTHENTICATED_USER_SCOPED[TYPE]) === 'function') {
@ -164,35 +119,7 @@ var handleAuthenticatedMessage = function (Env, map) {
}); });
} }
switch (msg[0]) {
case 'COOKIE': return void Respond(void 0);
case 'WRITE_LOGIN_BLOCK':
return void Block.writeLoginBlock(Env, msg[1], function (e) { // XXX SPECIAL
if (e) {
Env.WARN(e, 'WRITE_LOGIN_BLOCK');
return void Respond(e);
}
Respond(e);
});
case 'REMOVE_LOGIN_BLOCK':
return void Block.removeLoginBlock(Env, msg[1], function (e) { // XXX SPECIAL
if (e) {
Env.WARN(e, 'REMOVE_LOGIN_BLOCK');
return void Respond(e);
}
Respond(e);
});
case 'ADMIN':
return void Admin.command(Env, Server, safeKey, msg[1], function (e, result) { // XXX SPECIAL
if (e) {
Env.WARN(e, result);
return void Respond(e);
}
Respond(void 0, result);
});
default:
return void Respond('UNSUPPORTED_RPC_CALL', msg); return void Respond('UNSUPPORTED_RPC_CALL', msg);
}
}; };
var rpc = function (Env, Server, data, respond) { var rpc = function (Env, Server, data, respond) {
@ -241,45 +168,23 @@ var rpc = function (Env, Server, data, respond) {
return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY');
} }
if (isAuthenticatedCall(msg[1])) { var command = msg[1];
if (Core.checkSignature(Env, serialized, signature, publicKey) !== true) {
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); if (command === 'UPLOAD') {
// UPLOAD is a special case that skips signature validation
// intentional fallthrough behaviour
return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server);
} }
} else if (msg[1] !== 'UPLOAD') { if (isAuthenticatedCall(command)) {
Env.Log.warn('INVALID_RPC_CALL', msg[1]); // check the signature on the message
return void respond("INVALID_RPC_CALL"); // refuse the command if it doesn't validate
if (Core.checkSignature(Env, serialized, signature, publicKey) === true) {
return void handleAuthenticatedMessage(Env, publicKey, msg, respond, Server);
} }
return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY");
var safeKey = Util.escapeKeyCharacters(publicKey);
/* If you have gotten this far, you have signed the message with the
public key which you provided.
We can safely modify the state for that key
OR it's an unauthenticated call, which must not modify the state
for that key in a meaningful way.
*/
// discard validated cookie from message
msg.shift();
var Respond = function (e, msg) {
var session = Env.Sessions[safeKey];
var token = session? session.tokens.slice(-1)[0]: '';
var cookie = Core.makeCookie(token).join('|');
respond(e ? String(e): e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
};
if (typeof(msg) !== 'object' || !msg.length) {
return void Respond('INVALID_MSG');
} }
Env.Log.warn('INVALID_RPC_CALL', command);
handleAuthenticatedMessage(Env, { return void respond("INVALID_RPC_CALL");
msg: msg,
safeKey: safeKey,
Respond: Respond,
Server: Server,
});
}; };
RPC.create = function (config, cb) { RPC.create = function (config, cb) {
@ -302,10 +207,13 @@ RPC.create = function (config, cb) {
} }
}; };
if (typeof(config.domain) !== 'undefined') {
throw new Error('fuck');
}
var Env = { var Env = {
historyKeeper: config.historyKeeper, historyKeeper: config.historyKeeper,
intervals: config.intervals || {}, intervals: config.intervals || {},
defaultStorageLimit: config.defaultStorageLimit,
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024), maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
Sessions: {}, Sessions: {},
paths: {}, paths: {},
@ -326,6 +234,10 @@ RPC.create = function (config, cb) {
domain: config.domain // XXX domain: config.domain // XXX
}; };
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0?
config.defaultStorageLimit:
Core.DEFAULT_LIMIT;
try { try {
Env.admins = (config.adminKeys || []).map(function (k) { Env.admins = (config.adminKeys || []).map(function (k) {
k = k.replace(/\/+$/, ''); k = k.replace(/\/+$/, '');
@ -345,7 +257,7 @@ RPC.create = function (config, cb) {
paths.blob = keyOrDefaultString('blobPath', './blob'); paths.blob = keyOrDefaultString('blobPath', './blob');
var updateLimitDaily = function () { var updateLimitDaily = function () {
Quota.updateLimits(Env, undefined, function (e) { Quota.updateCachedLimits(Env, function (e) {
if (e) { if (e) {
WARN('limitUpdate', e); WARN('limitUpdate', e);
} }

@ -159,6 +159,13 @@ var createUser = function (config, cb) {
} }
wc.leave(); wc.leave();
})); }));
}).nThen(function (w) {
// give the server time to write your mailbox data before checking that it's correct
// XXX chainpad-server sends an ACK before the channel has actually been created
// causing you to think that everything is good.
// without this timeout the GET_METADATA rpc occasionally returns before
// the metadata has actually been written to the disk.
setTimeout(w(), 500);
}).nThen(function (w) { }).nThen(function (w) {
// confirm that you own your mailbox // confirm that you own your mailbox
user.anonRpc.send("GET_METADATA", user.mailboxChannel, w(function (err, data) { user.anonRpc.send("GET_METADATA", user.mailboxChannel, w(function (err, data) {

Loading…
Cancel
Save