/*jshint esversion: 6 */ const Data = module.exports; const Meta = require("../metadata"); const WriteQueue = require("../write-queue"); const Core = require("./core"); const Util = require("../common-util"); const HK = require("../hk-util"); Data.getMetadataRaw = function (Env, channel /* channelName */, _cb) { const cb = Util.once(Util.mkAsync(_cb)); if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } if (channel.length !== HK.STANDARD_CHANNEL_LENGTH) { return cb("INVALID_CHAN_LENGTH"); } var cached = Env.metadata_cache[channel]; if (HK.isMetadataMessage(cached)) { return void cb(void 0, cached); } Env.batchMetadata(channel, cb, function (done) { var ref = {}; var lineHandler = Meta.createLineHandler(ref, Env.Log.error); return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) { if (err) { // stream errors? return void done(err); } done(void 0, ref.meta); }); }); }; Data.getMetadata = function (Env, channel, cb, Server, netfluxId) { Data.getMetadataRaw(Env, channel, function (err, metadata) { if (err) { return void cb(err); } if (!(metadata && metadata.restricted)) { // if it's not restricted then just call back return void cb(void 0, metadata); } const session = HK.getNetfluxSession(Env, netfluxId); const allowed = HK.listAllowedUsers(metadata); if (!HK.isUserSessionAllowed(allowed, session)) { return void cb(void 0, { restricted: metadata.restricted, allowed: allowed, }); } cb(void 0, metadata); }); }; /* setMetadata - write a new line to the metadata log if a valid command is provided - data is an object: { channel: channelId, command: metadataCommand (string), value: value } */ var queueMetadata = WriteQueue(); Data.setMetadata = function (Env, safeKey, data, cb, Server) { var unsafeKey = Util.unescapeKeyCharacters(safeKey); var channel = data.channel; 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 cb('UNSUPPORTED_COMMAND'); } queueMetadata(channel, function (next) { Data.getMetadataRaw(Env, channel, function (err, metadata) { if (err) { cb(err); return void next(); } if (!Core.hasOwners(metadata)) { cb('E_NO_OWNERS'); return void next(); } // if you are a pending owner and not an owner // you can either ADD_OWNERS, or RM_PENDING_OWNERS // and you should only be able to add yourself as an owner // everything else should be rejected // else if you are not an owner // you should be rejected // else write the command // Confirm that the channel is owned by the user in question // or the user is accepting a pending ownership offer if (Core.hasPendingOwners(metadata) && Core.isPendingOwner(metadata, unsafeKey) && !Core.isOwner(metadata, unsafeKey)) { // If you are a pending owner, make sure you can only add yourelf as an owner if ((command !== 'ADD_OWNERS' && command !== 'RM_PENDING_OWNERS') || !Array.isArray(data.value) || data.value.length !== 1 || data.value[0] !== unsafeKey) { cb('INSUFFICIENT_PERMISSIONS'); return void next(); } // FIXME wacky fallthrough is hard to read // we could pass this off to a writeMetadataCommand function // and make the flow easier to follow } else if (!Core.isOwner(metadata, unsafeKey)) { cb('INSUFFICIENT_PERMISSIONS'); return void next(); } // Add the new metadata line var line = [command, data.value, +new Date()]; var changed = false; try { changed = Meta.handleCommand(metadata, line); } catch (e) { cb(e); return void next(); } // if your command is valid but it didn't result in any change to the metadata, // call back now and don't write any "useless" line to the log if (!changed) { cb(void 0, metadata); return void next(); } Env.msgStore.writeMetadata(channel, JSON.stringify(line), function (e) { if (e) { cb(e); return void next(); } // send the message back to the person who changed it // since we know they're allowed to see it cb(void 0, metadata); next(); const metadata_cache = Env.metadata_cache; const channel_cache = Env.channel_cache; // update the cached metadata metadata_cache[channel] = metadata; // as well as the metadata that's attached to the index... // XXX determine if we actually need this... var index = Util.find(channel_cache, [channel, 'index']); if (index && typeof(index) === 'object') { index.metadata = metadata; } // it's easy to check if the channel is restricted const isRestricted = metadata.restricted; // and these values will be used in any case const s_metadata = JSON.stringify(metadata); const hk_id = Env.historyKeeper.id; if (!isRestricted) { // pre-allow-list behaviour // if it's not restricted, broadcast the new metadata to everyone return void Server.channelBroadcast(channel, s_metadata, hk_id); } // otherwise derive the list of users (unsafeKeys) that are allowed to stay const allowed = HK.listAllowedUsers(metadata); // anyone who is not allowed will get the same error message const s_error = JSON.stringify({ error: 'ERESTRICTED', channel: channel, }); // iterate over the channel's userlist const toRemove = []; Server.getChannelUserList(channel).forEach(function (userId) { const session = HK.getNetfluxSession(Env, userId); // if the user is allowed to remain, send them the metadata if (HK.isUserSessionAllowed(allowed, session)) { return void Server.send(userId, [ 0, hk_id, 'MSG', userId, s_metadata ], function () {}); } // otherwise they are not in the list. // send them an error and kick them out! Server.send(userId, [ 0, hk_id, 'MSG', userId, s_error ], function () {}); }); Server.removeFromChannel(channel, toRemove); }); }); }); };