You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
13 KiB

11 months ago
  1. var Meta = module.exports;
  2. var deduplicate = require("./common-util").deduplicateString;
  3. /* Metadata fields and the commands that can modify them
  4. we assume that these commands can only be performed
  5. by owners or in some cases pending owners. Thus
  6. the owners field is guaranteed to exist.
  7. * channel <STRING>
  8. * validateKey <STRING>
  9. * owners <ARRAY>
  10. * ADD_OWNERS
  11. * RM_OWNERS
  12. * RESET_OWNERS
  13. * pending_owners <ARRAY>
  14. * ADD_PENDING_OWNERS
  15. * RM_PENDING_OWNERS
  16. * expire <NUMBER>
  17. * UPDATE_EXPIRATION (NOT_IMPLEMENTED)
  18. * restricted <BOOLEAN>
  19. * RESTRICT_ACCESS
  20. * allowed <ARRAY>
  21. * ADD_ALLOWED
  22. * RM_ALLOWED
  23. * RESET_ALLOWED
  24. * ADD_OWNERS
  25. * RESET_OWNERS
  26. * mailbox <STRING|MAP>
  27. * ADD_MAILBOX
  28. * RM_MAILBOX
  29. */
  30. var commands = {};
  31. var isValidPublicKey = function (owner) {
  32. return typeof(owner) === 'string' && owner.length === 44;
  33. };
  34. // isValidPublicKey is a better indication of what the above function does
  35. // I'm preserving this function name in case we ever want to expand its
  36. // criteria at a later time...
  37. var isValidOwner = isValidPublicKey;
  38. // ["RESTRICT_ACCESS", [true], 1561623438989]
  39. // ["RESTRICT_ACCESS", [false], 1561623438989]
  40. commands.RESTRICT_ACCESS = function (meta, args) {
  41. if (!Array.isArray(args) || typeof(args[0]) !== 'boolean') {
  42. throw new Error('INVALID_STATE');
  43. }
  44. var bool = args[0];
  45. // reject the proposed command if there is no change in state
  46. if (meta.restricted === bool) { return false; }
  47. // apply the new state
  48. meta.restricted = args[0];
  49. // if you're disabling access restrictions then you can assume
  50. // then there is nothing more to do. Leave the existing list as-is
  51. if (!bool) { return true; }
  52. // you're all set if an allow list already exists
  53. if (Array.isArray(meta.allowed)) { return true; }
  54. // otherwise define it
  55. meta.allowed = [];
  56. return true;
  57. };
  58. // ["ADD_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
  59. commands.ADD_ALLOWED = function (meta, args) {
  60. if (!Array.isArray(args)) {
  61. throw new Error("INVALID_ARGS");
  62. }
  63. var allowed = meta.allowed || [];
  64. var changed = false;
  65. args.forEach(function (arg) {
  66. // don't add invalid public keys
  67. if (!isValidPublicKey(arg)) { return; }
  68. // don't add owners to the allow list
  69. if (meta.owners.indexOf(arg) >= 0) { return; }
  70. // don't duplicate entries in the allow list
  71. if (allowed.indexOf(arg) >= 0) { return; }
  72. allowed.push(arg);
  73. changed = true;
  74. });
  75. if (changed) {
  76. meta.allowed = meta.allowed || allowed;
  77. }
  78. return changed;
  79. };
  80. // ["RM_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
  81. commands.RM_ALLOWED = function (meta, args) {
  82. if (!Array.isArray(args)) {
  83. throw new Error("INVALID_ARGS");
  84. }
  85. // there may not be anything to remove
  86. if (!meta.allowed) { return false; }
  87. var changed = false;
  88. args.forEach(function (arg) {
  89. var index = meta.allowed.indexOf(arg);
  90. if (index < 0) { return; }
  91. meta.allowed.splice(index, 1);
  92. changed = true;
  93. });
  94. return changed;
  95. };
  96. var arrayHasChanged = function (A, B) {
  97. var changed;
  98. A.some(function (a) {
  99. if (B.indexOf(a) < 0) { return (changed = true); }
  100. });
  101. if (changed) { return true; }
  102. B.some(function (b) {
  103. if (A.indexOf(b) < 0) { return (changed = true); }
  104. });
  105. return changed;
  106. };
  107. var filterInPlace = function (A, f) {
  108. for (var i = A.length - 1; i >= 0; i--) {
  109. if (f(A[i], i, A)) { A.splice(i, 1); }
  110. }
  111. };
  112. // ["RESET_ALLOWED", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=", ...], 1561623438989]
  113. commands.RESET_ALLOWED = function (meta, args) {
  114. if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
  115. var updated = args.filter(function (arg) {
  116. // don't allow invalid public keys
  117. if (!isValidPublicKey(arg)) { return false; }
  118. // don't ever add owners to the allow list
  119. if (meta.owners.indexOf(arg)) { return false; }
  120. return true;
  121. });
  122. // this is strictly an optimization...
  123. // a change in length is a clear indicator of a functional change
  124. if (meta.allowed && meta.allowed.length !== updated.length) {
  125. meta.allowed = updated;
  126. return true;
  127. }
  128. // otherwise we must check that the arrays contain distinct elements
  129. // if there is no functional change, then return false
  130. if (!arrayHasChanged(meta.allowed, updated)) { return false; }
  131. // otherwise overwrite the in-memory data and indicate that there was a change
  132. meta.allowed = updated;
  133. return true;
  134. };
  135. // ["ADD_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
  136. commands.ADD_OWNERS = function (meta, args) {
  137. // bail out if args isn't an array
  138. if (!Array.isArray(args)) {
  139. throw new Error('METADATA_INVALID_OWNERS');
  140. }
  141. // you shouldn't be able to get here if there are no owners
  142. // because only an owner should be able to change the owners
  143. if (!Array.isArray(meta.owners)) {
  144. throw new Error("METADATA_NONSENSE_OWNERS");
  145. }
  146. var changed = false;
  147. args.forEach(function (owner) {
  148. if (!isValidOwner(owner)) { return; }
  149. if (meta.owners.indexOf(owner) >= 0) { return; }
  150. meta.owners.push(owner);
  151. changed = true;
  152. });
  153. if (changed && Array.isArray(meta.allowed)) {
  154. // make sure owners are not included in the allow list
  155. filterInPlace(meta.allowed, function (member) {
  156. return meta.owners.indexOf(member) !== -1;
  157. });
  158. }
  159. return changed;
  160. };
  161. // ["RM_OWNERS", ["CrufexqXcY-z+eKJlEbNELVy5Sb7E-EAAEFI8GnEtZ0="], 1561623439989]
  162. commands.RM_OWNERS = function (meta, args) {
  163. // what are you doing if you don't have owners to remove?
  164. if (!Array.isArray(args)) {
  165. throw new Error('METADATA_INVALID_OWNERS');
  166. }
  167. // if there aren't any owners to start, this is also pointless
  168. if (!Array.isArray(meta.owners)) {
  169. throw new Error("METADATA_NONSENSE_OWNERS");
  170. }
  171. var changed = false;
  172. // remove owners one by one
  173. // we assume there are no duplicates
  174. args.forEach(function (owner) {
  175. var index = meta.owners.indexOf(owner);
  176. if (index < 0) { return; }
  177. if (meta.mailbox) {
  178. if (typeof(meta.mailbox) === "string") {
  179. delete meta.mailbox;
  180. } else {
  181. delete meta.mailbox[owner];
  182. }
  183. }
  184. meta.owners.splice(index, 1);
  185. changed = true;
  186. });
  187. if (meta.owners.length === 0 && meta.restricted) {
  188. meta.restricted = false;
  189. }
  190. return changed;
  191. };
  192. // ["ADD_PENDING_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623438989]
  193. commands.ADD_PENDING_OWNERS = function (meta, args) {
  194. // bail out if args isn't an array
  195. if (!Array.isArray(args)) {
  196. throw new Error('METADATA_INVALID_PENDING_OWNERS');
  197. }
  198. // you shouldn't be able to get here if there are no owners
  199. // because only an owner should be able to change the owners
  200. if (meta.pending_owners && !Array.isArray(meta.pending_owners)) {
  201. throw new Error("METADATA_NONSENSE_PENDING_OWNERS");
  202. }
  203. var changed = false;
  204. // Add pending_owners array if it doesn't exist
  205. if (!meta.pending_owners) {
  206. meta.pending_owners = deduplicate(args);
  207. return true;
  208. }
  209. // or fill it
  210. args.forEach(function (owner) {
  211. if (!isValidOwner(owner)) { return; }
  212. if (meta.pending_owners.indexOf(owner) >= 0) { return; }
  213. meta.pending_owners.push(owner);
  214. changed = true;
  215. });
  216. return changed;
  217. };
  218. // ["RM_PENDING_OWNERS", ["CrufexqXcY-z+eKJlEbNELVy5Sb7E-EAAEFI8GnEtZ0="], 1561623439989]
  219. commands.RM_PENDING_OWNERS = function (meta, args) {
  220. // what are you doing if you don't have owners to remove?
  221. if (!Array.isArray(args)) {
  222. throw new Error('METADATA_INVALID_PENDING_OWNERS');
  223. }
  224. // if there aren't any owners to start, this is also pointless
  225. if (!Array.isArray(meta.pending_owners)) {
  226. throw new Error("METADATA_NONSENSE_PENDING_OWNERS");
  227. }
  228. var changed = false;
  229. // remove owners one by one
  230. // we assume there are no duplicates
  231. args.forEach(function (owner) {
  232. var index = meta.pending_owners.indexOf(owner);
  233. if (index < 0) { return; }
  234. meta.pending_owners.splice(index, 1);
  235. changed = true;
  236. });
  237. return changed;
  238. };
  239. // ["RESET_OWNERS", ["7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I="], 1561623439989]
  240. commands.RESET_OWNERS = function (meta, args) {
  241. // expect a new array, even if it's empty
  242. if (!Array.isArray(args)) {
  243. throw new Error('METADATA_INVALID_OWNERS');
  244. }
  245. // assume there are owners to start
  246. if (!Array.isArray(meta.owners)) {
  247. throw new Error("METADATA_NONSENSE_OWNERS");
  248. }
  249. // overwrite the existing owners with the new one
  250. meta.owners = deduplicate(args.filter(isValidOwner));
  251. if (Array.isArray(meta.allowed)) {
  252. // make sure owners are not included in the allow list
  253. filterInPlace(meta.allowed, function (member) {
  254. return meta.owners.indexOf(member) !== -1;
  255. });
  256. }
  257. if (meta.owners.length === 0 && meta.restricted) {
  258. meta.restricted = false;
  259. }
  260. return true;
  261. };
  262. // ["ADD_MAILBOX", {"7eEqelGso3EBr5jHlei6av4r9w2B9XZiGGwA1EgZ-5I=": mailbox, ...}, 1561623439989]
  263. commands.ADD_MAILBOX = function (meta, args) {
  264. // expect a new array, even if it's empty
  265. if (!args || typeof(args) !== "object") {
  266. throw new Error('METADATA_INVALID_MAILBOX');
  267. }
  268. // assume there are owners to start
  269. if (!Array.isArray(meta.owners)) {
  270. throw new Error("METADATA_NONSENSE_OWNERS");
  271. }
  272. var changed = false;
  273. // For each mailbox we try to add, check if the associated edPublic is an owner
  274. // If they are, add or replace the mailbox
  275. Object.keys(args).forEach(function (edPublic) {
  276. if (meta.owners.indexOf(edPublic) === -1) { return; }
  277. if (typeof(meta.mailbox) === "string") {
  278. var str = meta.mailbox;
  279. meta.mailbox = {};
  280. meta.mailbox[meta.owners[0]] = str;
  281. }
  282. // Make sure mailbox is defined
  283. if (!meta.mailbox) { meta.mailbox = {}; }
  284. meta.mailbox[edPublic] = args[edPublic];
  285. changed = true;
  286. });
  287. return changed;
  288. };
  289. commands.RM_MAILBOX = function (meta, args) {
  290. if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
  291. if (!meta.mailbox || typeof(meta.mailbox) === 'undefined') {
  292. return false;
  293. }
  294. if (typeof(meta.mailbox) === 'string' && args.length === 0) {
  295. delete meta.mailbox;
  296. return true;
  297. }
  298. var changed = false;
  299. args.forEach(function (arg) {
  300. if (meta.mailbox[arg] === 'undefined') { return; }
  301. delete meta.mailbox[arg];
  302. changed = true;
  303. });
  304. return changed;
  305. };
  306. commands.UPDATE_EXPIRATION = function () {
  307. throw new Error("E_NOT_IMPLEMENTED");
  308. };
  309. var handleCommand = Meta.handleCommand = function (meta, line) {
  310. var command = line[0];
  311. var args = line[1];
  312. //var time = line[2];
  313. if (typeof(commands[command]) !== 'function') {
  314. throw new Error("METADATA_UNSUPPORTED_COMMAND");
  315. }
  316. return commands[command](meta, args);
  317. };
  318. Meta.commands = Object.keys(commands);
  319. Meta.createLineHandler = function (ref, errorHandler) {
  320. ref.meta = {};
  321. ref.index = 0;
  322. ref.logged = {};
  323. return function (err, line) {
  324. if (err) {
  325. // it's not abnormal that metadata exists without a corresponding log
  326. // so ENOENT is fine
  327. if (ref.index === 0 && err.code === 'ENOENT') { return; }
  328. // any other errors are abnormal
  329. return void errorHandler('METADATA_HANDLER_LINE_ERR', {
  330. error: err,
  331. index: ref.index,
  332. line: JSON.stringify(line),
  333. });
  334. }
  335. // the case above is special, everything else should increment the index
  336. var index = ref.index++;
  337. if (typeof(line) === 'undefined') { return; }
  338. if (Array.isArray(line)) {
  339. try {
  340. handleCommand(ref.meta, line);
  341. } catch (err2) {
  342. var code = err2.message;
  343. if (ref.logged[code]) { return; }
  344. ref.logged[code] = true;
  345. errorHandler("METADATA_COMMAND_ERR", {
  346. error: err2.stack,
  347. line: line,
  348. });
  349. }
  350. return;
  351. }
  352. // the first line of a channel is processed before the dedicated metadata log.
  353. // it can contain a map, in which case it should be used as the initial state.
  354. // it's possible that a trim-history command was interrupted, in which case
  355. // this first message might exist in parallel with the more recent metadata log
  356. // which will contain the computed state of the previous metadata log
  357. // which has since been archived.
  358. // Thus, accept both the first and second lines you process as valid initial state
  359. // preferring the second if it exists
  360. if (index < 2 && line && typeof(line) === 'object') {
  361. // special case!
  362. ref.meta = line;
  363. return;
  364. }
  365. errorHandler("METADATA_HANDLER_WEIRDLINE", {
  366. line: line,
  367. index: index,
  368. });
  369. };
  370. };