Merge remote-tracking branch 'origin/communities-allow-list' into communities-allow

pull/1/head
yflory 5 years ago
commit d0eb96815d

@ -72,7 +72,7 @@ server {
set $styleSrc "'unsafe-inline' 'self' ${main_domain}";
# connect-src restricts URLs which can be loaded using script interfaces
set $connectSrc "'self' https://${main_domain} $main_domain https://${api_domain} blob:";
set $connectSrc "'self' https://${main_domain} ${main_domain} https://${api_domain} blob: wss://${api_domain} ${api_domain} ${files_domain}";
# fonts can be loaded from data-URLs or the main domain
set $fontSrc "'self' data: ${main_domain}";

@ -23,7 +23,7 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
if (e) { return void cb(e); }
cb();
const channel_cache = Env.historyKeeper.channel_cache;
const channel_cache = Env.channel_cache;
const clear = function () {
// delete the channel cache because it will have been invalidated
@ -117,8 +117,8 @@ Channel.removeOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
}
cb(void 0, 'OK');
const channel_cache = Env.historyKeeper.channel_cache;
const metadata_cache = Env.historyKeeper.metadata_cache;
const channel_cache = Env.channel_cache;
const metadata_cache = Env.metadata_cache;
const clear = function () {
delete channel_cache[channelId];
@ -187,8 +187,8 @@ Channel.trimHistory = function (Env, safeKey, data, cb) {
// clear historyKeeper's cache for this channel
Env.historyKeeper.channelClose(channelId);
cb(void 0, 'OK');
delete Env.historyKeeper.channel_cache[channelId];
delete Env.historyKeeper.metadata_cache[channelId];
delete Env.channel_cache[channelId];
delete Env.metadata_cache[channelId];
});
});
};

@ -43,7 +43,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
var command = data.command;
if (!channel || !Core.isValidId(channel)) { return void cb ('INVALID_CHAN'); }
if (!command || typeof (command) !== 'string') { return void cb('INVALID_COMMAND'); }
if (Meta.commands.indexOf(command) === -1) { return void('UNSUPPORTED_COMMAND'); }
if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); }
queueMetadata(channel, function (next) {
Data.getMetadata(Env, channel, function (err, metadata) {
@ -111,8 +111,8 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) {
cb(void 0, metadata);
next();
const metadata_cache = Env.historyKeeper.metadata_cache;
const channel_cache = Env.historyKeeper.channel_cache;
const metadata_cache = Env.metadata_cache;
const channel_cache = Env.channel_cache;
metadata_cache[channel] = metadata;

@ -6,9 +6,22 @@ const WriteQueue = require("./write-queue");
const BatchRead = require("./batch-read");
const RPC = require("./rpc");
const HK = require("./hk-util.js");
const Core = require("./commands/core");
const Store = require("./storage/file");
const BlobStore = require("./storage/blob");
module.exports.create = function (config, cb) {
const Log = config.log;
var WARN = function (e, output) {
if (e && output) {
Log.warn(e, {
output: output,
message: String(e),
stack: new Error(e).stack,
});
}
};
Log.silly('HK_LOADING', 'LOADING HISTORY_KEEPER MODULE');
@ -25,9 +38,62 @@ module.exports.create = function (config, cb) {
channel_cache: {},
queueStorage: WriteQueue(),
batchIndexReads: BatchRead("HK_GET_INDEX"),
//historyKeeper: config.historyKeeper,
intervals: config.intervals || {},
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
Sessions: {},
paths: {},
//msgStore: config.store,
pinStore: undefined,
pinnedPads: {},
pinsLoaded: false,
pendingPinInquiries: {},
pendingUnpins: {},
pinWorkers: 5,
limits: {},
admins: [],
WARN: WARN,
flushCache: config.flushCache,
adminEmail: config.adminEmail,
allowSubscriptions: config.allowSubscriptions,
myDomain: config.myDomain,
mySubdomain: config.mySubdomain,
customLimits: config.customLimits,
// FIXME this attribute isn't in the default conf
// but it is referenced in Quota
domain: config.domain
};
var paths = Env.paths;
var keyOrDefaultString = function (key, def) {
return typeof(config[key]) === 'string'? config[key]: def;
};
config.historyKeeper = {
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
paths.block = keyOrDefaultString('blockPath', './block');
paths.data = keyOrDefaultString('filePath', './datastore');
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
paths.blob = keyOrDefaultString('blobPath', './blob');
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0?
config.defaultStorageLimit:
Core.DEFAULT_LIMIT;
try {
Env.admins = (config.adminKeys || []).map(function (k) {
k = k.replace(/\/+$/, '');
var s = k.split('/');
return s[s.length-1];
});
} catch (e) {
console.error("Can't parse admin keys. Please update or fix your config.js file!");
}
config.historyKeeper = Env.historyKeeper = {
metadata_cache: Env.metadata_cache,
channel_cache: Env.channel_cache,
@ -45,7 +111,20 @@ module.exports.create = function (config, cb) {
HK.dropChannel(Env, channelName);
},
channelOpen: function (Server, channelName, userId) {
Env.channel_cache[channelName] = {};
Env.channel_cache[channelName] = Env.channel_cache[channelName] || {};
//const metadata = Env.metadata_cache[channelName];
// chainpad-server@4.0.3 supports a removeFromChannel method
// Server.removeFromChannel(channelName, userId);
// this lets us kick users from restricted channels
// XXX RESTRICT
// this event is emitted whenever a user joins a channel.
// if that channel is restricted then we should forcefully disconnect them.
// we won't know that it's restricted until we load its metadata.
// as long as metadata is in memory as long as anyone is sending messages to a channel
// then we won't broadcast messages to unauthorized users
Server.send(userId, [
0,
Env.id,
@ -63,11 +142,34 @@ module.exports.create = function (config, cb) {
Log.verbose('HK_ID', 'History keeper ID: ' + Env.id);
nThen(function (w) {
require('./storage/file').create(config, w(function (_store) {
// create a pin store
Store.create({
filePath: pinPath,
}, w(function (s) {
Env.pinStore = s;
}));
// create a channel store
Store.create(config, w(function (_store) {
config.store = _store;
Env.store = _store;
Env.msgStore = _store; // API used by rpc
Env.store = _store; // API used by historyKeeper
}));
// create a blob store
BlobStore.create({
blobPath: config.blobPath,
blobStagingPath: config.blobStagingPath,
archivePath: config.archivePath,
getSession: function (safeKey) {
return Core.getSession(Env.Sessions, safeKey);
},
}, w(function (err, blob) {
if (err) { throw new Error(err); }
Env.blobStore = blob;
}));
}).nThen(function (w) {
// create a task store
require("./storage/tasks").create(config, w(function (e, tasks) {
if (e) {
throw e;
@ -87,7 +189,7 @@ module.exports.create = function (config, cb) {
}, 1000 * 60 * 5); // run every five minutes
}));
}).nThen(function () {
RPC.create(config, function (err, _rpc) {
RPC.create(Env, function (err, _rpc) {
if (err) { throw err; }
Env.rpc = _rpc;

@ -75,6 +75,29 @@ const isMetadataMessage = function (parsed) {
return Boolean(parsed && parsed.channel);
};
const isChannelRestricted = function (metadata) { // XXX RESTRICT
metadata = metadata;
return false;
};
const isUserAllowed = function (metadata, userId) { // XXX RESTRICT
/*
at this point all we have is the user's netflux id.
the allow-list is encoded for 'unsafeKeys' (URL-unsafe base64 encoded public signing keys).
we need a lookup table: netfluxId => public keys with which this netflux session has authenticated.
from there we can check whether the user has authenticated for any of the allowed keys this session.
owners are implicitly allowed to view any file they own.
pending_owners too.
otherwise check metadata.allowed.
*/
userId = userId;
return false;
};
// validateKeyStrings supplied by clients must decode to 32-byte Uint8Arrays
const isValidValidateKeyString = function (key) {
try {
@ -646,6 +669,16 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
// And then check if the channel is expired. If it is, send the error and abort
// FIXME this is hard to read because 'checkExpired' has side effects
if (checkExpired(Env, Server, channelName)) { return void waitFor.abort(); }
// XXX RESTRICT
// once we've loaded the metadata we can check whether the channel is restricted
// and notify the user if they're not included in the list
if (isChannelRestricted(index.metadata) && isUserAllowed(index.metadata, userId)) {
// XXX RESTRICT send a message indicating that they need to authenticate
// for a list of private keys...
return void waitFor.abort();
}
// always send metadata with GET_HISTORY requests
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(index.metadata)], w);
}));
@ -789,9 +822,9 @@ const handleGetFullHistory = function (Env, Server, seq, userId, parsed) {
};
const directMessageCommands = {
GET_HISTORY: handleGetHistory,
GET_HISTORY_RANGE: handleGetHistoryRange,
GET_FULL_HISTORY: handleGetFullHistory,
GET_HISTORY: handleGetHistory, // XXX RESTRICT
GET_HISTORY_RANGE: handleGetHistoryRange, // XXX RESTRICT
GET_FULL_HISTORY: handleGetFullHistory, // XXX RESTRICT
};
/* onDirectMessage
@ -817,6 +850,10 @@ HK.onDirectMessage = function (Env, Server, seq, userId, json) {
// have to abort later (once we know the expiration time)
if (checkExpired(Env, Server, parsed[1])) { return; }
// XXX RESTRICT
// metadata might already be in memory.
// rejecting unauthorized users here is an optimization
// look up the appropriate command in the map of commands or fall back to RPC
var command = directMessageCommands[parsed[0]] || handleRPC;

@ -2,23 +2,169 @@ var Meta = module.exports;
var deduplicate = require("./common-util").deduplicateString;
/* Metadata fields:
/* Metadata fields and the commands that can modify them
we assume that these commands can only be performed
by owners or in some cases pending owners. Thus
the owners field is guaranteed to exist.
* channel <STRING>
* validateKey <STRING>
* owners <ARRAY>
* ADD_OWNERS
* RM_OWNERS
* RESET_OWNERS
* pending_owners <ARRAY>
* ADD_PENDING_OWNERS
* RM_PENDING_OWNERS
* expire <NUMBER>
* UPDATE_EXPIRATION (NOT_IMPLEMENTED)
* restricted <BOOLEAN>
* RESTRICT_ACCESS
* allowed <ARRAY>
* ADD_ALLOWED
* RM_ALLOWED
* RESET_ALLOWED
* ADD_OWNERS
* RESET_OWNERS
* mailbox <STRING|MAP>
* ADD_MAILBOX
* RM_MAILBOX
*/
var commands = {};
var isValidOwner = function (owner) {
var isValidPublicKey = function (owner) {
return typeof(owner) === 'string' && owner.length === 44;
};
// isValidPublicKey is a better indication of what the above function does
// I'm preserving this function name in case we ever want to expand its
// criteria at a later time...
var isValidOwner = isValidPublicKey;
// ["RESTRICT_ACCESS", [true], 1561623438989]
// ["RESTRICT_ACCESS", [false], 1561623438989]
commands.RESTRICT_ACCESS = function (meta, args) {
if (!Array.isArray(args) || typeof(args[0]) !== 'boolean') {
throw new Error('INVALID_STATE');
}
var bool = args[0];
// reject the proposed command if there is no change in state
if (meta.restricted === bool) { return false; }
// apply the new state
meta.restricted = args[0];
// if you're disabling access restrictions then you can assume
// then there is nothing more to do. Leave the existing list as-is
if (!bool) { return true; }
// you're all set if an allow list already exists
if (Array.isArray(meta.allowed)) { return true; }
// otherwise define it
meta.allowed = [];
return true;
};
// ["ADD_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.ADD_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) {
throw new Error("INVALID_ARGS");
}
var allowed = meta.allowed || [];
var changed = false;
args.forEach(function (arg) {
// don't add invalid public keys
if (!isValidPublicKey(arg)) { return; }
// don't add owners to the allow list
if (meta.owners.indexOf(arg) >= 0) { return; }
// don't duplicate entries in the allow list
if (allowed.indexOf(arg) >= 0) { return; }
allowed.push(arg);
changed = true;
});
if (changed) {
meta.allowed = meta.allowed || allowed;
}
return changed;
};
// ["RM_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.RM_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) {
throw new Error("INVALID_ARGS");
}
// there may not be anything to remove
if (!meta.allowed) { return false; }
var changed = false;
args.forEach(function (arg) {
var index = meta.allowed.indexOf(arg);
if (index < 0) { return; }
meta.allowed.splice(index, 1);
changed = true;
});
return changed;
};
var arrayHasChanged = function (A, B) {
var changed;
A.some(function (a) {
if (B.indexOf(a) < 0) { return (changed = true); }
});
if (changed) { return true; }
B.some(function (b) {
if (A.indexOf(b) < 0) { return (changed = true); }
});
return changed;
};
var filterInPlace = function (A, f) {
for (var i = A.length - 1; i >= 0; i--) {
if (f(A[i], i, A)) { A.splice(i, 1); }
}
};
// ["RESET_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
commands.RESET_ALLOWED = function (meta, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
var updated = args.filter(function (arg) {
// don't allow invalid public keys
if (!isValidPublicKey(arg)) { return false; }
// don't ever add owners to the allow list
if (meta.owners.indexOf(arg)) { return false; }
return true;
});
// this is strictly an optimization...
// a change in length is a clear indicator of a functional change
if (meta.allowed && meta.allowed.length !== updated.length) {
meta.allowed = updated;
return true;
}
// otherwise we must check that the arrays contain distinct elements
// if there is no functional change, then return false
if (!arrayHasChanged(meta.allowed, updated)) { return false; }
// otherwise overwrite the in-memory data and indicate that there was a change
meta.allowed = updated;
return true;
};
// ["ADD_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
commands.ADD_OWNERS = function (meta, args) {
// bail out if args isn't an array
@ -40,6 +186,13 @@ commands.ADD_OWNERS = function (meta, args) {
changed = true;
});
if (changed && Array.isArray(meta.allowed)) {
// make sure owners are not included in the allow list
filterInPlace(meta.allowed, function (member) {
return meta.owners.indexOf(member) !== -1;
});
}
return changed;
};
@ -141,6 +294,14 @@ commands.RESET_OWNERS = function (meta, args) {
// overwrite the existing owners with the new one
meta.owners = deduplicate(args.filter(isValidOwner));
if (Array.isArray(meta.allowed)) {
// make sure owners are not included in the allow list
filterInPlace(meta.allowed, function (member) {
return meta.owners.indexOf(member) !== -1;
});
}
return true;
};
@ -178,6 +339,25 @@ commands.ADD_MAILBOX = function (meta, args) {
return changed;
};
commands.RM_MAILBOX = function (meta, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
if (!meta.mailbox || typeof(meta.mailbox) === 'undefined') {
return false;
}
if (typeof(meta.mailbox) === 'string' && args.length === 0) {
delete meta.mailbox;
return true;
}
var changed = false;
args.forEach(function (arg) {
if (meta.mailbox[arg] === 'undefined') { return; }
delete meta.mailbox[arg];
changed = true;
});
return changed;
};
commands.UPDATE_EXPIRATION = function () {
throw new Error("E_NOT_IMPLEMENTED");
};

@ -1,6 +1,4 @@
/*jshint esversion: 6 */
const nThen = require("nthen");
const Util = require("./common-util");
const Core = require("./commands/core");
@ -14,17 +12,14 @@ const Upload = require("./commands/upload");
var RPC = module.exports;
const Store = require("./storage/file");
const BlobStore = require("./storage/blob");
const UNAUTHENTICATED_CALLS = {
GET_FILE_SIZE: Pinning.getFileSize,
GET_MULTIPLE_FILE_SIZE: Pinning.getMultipleFileSize,
GET_DELETED_PADS: Pinning.getDeletedPads,
IS_CHANNEL_PINNED: Pinning.isChannelPinned,
IS_NEW_CHANNEL: Channel.isNewChannel,
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage,
GET_METADATA: Metadata.getMetadata,
WRITE_PRIVATE_MESSAGE: Channel.writePrivateMessage, // XXX RESTRICT
GET_METADATA: Metadata.getMetadata, // XXX RESTRICT
};
var isUnauthenticateMessage = function (msg) {
@ -187,86 +182,12 @@ var rpc = function (Env, Server, data, respond) {
return void respond("INVALID_RPC_CALL");
};
RPC.create = function (config, cb) {
var Log = config.log;
// load pin-store...
Log.silly('LOADING RPC MODULE');
var keyOrDefaultString = function (key, def) {
return typeof(config[key]) === 'string'? config[key]: def;
};
var WARN = function (e, output) {
if (e && output) {
Log.warn(e, {
output: output,
message: String(e),
stack: new Error(e).stack,
});
}
};
if (typeof(config.domain) !== 'undefined') {
throw new Error('fuck');
}
var Env = {
historyKeeper: config.historyKeeper,
intervals: config.intervals || {},
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
Sessions: {},
paths: {},
msgStore: config.store,
pinStore: undefined,
pinnedPads: {},
pinsLoaded: false,
pendingPinInquiries: {},
pendingUnpins: {},
pinWorkers: 5,
limits: {},
admins: [],
Log: Log,
WARN: WARN,
flushCache: config.flushCache,
adminEmail: config.adminEmail,
allowSubscriptions: config.allowSubscriptions,
myDomain: config.myDomain,
mySubdomain: config.mySubdomain,
customLimits: config.customLimits,
// FIXME this attribute isn't in the default conf
// but it is referenced in Quota
domain: config.domain
};
Env.defaultStorageLimit = typeof(config.defaultStorageLimit) === 'number' && config.defaultStorageLimit > 0?
config.defaultStorageLimit:
Core.DEFAULT_LIMIT;
try {
Env.admins = (config.adminKeys || []).map(function (k) {
k = k.replace(/\/+$/, '');
var s = k.split('/');
return s[s.length-1];
});
} catch (e) {
console.error("Can't parse admin keys. Please update or fix your config.js file!");
}
RPC.create = function (Env, cb) {
var Sessions = Env.Sessions;
var paths = Env.paths;
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
paths.block = keyOrDefaultString('blockPath', './block');
paths.data = keyOrDefaultString('filePath', './datastore');
paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
paths.blob = keyOrDefaultString('blobPath', './blob');
var updateLimitDaily = function () {
Quota.updateCachedLimits(Env, function (e) {
if (e) {
WARN('limitUpdate', e);
Env.WARN('limitUpdate', e);
}
});
};
@ -276,24 +197,11 @@ RPC.create = function (config, cb) {
Pinning.loadChannelPins(Env);
nThen(function (w) {
Store.create({
filePath: pinPath,
}, w(function (s) {
Env.pinStore = s;
}));
BlobStore.create({
blobPath: config.blobPath,
blobStagingPath: config.blobStagingPath,
archivePath: config.archivePath,
getSession: function (safeKey) {
return Core.getSession(Sessions, safeKey);
},
}, w(function (err, blob) {
if (err) { throw new Error(err); }
Env.blobStore = blob;
}));
}).nThen(function () {
// expire old sessions once per minute
Env.intervals.sessionExpirationInterval = setInterval(function () {
Core.expireSessions(Sessions);
}, Core.SESSION_EXPIRATION_TIME);
cb(void 0, function (Server, data, respond) {
try {
return rpc(Env, Server, data, respond);
@ -302,9 +210,4 @@ RPC.create = function (config, cb) {
console.log(e.stack);
}
});
// expire old sessions once per minute
Env.intervals.sessionExpirationInterval = setInterval(function () {
Core.expireSessions(Sessions);
}, Core.SESSION_EXPIRATION_TIME);
});
};

@ -970,6 +970,7 @@ var trimChannel = function (env, channelName, hash, _cb) {
}
var msg = Util.tryParse(s_msg);
if (!msg) { return void readMore(); }
var msgHash = Extras.getHash(msg[4]);
if (msgHash === hash) {

@ -357,9 +357,142 @@ nThen(function (w) {
bob.name = 'bob';
//console.log("Initialized Bob");
}));
}).nThen(function (w) {
// restrict access to oscar's mailbox channel
oscar.rpc.send('SET_METADATA', {
command: 'RESTRICT_ACCESS',
channel: oscar.mailboxChannel,
value: [ true ]
}, w(function (err, response) {
if (err) {
return void console.log(err);
}
var metadata = response[0];
if (!(metadata && metadata.restricted)) {
throw new Error("EXPECTED MAILBOX TO BE RESTRICTED");
}
}));
}).nThen(function (w) {
// XXX RESTRICT GET_METADATA should fail because alice is not on the allow list
// expect INSUFFICIENT_PERMISSIONS
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err) {
if (!err) {
// XXX RESTRICT alice should not be permitted to read oscar's mailbox's metadata
}
}));
}).nThen(function (w) {
// add alice to oscar's mailbox's allow list for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_ALLOWED',
channel: oscar.mailboxChannel,
value: [
alice.edKeys.edPublic
]
}, w(function (err /*, metadata */) {
if (err) {
return void console.error(err);
}
//console.log('XXX', metadata);
}));
}).nThen(function (w) {
oscar.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("OSCAR SHOULD BE ABLE TO READ HIS OWN METADATA");
}
var metadata = response && response[0];
if (!metadata) {
throw new Error("EXPECTED METADATA");
}
if (metadata.allowed[0] !== alice.edKeys.edPublic) {
throw new Error("EXPECTED ALICE TO BE ON ALLOW LIST");
}
}));
}).nThen(function () {
//setTimeout(w(), 500);
// XXX RESTRICT alice should now be able to read oscar's mailbox metadata
/*
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, function (err, response) {
if (err) {
PROBLEM
}
});
*/
}).nThen(function (w) {
//throw new Error("boop");
// add alice as an owner of oscar's mailbox for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_OWNERS',
channel: oscar.mailboxChannel,
value: [
alice.edKeys.edPublic
]
}, Util.mkTimeout(w(function (err) {
if (err === 'TIMEOUT') {
throw new Error(err);
}
if (err) {
throw new Error("ADD_OWNERS_FAILURE");
}
}), 2000));
}).nThen(function (w) {
// alice should now be able to read oscar's mailbox metadata
alice.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("EXPECTED ALICE TO BE ALLOWED TO READ OSCAR'S METADATA");
}
var metadata = response && response[0];
if (!metadata) { throw new Error("EXPECTED METADATA"); }
if (metadata.allowed.length !== 0) {
throw new Error("EXPECTED AN EMPTY ALLOW LIST");
}
}));
}).nThen(function (w) {
// disable the access restrictionallow list
oscar.rpc.send('SET_METADATA', {
command: 'RESTRICT_ACCESS',
channel: oscar.mailboxChannel,
value: [
false
]
}, w(function (err) {
if (err) {
throw new Error("COULD_NOT_DISABLE_RESTRICTED_ACCESS");
}
}));
// add alice to oscar's mailbox's allow list for some reason
oscar.rpc.send('SET_METADATA', {
command: 'ADD_ALLOWED',
channel: oscar.mailboxChannel,
value: [
bob.edKeys.edPublic
]
}, w(function (err) {
if (err) {
return void console.error(err);
}
}));
}).nThen(function (w) {
oscar.anonRpc.send('GET_METADATA', oscar.mailboxChannel, w(function (err, response) {
if (err) {
throw new Error("OSCAR SHOULD BE ABLE TO READ HIS OWN METADATA");
}
var metadata = response && response[0];
if (!metadata) {
throw new Error("EXPECTED METADATA");
}
if (metadata.allowed[0] !== bob.edKeys.edPublic) {
throw new Error("EXPECTED ALICE TO BE ON ALLOW LIST");
}
if (metadata.restricted) {
throw new Error("RESTRICTED_ACCESS_NOT_DISABLED");
}
}));
}).nThen(function () {
//setTimeout(w(), 500);
}).nThen(function (w) {
// Alice loads the roster...
var rosterKeys = Crypto.Team.deriveMemberKeys(sharedConfig.rosterSeed, alice.curveKeys);

@ -68,6 +68,19 @@
};
};
Util.mkTimeout = function (_f, ms) {
ms = ms || 0;
var f = Util.once(_f);
var timeout = setTimeout(function () {
f('TIMEOUT');
}, ms);
return Util.both(f, function () {
clearTimeout(timeout);
});
};
Util.response = function () {
var pending = {};
var timeouts = {};

Loading…
Cancel
Save