kuzzle 2.18.0 → 2.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/index.d.ts +1 -0
  2. package/index.js +3 -0
  3. package/lib/api/controllers/adminController.js +9 -0
  4. package/lib/api/controllers/authController.js +2 -1
  5. package/lib/api/controllers/debugController.d.ts +59 -0
  6. package/lib/api/controllers/debugController.js +285 -0
  7. package/lib/api/controllers/documentController.js +49 -31
  8. package/lib/api/controllers/index.js +1 -0
  9. package/lib/api/controllers/securityController.js +28 -22
  10. package/lib/api/controllers/serverController.js +2 -1
  11. package/lib/api/documentExtractor.js +51 -9
  12. package/lib/api/funnel.js +30 -8
  13. package/lib/api/httpRoutes.js +6 -0
  14. package/lib/api/request/kuzzleRequest.d.ts +37 -4
  15. package/lib/api/request/kuzzleRequest.js +115 -29
  16. package/lib/api/request/requestResponse.js +25 -0
  17. package/lib/cluster/idCardHandler.js +1 -1
  18. package/lib/config/default.config.js +3 -0
  19. package/lib/config/documentEventAliases.d.ts +7 -0
  20. package/lib/config/documentEventAliases.js +26 -12
  21. package/lib/core/backend/backend.d.ts +4 -0
  22. package/lib/core/backend/backend.js +9 -0
  23. package/lib/core/backend/backendController.d.ts +7 -1
  24. package/lib/core/backend/backendController.js +15 -3
  25. package/lib/core/network/protocols/httpwsProtocol.js +14 -6
  26. package/lib/core/plugin/plugin.js +7 -0
  27. package/lib/core/shared/sdk/embeddedSdk.d.ts +1 -1
  28. package/lib/core/shared/sdk/embeddedSdk.js +33 -0
  29. package/lib/kerror/codes/0-core.json +35 -0
  30. package/lib/kerror/codes/2-api.json +6 -0
  31. package/lib/kuzzle/kuzzle.d.ts +1 -1
  32. package/lib/kuzzle/kuzzle.js +27 -8
  33. package/lib/model/storage/apiKey.js +1 -6
  34. package/lib/service/storage/elasticsearch.js +40 -13
  35. package/lib/types/DebugModule.d.ts +23 -0
  36. package/lib/types/DebugModule.js +39 -0
  37. package/lib/types/config/SecurityConfiguration.d.ts +10 -0
  38. package/lib/util/crypto.d.ts +1 -0
  39. package/lib/util/crypto.js +12 -0
  40. package/lib/util/name-generator.d.ts +79 -0
  41. package/lib/util/name-generator.js +1409 -1345
  42. package/lib/util/time.d.ts +1 -0
  43. package/lib/util/time.js +9 -0
  44. package/package.json +4 -6
  45. package/lib/core/security/README.md +0 -224
  46. package/lib/core/shared/README.md +0 -3
  47. package/package-lock.json +0 -8422
@@ -46,6 +46,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
46
46
  return (mod && mod.__esModule) ? mod : { "default": mod };
47
47
  };
48
48
  Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.BACKEND_IMPORT_KEY = void 0;
49
50
  const path_1 = __importDefault(require("path"));
50
51
  const murmurhash_native_1 = require("murmurhash-native");
51
52
  const json_stable_stringify_1 = __importDefault(require("json-stable-stringify"));
@@ -78,7 +79,8 @@ const cluster_1 = __importDefault(require("../cluster"));
78
79
  const package_json_1 = require("../../package.json");
79
80
  const name_generator_1 = require("../util/name-generator");
80
81
  const openapi_1 = require("../api/openapi");
81
- const BACKEND_IMPORT_KEY = 'backend:init:import';
82
+ const crypto_1 = require("../util/crypto");
83
+ exports.BACKEND_IMPORT_KEY = 'backend:init:import';
82
84
  let _kuzzle = null;
83
85
  Reflect.defineProperty(global, 'kuzzle', {
84
86
  configurable: true,
@@ -201,7 +203,7 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
201
203
  this.log.info('[✔] Cluster initialized');
202
204
  }
203
205
  else {
204
- id = (0, name_generator_1.generateRandomName)('knode');
206
+ id = name_generator_1.NameGenerator.generateRandomName({ prefix: 'knode' });
205
207
  this.log.info('[X] Cluster disabled: single node mode.');
206
208
  }
207
209
  return id;
@@ -368,11 +370,10 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
368
370
  */
369
371
  async _waitForImportToFinish() {
370
372
  const importTypes = Object.keys(this.importTypes);
371
- while (importTypes.length) {
373
+ for (const importType of importTypes) {
372
374
  // If the import is done, we pop it from the queue to check the next one
373
- if (await this.ask('core:cache:internal:get', `${BACKEND_IMPORT_KEY}:${importTypes[0]}`)) {
374
- importTypes.shift();
375
- continue;
375
+ if (await this.ask('core:cache:internal:get', `${exports.BACKEND_IMPORT_KEY}:${importType}`)) {
376
+ return;
376
377
  }
377
378
  await bluebird_1.default.delay(1000);
378
379
  }
@@ -399,8 +400,26 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
399
400
  const lockedMutex = [];
400
401
  try {
401
402
  for (const [type, importMethod] of Object.entries(this.importTypes)) {
403
+ const importPayload = {};
404
+ switch (type) {
405
+ case 'fixtures':
406
+ lodash_1.default.set(importPayload, 'toSupport.fixtures', toSupport.fixtures);
407
+ break;
408
+ case 'mappings':
409
+ lodash_1.default.set(importPayload, 'toSupport.mappings', toSupport.mappings);
410
+ lodash_1.default.set(importPayload, 'toImport.mappings', toImport.mappings);
411
+ break;
412
+ case 'permissions':
413
+ lodash_1.default.set(importPayload, 'toSupport.securities', toSupport.securities);
414
+ lodash_1.default.set(importPayload, 'toImport.profiles', toImport.profiles);
415
+ lodash_1.default.set(importPayload, 'toImport.roles', toImport.roles);
416
+ lodash_1.default.set(importPayload, 'toImport.users', toImport.users);
417
+ break;
418
+ }
419
+ const importPayloadHash = (0, crypto_1.sha256)((0, json_stable_stringify_1.default)(importPayload));
402
420
  const mutex = new mutex_1.Mutex(`backend:import:${type}`, { timeout: 0 });
403
- const initialized = await this.ask('core:cache:internal:get', `${BACKEND_IMPORT_KEY}:${type}`) === '1';
421
+ const existingHash = await this.ask('core:cache:internal:get', `${exports.BACKEND_IMPORT_KEY}:${type}`);
422
+ const initialized = existingHash === importPayloadHash;
404
423
  const locked = await mutex.lock();
405
424
  await importMethod({ toImport, toSupport }, {
406
425
  firstCall: !initialized && locked,
@@ -409,7 +428,7 @@ class Kuzzle extends kuzzleEventEmitter_1.default {
409
428
  });
410
429
  if (!initialized && locked) {
411
430
  lockedMutex.push(mutex);
412
- await this.ask('core:cache:internal:store', `${BACKEND_IMPORT_KEY}:${type}`, 1, { ttl: 5 * 60 * 1000 });
431
+ await this.ask('core:cache:internal:store', `${exports.BACKEND_IMPORT_KEY}:${type}`, importPayloadHash);
413
432
  }
414
433
  }
415
434
  await this._waitForImportToFinish();
@@ -21,16 +21,11 @@
21
21
 
22
22
  'use strict';
23
23
 
24
- const crypto = require('crypto');
25
-
24
+ const { sha256 } = require('../../util/crypto');
26
25
  const debug = require('../../util/debug')('models:storage:apiKey');
27
26
  const kerror = require('../../kerror');
28
27
  const BaseModel = require('./baseModel');
29
28
 
30
- function sha256 (string) {
31
- return crypto.createHash('sha256').update(string).digest('hex');
32
- }
33
-
34
29
  class ApiKey extends BaseModel {
35
30
  constructor (_source, _id = null) {
36
31
  super(_source, _id);
@@ -182,7 +182,7 @@ class ElasticSearch extends Service {
182
182
  /**
183
183
  * Translate Koncorde filters to Elasticsearch query
184
184
  *
185
- * @param {Object} koncordeFilters - Set of valid Koncorde filters
185
+ * @param {Object} filters - Set of valid Koncorde filters
186
186
  * @returns {Object} Equivalent Elasticsearch query
187
187
  */
188
188
  translateKoncordeFilters (filters) {
@@ -1435,10 +1435,9 @@ class ElasticSearch extends Service {
1435
1435
  // rollback the whole operation. Since mappings can't be rollback, we try to
1436
1436
  // update the settings first, then the mappings and we rollback the settings
1437
1437
  // if putMappings fail.
1438
- let indiceSettings;
1438
+ let indexSettings;
1439
1439
  try {
1440
- const response = await this._client.indices.getSettings(esRequest);
1441
- indiceSettings = response.body[esRequest.index].settings;
1440
+ indexSettings = await this._getSettings(esRequest);
1442
1441
  }
1443
1442
  catch (error) {
1444
1443
  throw this._esWrapper.formatESError(error);
@@ -1460,11 +1459,7 @@ class ElasticSearch extends Service {
1460
1459
  }
1461
1460
  }
1462
1461
  catch (error) {
1463
- const allowedSettings = {
1464
- index: _.omit(
1465
- indiceSettings.index,
1466
- ['creation_date', 'provided_name', 'uuid', 'version'])
1467
- };
1462
+ const allowedSettings = this.getAllowedIndexSettings(indexSettings);
1468
1463
 
1469
1464
  // Rollback to previous settings
1470
1465
  if (! _.isEmpty(settings)) {
@@ -1477,6 +1472,20 @@ class ElasticSearch extends Service {
1477
1472
  return null;
1478
1473
  }
1479
1474
 
1475
+ /**
1476
+ * Given index settings we return a new version of index settings
1477
+ * only with allowed settings that can be set (during update or create index).
1478
+ * @param indexSettings the index settings
1479
+ * @returns {{index: *}} a new index settings with only allowed settings.
1480
+ */
1481
+ getAllowedIndexSettings (indexSettings) {
1482
+ return {
1483
+ index: _.omit(
1484
+ indexSettings.index,
1485
+ ['creation_date', 'provided_name', 'uuid', 'version'])
1486
+ };
1487
+ }
1488
+
1480
1489
  /**
1481
1490
  * Sends an empty UpdateByQuery request to update the search index
1482
1491
  *
@@ -1583,7 +1592,7 @@ class ElasticSearch extends Service {
1583
1592
  }
1584
1593
 
1585
1594
  /**
1586
- * Empties the content of a collection. Keep the existing mapping.
1595
+ * Empties the content of a collection. Keep the existing mapping and settings.
1587
1596
  *
1588
1597
  * @param {String} index - Index name
1589
1598
  * @param {String} collection - Collection name
@@ -1592,6 +1601,7 @@ class ElasticSearch extends Service {
1592
1601
  */
1593
1602
  async truncateCollection (index, collection) {
1594
1603
  let mappings;
1604
+ let settings;
1595
1605
 
1596
1606
  const esRequest = {
1597
1607
  index: await this._getIndice(index, collection)
@@ -1599,7 +1609,11 @@ class ElasticSearch extends Service {
1599
1609
 
1600
1610
  try {
1601
1611
  mappings = await this.getMapping(index, collection, { includeKuzzleMeta: true });
1602
-
1612
+ settings = await this._getSettings(esRequest);
1613
+ settings = {
1614
+ ...settings,
1615
+ ...this.getAllowedIndexSettings(settings)
1616
+ };
1603
1617
  await this._client.indices.delete(esRequest);
1604
1618
 
1605
1619
  await this._client.indices.create({
@@ -1608,7 +1622,8 @@ class ElasticSearch extends Service {
1608
1622
  aliases: {
1609
1623
  [this._getAlias(index, collection)]: {}
1610
1624
  },
1611
- mappings
1625
+ mappings,
1626
+ settings
1612
1627
  }
1613
1628
  });
1614
1629
 
@@ -2535,7 +2550,7 @@ class ElasticSearch extends Service {
2535
2550
  *
2536
2551
  * @param {String} index - Index name
2537
2552
  * @param {String} collection - Collection name
2538
- * @param {Array.<String>} documents - Documents IDs
2553
+ * @param {Array.<String>} ids - Documents IDs
2539
2554
  * @param {Object} options - timeout (undefined), refresh (undefined)
2540
2555
  *
2541
2556
  * @returns {Promise.<{ documents, errors }>
@@ -2857,6 +2872,18 @@ class ElasticSearch extends Service {
2857
2872
  return body[0].index;
2858
2873
  }
2859
2874
 
2875
+ /**
2876
+ * Given an ES Request returns the settings of the corresponding indice.
2877
+ *
2878
+ * @param esRequest the ES Request with wanted settings.
2879
+ * @return {Promise<*>} the settings of the indice.
2880
+ * @private
2881
+ */
2882
+ async _getSettings (esRequest) {
2883
+ const response = await this._client.indices.getSettings(esRequest);
2884
+ return response.body[esRequest.index].settings;
2885
+ }
2886
+
2860
2887
  /**
2861
2888
  * Given index + collection, returns an available indice name.
2862
2889
  * Use this function when creating the associated indice. Otherwise use `_getAlias`.
@@ -0,0 +1,23 @@
1
+ /// <reference types="node" />
2
+ import EventEmitter from 'events';
3
+ import Inspector from 'inspector';
4
+ export declare type DebugModuleOptions = {
5
+ methods?: string[];
6
+ events?: string[];
7
+ };
8
+ export declare abstract class DebugModule extends EventEmitter {
9
+ name: string;
10
+ methods: string[];
11
+ events: string[];
12
+ /**
13
+ * Called when the module is loaded, after the debugger has been enabled
14
+ */
15
+ abstract init(inspector: Inspector.Session): Promise<void>;
16
+ /**
17
+ * Called when the module should be cleaned up.
18
+ * - After the Debug Controller has been disabled
19
+ * - Before the debugger is disconnected
20
+ */
21
+ abstract cleanup(): Promise<void>;
22
+ constructor(name: string, options?: DebugModuleOptions);
23
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DebugModule = void 0;
7
+ const events_1 = __importDefault(require("events"));
8
+ class DebugModule extends events_1.default {
9
+ constructor(name, options = {}) {
10
+ super();
11
+ this.name = name;
12
+ this.methods = options.methods || [];
13
+ this.events = options.events || [];
14
+ if (!this.name || this.name.length === 0) {
15
+ throw new Error('DebugModule should have a name');
16
+ }
17
+ if (this.name.charAt(0) !== this.name.charAt(0).toUpperCase()) {
18
+ throw new Error(`Debug Module name "${name}" should start with an uppercase letter`);
19
+ }
20
+ for (const event of this.events) {
21
+ if (event.length === 0) {
22
+ throw new Error(`Event name should not be empty for "${name}"`);
23
+ }
24
+ if (event.charAt(0) !== event.charAt(0).toLowerCase()) {
25
+ throw new Error(`Event name "${event}" should start with a lowercase letter for module "${name}"`);
26
+ }
27
+ }
28
+ for (const method of this.methods) {
29
+ if (method.length === 0) {
30
+ throw new Error(`Method name should not be empty for Debug Module "${name}"`);
31
+ }
32
+ if (method.charAt(0) !== method.charAt(0).toLowerCase()) {
33
+ throw new Error(`Method name "${method}" should start with a lowercase letter for module "${name}"`);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ exports.DebugModule = DebugModule;
39
+ //# sourceMappingURL=DebugModule.js.map
@@ -2,6 +2,16 @@
2
2
  import { JSONObject } from '../../../index';
3
3
  import { RoleDefinition, ProfileDefinition } from '../index';
4
4
  export declare type SecurityConfiguration = {
5
+ /**
6
+ * Debugger configuration
7
+ */
8
+ debug: {
9
+ /**
10
+ * Allow to use the Chrome DevTools Protocol directly
11
+ * through `debug:post`
12
+ */
13
+ native_debug_protocol: boolean;
14
+ };
5
15
  /**
6
16
  * The profileIds applied to a user created with the API action
7
17
  * `security:createRestrictedUser`.
@@ -0,0 +1 @@
1
+ export declare function sha256(string: string): string;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sha256 = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ function sha256(string) {
9
+ return crypto_1.default.createHash('sha256').update(string).digest('hex');
10
+ }
11
+ exports.sha256 = sha256;
12
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1,79 @@
1
+ export declare type GenerateRandomNameOpts = {
2
+ /**
3
+ * Optional prefix.
4
+ */
5
+ prefix?: string;
6
+ /**
7
+ * Optional separator. Defaults to `-`.
8
+ */
9
+ separator?: string;
10
+ /**
11
+ * Optional postfix random number range. Set to `false` to disable postfix. Defaults to `{ min: 0, max: 100000 }`
12
+ */
13
+ postfixRandRange?: {
14
+ /**
15
+ * Optional minimum postfix random number (inclusive). Defaults to `0`.
16
+ */
17
+ min?: number;
18
+ /**
19
+ * Maximum postfix random number (exclusive).
20
+ */
21
+ max: number;
22
+ } | false;
23
+ };
24
+ export declare class NameGenerator {
25
+ /**
26
+ * Returns a random name.
27
+ *
28
+ * # Usage:
29
+ *
30
+ * ```js
31
+ * const name = NameGenerator.getRandomName(); // 'verdi'
32
+ * ```
33
+ *
34
+ * @returns a random name
35
+ */
36
+ static getRandomName(): string;
37
+ /**
38
+ * Returns a random adjective.
39
+ *
40
+ * # Usage
41
+ *
42
+ * ```js
43
+ * const adj = NameGenerator.getRandomAdjective(); // 'absent'
44
+ * ```
45
+ *
46
+ * @returns a random adjective
47
+ */
48
+ static getRandomAdjective(): string;
49
+ /**
50
+ * Generates a random formatted name that consists of an optional prefix, a random adjective,
51
+ * a random name and an optional random number separated by separator (default: '-').
52
+ *
53
+ * Format: `[prefix<separator>]<adjective><separator><name>[<separator>random number]`
54
+ *
55
+ * Format example: `something-dashing-euler-1164`
56
+ *
57
+ * ## Usage
58
+ *
59
+ * ```js
60
+ * let name = NameGenerator.generateRandomName({
61
+ * prefix: 'my',
62
+ * separator: '_',
63
+ * postfixRandRange: { min: 1, max: 10 }
64
+ * }); // 'my_abandoned_yogi_5'
65
+ *
66
+ * name = NameGenerator.generateRandomName({
67
+ * separator: ' ',
68
+ * postfixRandRange: false
69
+ * }); // 'amused vampire'
70
+ * ```
71
+ *
72
+ * @param {GenerateRandomNameOpts} opts Optional options
73
+ *
74
+ * @returns a random formatted name
75
+ */
76
+ static generateRandomName({ prefix, separator, postfixRandRange, }?: GenerateRandomNameOpts): string;
77
+ }
78
+ export declare function randomNumber(max: number): number;
79
+ export declare function randomNumber(min: number, max: number): number;