|
|
@ -2,23 +2,169 @@ var Meta = module.exports;
|
|
|
|
|
|
|
|
|
|
|
|
var deduplicate = require("./common-util").deduplicateString;
|
|
|
|
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>
|
|
|
|
* channel <STRING>
|
|
|
|
* validateKey <STRING>
|
|
|
|
* validateKey <STRING>
|
|
|
|
* owners <ARRAY>
|
|
|
|
* owners <ARRAY>
|
|
|
|
* ADD_OWNERS
|
|
|
|
* ADD_OWNERS
|
|
|
|
* RM_OWNERS
|
|
|
|
* RM_OWNERS
|
|
|
|
|
|
|
|
* RESET_OWNERS
|
|
|
|
|
|
|
|
* pending_owners <ARRAY>
|
|
|
|
|
|
|
|
* ADD_PENDING_OWNERS
|
|
|
|
|
|
|
|
* RM_PENDING_OWNERS
|
|
|
|
* expire <NUMBER>
|
|
|
|
* 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 commands = {};
|
|
|
|
|
|
|
|
|
|
|
|
var isValidOwner = function (owner) {
|
|
|
|
var isValidPublicKey = function (owner) {
|
|
|
|
return typeof(owner) === 'string' && owner.length === 44;
|
|
|
|
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]
|
|
|
|
// ["ADD_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
|
|
|
|
commands.ADD_OWNERS = function (meta, args) {
|
|
|
|
commands.ADD_OWNERS = function (meta, args) {
|
|
|
|
// bail out if args isn't an array
|
|
|
|
// bail out if args isn't an array
|
|
|
@ -40,6 +186,13 @@ commands.ADD_OWNERS = function (meta, args) {
|
|
|
|
changed = true;
|
|
|
|
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;
|
|
|
|
return changed;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -141,6 +294,14 @@ commands.RESET_OWNERS = function (meta, args) {
|
|
|
|
|
|
|
|
|
|
|
|
// overwrite the existing owners with the new one
|
|
|
|
// overwrite the existing owners with the new one
|
|
|
|
meta.owners = deduplicate(args.filter(isValidOwner));
|
|
|
|
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;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -178,6 +339,25 @@ commands.ADD_MAILBOX = function (meta, args) {
|
|
|
|
return changed;
|
|
|
|
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 () {
|
|
|
|
commands.UPDATE_EXPIRATION = function () {
|
|
|
|
throw new Error("E_NOT_IMPLEMENTED");
|
|
|
|
throw new Error("E_NOT_IMPLEMENTED");
|
|
|
|
};
|
|
|
|
};
|
|
|
|