kuzzle 2.18.0 → 2.18.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.
@@ -35,6 +35,7 @@ const { NativeController } = require('./baseController');
35
35
  const formatProcessing = require('../../core/auth/formatProcessing');
36
36
  const { User } = require('../../model/security/user');
37
37
  const ApiKey = require('../../model/storage/apiKey');
38
+ const SecurityController = require('./securityController');
38
39
 
39
40
  /**
40
41
  * @class AuthController
@@ -471,7 +472,7 @@ class AuthController extends NativeController {
471
472
  userId,
472
473
  });
473
474
 
474
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on user "${userId}."`);
475
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on user "${userId}."`);
475
476
 
476
477
  return formatProcessing.serializeUser(user);
477
478
  }
@@ -228,8 +228,12 @@ class DocumentController extends NativeController {
228
228
  mimeType = 'text/csv';
229
229
  }
230
230
 
231
- request.response.setHeader('Content-Type', mimeType);
232
- request.response.setHeader('Content-Disposition', `attachment; filename="${index}-${collection}.${format}"`);
231
+ request.response.configure({
232
+ headers: {
233
+ 'Content-Disposition': `attachment; filename="${index}-${collection}.${format}"`,
234
+ 'Content-Type': mimeType,
235
+ }
236
+ });
233
237
 
234
238
  return dumpCollectionDocuments(
235
239
  index,
@@ -38,6 +38,11 @@ const { generateRandomName } = require('../../util/name-generator');
38
38
  * @class SecurityController
39
39
  */
40
40
  class SecurityController extends NativeController {
41
+
42
+ static userOrSdk (userId) {
43
+ return userId === null ? 'EmbeddedSDK' : `User "${userId}"`;
44
+ }
45
+
41
46
  constructor () {
42
47
  super([
43
48
  'checkRights',
@@ -150,7 +155,7 @@ class SecurityController extends NativeController {
150
155
  refresh,
151
156
  });
152
157
 
153
- global.kuzzle.log.info(`[SECURITY] User "${creatorId}" applied action "${request.input.action}" on user "${userId}."`);
158
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(creatorId)} applied action "${request.input.action}" on user "${userId}."`);
154
159
  return apiKey.serialize({ includeToken: true });
155
160
  }
156
161
 
@@ -372,7 +377,7 @@ class SecurityController extends NativeController {
372
377
  userId,
373
378
  });
374
379
 
375
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on role "${role._id}."`);
380
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on role "${role._id}."`);
376
381
  return formatProcessing.serializeRole(role);
377
382
  }
378
383
 
@@ -393,7 +398,7 @@ class SecurityController extends NativeController {
393
398
  userId,
394
399
  });
395
400
 
396
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on role "${role._id}."`);
401
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on role "${role._id}."`);
397
402
  return formatProcessing.serializeRole(role);
398
403
  }
399
404
 
@@ -410,7 +415,7 @@ class SecurityController extends NativeController {
410
415
  refresh: request.getRefresh('wait_for')
411
416
  });
412
417
 
413
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action} on role "${id}."`);
418
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action} on role "${id}."`);
414
419
 
415
420
  // @todo This avoids a breaking change... but we should really return
416
421
  // an acknowledgment.
@@ -473,7 +478,7 @@ class SecurityController extends NativeController {
473
478
  userId,
474
479
  });
475
480
 
476
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on profile "${profile._id}."`);
481
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on profile "${profile._id}."`);
477
482
 
478
483
  return formatProcessing.serializeProfile(profile);
479
484
  }
@@ -502,7 +507,7 @@ class SecurityController extends NativeController {
502
507
  userId,
503
508
  });
504
509
 
505
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on profile "${profile._id}."`);
510
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on profile "${profile._id}."`);
506
511
 
507
512
  return formatProcessing.serializeProfile(profile);
508
513
  }
@@ -523,7 +528,7 @@ class SecurityController extends NativeController {
523
528
 
524
529
  await this.ask('core:security:profile:delete', id, options);
525
530
 
526
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on profile "${id}."`);
531
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on profile "${id}."`);
527
532
 
528
533
  // @todo - replace by an acknowledgement
529
534
  return { _id: id };
@@ -603,7 +608,8 @@ class SecurityController extends NativeController {
603
608
  ids = request.getBodyArray('ids');
604
609
  }
605
610
  else {
606
- ids = request.getString('ids').split(',');
611
+ // @deprecated Should be replaced with request.getArray('ids')
612
+ ids = request.getArrayLegacy('ids');
607
613
  }
608
614
 
609
615
  const users = await this.ask('core:security:user:mGet', ids);
@@ -758,7 +764,7 @@ class SecurityController extends NativeController {
758
764
 
759
765
  await this.ask('core:security:user:delete', id, options);
760
766
 
761
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action}" on user "${id}."`);
767
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action}" on user "${id}."`);
762
768
 
763
769
  return { _id: id };
764
770
  }
@@ -865,7 +871,7 @@ class SecurityController extends NativeController {
865
871
  content,
866
872
  { refresh: request.getRefresh('wait_for'), userId });
867
873
 
868
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on user "${id}."`);
874
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on user "${id}."`);
869
875
 
870
876
  return formatProcessing.serializeUser(user);
871
877
  }
@@ -888,7 +894,7 @@ class SecurityController extends NativeController {
888
894
  userId,
889
895
  });
890
896
 
891
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on profile "${id}."`);
897
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on profile "${id}."`);
892
898
  return formatProcessing.serializeProfile(updated);
893
899
  }
894
900
 
@@ -910,7 +916,7 @@ class SecurityController extends NativeController {
910
916
  userId,
911
917
  });
912
918
 
913
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on role "${id}."`);
919
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on role "${id}."`);
914
920
 
915
921
  return formatProcessing.serializeRole(updated);
916
922
  }
@@ -951,7 +957,7 @@ class SecurityController extends NativeController {
951
957
  }
952
958
  }
953
959
 
954
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}".`);
960
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}".`);
955
961
 
956
962
  return user;
957
963
  }
@@ -1040,7 +1046,7 @@ class SecurityController extends NativeController {
1040
1046
 
1041
1047
  const createMethod = this.getStrategyMethod(strategy, 'create');
1042
1048
 
1043
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action}" on user "${id}."`);
1049
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action}" on user "${id}."`);
1044
1050
  return createMethod(request, body, id, strategy);
1045
1051
  }
1046
1052
 
@@ -1064,7 +1070,7 @@ class SecurityController extends NativeController {
1064
1070
 
1065
1071
  const updateMethod = this.getStrategyMethod(strategy, 'update');
1066
1072
 
1067
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action}" on user "${id}."`);
1073
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action}" on user "${id}."`);
1068
1074
 
1069
1075
  return updateMethod(request, body, id, strategy);
1070
1076
  }
@@ -1117,7 +1123,7 @@ class SecurityController extends NativeController {
1117
1123
 
1118
1124
  await deleteMethod(request, id, strategy);
1119
1125
 
1120
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action}" on user "${id}."`);
1126
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action}" on user "${id}."`);
1121
1127
 
1122
1128
  return { acknowledged: true };
1123
1129
  }
@@ -1228,10 +1234,10 @@ class SecurityController extends NativeController {
1228
1234
  }
1229
1235
 
1230
1236
  if (successes.length > 1000) {
1231
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" deleted the following ${type}s: ${successes.slice(0, 1000).join(', ')}... (${successes.length - 1000} more users deleted)."`);
1237
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} deleted the following ${type}s: ${successes.slice(0, 1000).join(', ')}... (${successes.length - 1000} more users deleted)."`);
1232
1238
  }
1233
1239
  else {
1234
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" deleted the following ${type}s: ${successes.join(', ')}."`);
1240
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} deleted the following ${type}s: ${successes.join(', ')}."`);
1235
1241
  }
1236
1242
 
1237
1243
  return successes;
@@ -1253,7 +1259,7 @@ class SecurityController extends NativeController {
1253
1259
  userId,
1254
1260
  });
1255
1261
 
1256
- global.kuzzle.log.info(`[SECURITY] User "${userId}" applied action "${request.input.action}" on user "${id}."`);
1262
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(userId)} applied action "${request.input.action}" on user "${id}."`);
1257
1263
 
1258
1264
  return formatProcessing.serializeUser(updated);
1259
1265
  }
@@ -1347,7 +1353,7 @@ class SecurityController extends NativeController {
1347
1353
  }
1348
1354
 
1349
1355
  if (creationFailure === null) {
1350
- global.kuzzle.log.info(`[SECURITY] User "${request.getKuid()}" applied action "${request.input.action}" on user "${id}."`);
1356
+ global.kuzzle.log.info(`[SECURITY] ${SecurityController.userOrSdk(request.getKuid())} applied action "${request.input.action}" on user "${id}."`);
1351
1357
  return createdUser;
1352
1358
  }
1353
1359
 
@@ -188,7 +188,8 @@ class ServerController extends NativeController {
188
188
 
189
189
  let services;
190
190
  if (typeof request.input.args.services === 'string') {
191
- services = request.input.args.services.split(',');
191
+ // @deprecated Should be replaced with request.getArray('services')
192
+ services = request.getArrayLegacy('services');
192
193
  }
193
194
  if (! services || services.includes('internalCache')) {
194
195
  try {
@@ -56,7 +56,8 @@ const extractors = ([
56
56
  ids = request.input.args.ids;
57
57
  }
58
58
  else if (typeof request.input.args.ids === 'string') {
59
- ids = request.input.args.ids.split(',');
59
+ // @deprecated Should be replaced with request.getArray('ids')
60
+ ids = request.getArrayLegacy('ids');
60
61
  }
61
62
  else {
62
63
  throw assertionError.get(
@@ -258,6 +258,9 @@ export declare class KuzzleRequest {
258
258
  /**
259
259
  * Gets a parameter from a request arguments and checks that it is an array
260
260
  *
261
+ * If the request argument is a JSON String instead of an array, it will be parsed
262
+ * and returned if it is a valid JSON array, otherwise it will @throws {api.assert.invalid_type}.
263
+ *
261
264
  * @param name parameter name
262
265
  * @param def default value to return if the parameter is not set
263
266
  *
@@ -266,9 +269,29 @@ export declare class KuzzleRequest {
266
269
  * @throws {api.assert.invalid_type} If the fetched parameter is not an array
267
270
  */
268
271
  getArray(name: string, def?: [] | undefined): any[];
272
+ /**
273
+ * @deprecated do not use, Use getArray instead
274
+ *
275
+ * Gets a parameter from a request arguments and checks that it is an array
276
+ *
277
+ * If the request argument is a String instead of an array, it will be JSON parsed
278
+ * and returned if it is a valid JSON array, otherwise it will return the string splitted on `,`.
279
+ *
280
+ *
281
+ * @param name parameter name
282
+ * @param def default value to return if the parameter is not set
283
+ *
284
+ * @throws {api.assert.missing_argument} If parameter not found and no default
285
+ * value provided
286
+ * @throws {api.assert.invalid_type} If the fetched parameter is not an array or a string
287
+ */
288
+ getArrayLegacy(name: string, def?: [] | undefined): any[];
269
289
  /**
270
290
  * Gets a parameter from a request arguments and checks that it is an object
271
291
  *
292
+ * If the request argument is a JSON String instead of an object, it will be parsed
293
+ * and returned if it is a valid JSON object, otherwise it will @throws {api.assert.invalid_type}.
294
+ *
272
295
  * @param name parameter name
273
296
  * @param def default value to return if the parameter is not set
274
297
  *
@@ -374,6 +397,7 @@ export declare class KuzzleRequest {
374
397
  * @param obj container object
375
398
  * @param name parameter name
376
399
  * @param errorName name to use in error messages
400
+ * @param querystring if true, the object is expected to be found in a querystring
377
401
  */
378
402
  private _getBoolean;
379
403
  /**
@@ -419,6 +443,7 @@ export declare class KuzzleRequest {
419
443
  * @param name parameter name
420
444
  * @param errorName name to use in error messages
421
445
  * @param def default value
446
+ * @param querystring if true, the object is expected to be found in a querystring
422
447
  */
423
448
  private _getObject;
424
449
  }
@@ -228,7 +228,9 @@ class KuzzleRequest {
228
228
  }
229
229
  this.status = options.status || 200;
230
230
  if (options.headers) {
231
- this.response.setHeaders(options.headers);
231
+ this.response.configure({
232
+ headers: options.headers
233
+ });
232
234
  }
233
235
  if (options.raw !== undefined) {
234
236
  this.response.raw = options.raw;
@@ -465,7 +467,7 @@ class KuzzleRequest {
465
467
  * @param name parameter name
466
468
  */
467
469
  getBoolean(name) {
468
- return this._getBoolean(this.input.args, name, name);
470
+ return this._getBoolean(this.input.args, name, name, true);
469
471
  }
470
472
  /**
471
473
  * Gets a parameter from a request arguments and checks that it is a number
@@ -509,6 +511,9 @@ class KuzzleRequest {
509
511
  /**
510
512
  * Gets a parameter from a request arguments and checks that it is an array
511
513
  *
514
+ * If the request argument is a JSON String instead of an array, it will be parsed
515
+ * and returned if it is a valid JSON array, otherwise it will @throws {api.assert.invalid_type}.
516
+ *
512
517
  * @param name parameter name
513
518
  * @param def default value to return if the parameter is not set
514
519
  *
@@ -517,11 +522,56 @@ class KuzzleRequest {
517
522
  * @throws {api.assert.invalid_type} If the fetched parameter is not an array
518
523
  */
519
524
  getArray(name, def = undefined) {
520
- return this._getArray(this.input.args, name, name, def);
525
+ return this._getArray(this.input.args, name, name, def, true);
526
+ }
527
+ /**
528
+ * @deprecated do not use, Use getArray instead
529
+ *
530
+ * Gets a parameter from a request arguments and checks that it is an array
531
+ *
532
+ * If the request argument is a String instead of an array, it will be JSON parsed
533
+ * and returned if it is a valid JSON array, otherwise it will return the string splitted on `,`.
534
+ *
535
+ *
536
+ * @param name parameter name
537
+ * @param def default value to return if the parameter is not set
538
+ *
539
+ * @throws {api.assert.missing_argument} If parameter not found and no default
540
+ * value provided
541
+ * @throws {api.assert.invalid_type} If the fetched parameter is not an array or a string
542
+ */
543
+ getArrayLegacy(name, def = undefined) {
544
+ const value = (0, lodash_1.get)(this.input.args, name, def);
545
+ if (value === undefined) {
546
+ throw assertionError.get('missing_argument', name);
547
+ }
548
+ if (Array.isArray(value)) {
549
+ return value;
550
+ }
551
+ if (typeof value !== 'string') {
552
+ throw assertionError.get('invalid_type', name, 'array');
553
+ }
554
+ // If we are using the HTTP protocol and we have a string instead of an Array
555
+ // we try to parse it as JSON
556
+ if (this.context.connection.protocol === 'http') {
557
+ try {
558
+ const parsedValue = JSON.parse(value);
559
+ if (Array.isArray(parsedValue)) {
560
+ return parsedValue;
561
+ }
562
+ }
563
+ catch (e) {
564
+ // Do nothing, let the code continue
565
+ }
566
+ }
567
+ return value.split(',');
521
568
  }
522
569
  /**
523
570
  * Gets a parameter from a request arguments and checks that it is an object
524
571
  *
572
+ * If the request argument is a JSON String instead of an object, it will be parsed
573
+ * and returned if it is a valid JSON object, otherwise it will @throws {api.assert.invalid_type}.
574
+ *
525
575
  * @param name parameter name
526
576
  * @param def default value to return if the parameter is not set
527
577
  *
@@ -530,7 +580,7 @@ class KuzzleRequest {
530
580
  * @throws {api.assert.invalid_type} If the fetched parameter is not an object
531
581
  */
532
582
  getObject(name, def = undefined) {
533
- return this._getObject(this.input.args, name, name, def);
583
+ return this._getObject(this.input.args, name, name, def, true);
534
584
  }
535
585
  /**
536
586
  * Gets a parameter from a request arguments and check with moment.js if the date is an ISO8601 format date
@@ -677,13 +727,7 @@ class KuzzleRequest {
677
727
  || this.context.connection.misc.verb !== 'GET') {
678
728
  return this.getBody({});
679
729
  }
680
- const searchBody = this.getString('searchBody', '{}');
681
- try {
682
- return JSON.parse(searchBody);
683
- }
684
- catch (err) {
685
- throw assertionError.get('invalid_argument', err.message);
686
- }
730
+ return this.getObject('searchBody', {});
687
731
  }
688
732
  /**
689
733
  * Returns the search params.
@@ -736,16 +780,17 @@ class KuzzleRequest {
736
780
  * @param obj container object
737
781
  * @param name parameter name
738
782
  * @param errorName name to use in error messages
783
+ * @param querystring if true, the object is expected to be found in a querystring
739
784
  */
740
- _getBoolean(obj, name, errorName) {
785
+ _getBoolean(obj, name, errorName, querystring = false) {
741
786
  let value = (0, lodash_1.get)(obj, name);
742
787
  // In HTTP, booleans are flags: if it's in the querystring, it's set,
743
788
  // whatever its value.
744
789
  // If a user needs to unset the option, they need to remove it from the
745
790
  // querystring.
746
- if (this.context.connection.protocol === 'http') {
791
+ if (this.context.connection.protocol === 'http' && querystring) {
747
792
  value = value !== undefined;
748
- obj[name] = value;
793
+ (0, lodash_1.set)(obj, name, value);
749
794
  }
750
795
  else if (value === undefined || value === null) {
751
796
  value = false;
@@ -822,12 +867,30 @@ class KuzzleRequest {
822
867
  * @param errorName name to use in error messages
823
868
  * @param def default value
824
869
  */
825
- _getArray(obj, name, errorName, def = undefined) {
870
+ _getArray(obj, name, errorName, def = undefined, querystring = false) {
826
871
  const value = (0, lodash_1.get)(obj, name, def);
827
872
  if (value === undefined) {
828
873
  throw assertionError.get('missing_argument', errorName);
829
874
  }
830
875
  if (!Array.isArray(value)) {
876
+ // If we are using the HTTP protocol and we have a string instead of an Array
877
+ // we try to parse it as JSON
878
+ if (this.context.connection.protocol === 'http'
879
+ && querystring
880
+ && typeof value === 'string') {
881
+ try {
882
+ const parsedValue = JSON.parse(value);
883
+ if (Array.isArray(parsedValue)) {
884
+ // Replace the value with the parsed value
885
+ // This way subsequent calls to this function will return the parsed value directly
886
+ (0, lodash_1.set)(obj, name, parsedValue);
887
+ return parsedValue;
888
+ }
889
+ }
890
+ catch (e) {
891
+ // Do nothing, let the error be thrown below
892
+ }
893
+ }
831
894
  throw assertionError.get('invalid_type', errorName, 'array');
832
895
  }
833
896
  return value;
@@ -839,13 +902,32 @@ class KuzzleRequest {
839
902
  * @param name parameter name
840
903
  * @param errorName name to use in error messages
841
904
  * @param def default value
905
+ * @param querystring if true, the object is expected to be found in a querystring
842
906
  */
843
- _getObject(obj, name, errorName, def = undefined) {
907
+ _getObject(obj, name, errorName, def = undefined, querystring = false) {
844
908
  const value = (0, lodash_1.get)(obj, name, def);
845
909
  if (value === undefined) {
846
910
  throw assertionError.get('missing_argument', errorName);
847
911
  }
848
912
  if (!(0, safeObject_1.isPlainObject)(value)) {
913
+ // If we are using the HTTP protocol and we have a string instead of an Array
914
+ // we try to parse it as JSON
915
+ if (this.context.connection.protocol === 'http'
916
+ && querystring
917
+ && typeof value === 'string') {
918
+ try {
919
+ const parsedValue = JSON.parse(value);
920
+ if ((0, safeObject_1.isPlainObject)(parsedValue)) {
921
+ // Replace the value with the parsed value
922
+ // This way subsequent calls to this function will return the parsed value directly
923
+ (0, lodash_1.set)(obj, name, parsedValue);
924
+ return parsedValue;
925
+ }
926
+ }
927
+ catch (e) {
928
+ // Do nothing, let the error be thrown below
929
+ }
930
+ }
849
931
  throw assertionError.get('invalid_type', errorName, 'object');
850
932
  }
851
933
  return value;
@@ -49,6 +49,11 @@ const assert = __importStar(require("../../util/assertType"));
49
49
  // \u200b is a zero width space, used to masquerade console.log output
50
50
  const _request = 'request\u200b';
51
51
  const _headers = 'headers\u200b';
52
+ const _userHeaders = 'userHeaders\u200b'; // List of headers to be sent in the response
53
+ // List of headers that should not be present in the body of the response
54
+ const restrictedHeaders = [
55
+ 'set-cookie',
56
+ ];
52
57
  class Headers {
53
58
  constructor() {
54
59
  this.namesMap = new Map();
@@ -150,6 +155,7 @@ class RequestResponse {
150
155
  this.raw = false;
151
156
  this[_request] = request;
152
157
  this[_headers] = new Headers();
158
+ this[_userHeaders] = new Set();
153
159
  Object.seal(this);
154
160
  }
155
161
  /**
@@ -254,6 +260,9 @@ class RequestResponse {
254
260
  configure(options = {}) {
255
261
  if (options.headers) {
256
262
  this.setHeaders(options.headers);
263
+ for (const key of Object.keys(options.headers)) {
264
+ this[_userHeaders].add(key.toLowerCase());
265
+ }
257
266
  }
258
267
  if (options.status) {
259
268
  this.status = options.status;
@@ -316,6 +325,21 @@ class RequestResponse {
316
325
  status: this.status,
317
326
  };
318
327
  }
328
+ const filteredHeaders = {};
329
+ for (const name of this[_userHeaders]) {
330
+ filteredHeaders[name] = this.getHeader(name);
331
+ }
332
+ /**
333
+ * Remove headers that are not allowed to be sent to the client in the response's body
334
+ * For example "set-cookie" headers should only be visible by the browser,
335
+ * otherwise they may leak information about the server's cookies, since the browser will
336
+ * not be able to restrict them to the domain of the request.
337
+ */
338
+ for (const header of restrictedHeaders) {
339
+ if (filteredHeaders[header] !== undefined) {
340
+ filteredHeaders[header] = undefined;
341
+ }
342
+ }
319
343
  return {
320
344
  content: {
321
345
  action: this.action,
@@ -323,6 +347,7 @@ class RequestResponse {
323
347
  controller: this.controller,
324
348
  deprecations: this.deprecations,
325
349
  error: this.error,
350
+ headers: filteredHeaders,
326
351
  index: this.index,
327
352
  node: this.node,
328
353
  requestId: this.requestId,
@@ -86,5 +86,11 @@ export declare class BackendController extends ApplicationManager {
86
86
  * @param controller Controller class
87
87
  */
88
88
  use(controller: Controller): void;
89
- private _add;
89
+ /**
90
+ * Adds the controller definition to the list of application controllers.
91
+ *
92
+ * This method also check if the definition is valid to throw with a stacktrace
93
+ * beginning on the user code adding the controller.
94
+ */
95
+ private add;
90
96
  }
@@ -42,11 +42,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
42
42
  __setModuleDefault(result, mod);
43
43
  return result;
44
44
  };
45
+ var __importDefault = (this && this.__importDefault) || function (mod) {
46
+ return (mod && mod.__esModule) ? mod : { "default": mod };
47
+ };
45
48
  Object.defineProperty(exports, "__esModule", { value: true });
46
49
  exports.BackendController = void 0;
47
50
  const inflector_1 = require("../../util/inflector");
48
51
  const kerror = __importStar(require("../../kerror"));
49
52
  const index_1 = require("./index");
53
+ const plugin_1 = __importDefault(require("../plugin/plugin"));
50
54
  const assertionError = kerror.wrap('plugin', 'assert');
51
55
  const runtimeError = kerror.wrap('plugin', 'runtime');
52
56
  class BackendController extends index_1.ApplicationManager {
@@ -74,7 +78,8 @@ class BackendController extends index_1.ApplicationManager {
74
78
  if (this._application.started) {
75
79
  throw runtimeError.get('already_started', 'controller');
76
80
  }
77
- this._add(name, definition);
81
+ plugin_1.default.checkControllerDefinition(name, definition);
82
+ this.add(name, definition);
78
83
  }
79
84
  /**
80
85
  * Uses a new controller class.
@@ -147,6 +152,7 @@ class BackendController extends index_1.ApplicationManager {
147
152
  controller.name = inflector_1.Inflector.kebabCase(controller.constructor.name)
148
153
  .replace('-controller', '');
149
154
  }
155
+ plugin_1.default.checkControllerDefinition(controller.name, controller.definition);
150
156
  for (const [action, definition] of Object.entries(controller.definition.actions)) {
151
157
  if (typeof definition.handler !== 'function') {
152
158
  throw assertionError.get('invalid_controller_definition', controller.name, `Handler for action "${action}" is not a function.`);
@@ -158,9 +164,15 @@ class BackendController extends index_1.ApplicationManager {
158
164
  definition.handler = definition.handler.bind(controller);
159
165
  }
160
166
  }
161
- this._add(controller.name, controller.definition);
167
+ this.add(controller.name, controller.definition);
162
168
  }
163
- _add(name, definition) {
169
+ /**
170
+ * Adds the controller definition to the list of application controllers.
171
+ *
172
+ * This method also check if the definition is valid to throw with a stacktrace
173
+ * beginning on the user code adding the controller.
174
+ */
175
+ add(name, definition) {
164
176
  if (this._application._controllers[name]) {
165
177
  throw assertionError.get('invalid_controller_definition', name, 'A controller with this name already exists');
166
178
  }
@@ -256,10 +256,15 @@ class HttpWsProtocol extends Protocol {
256
256
  }
257
257
 
258
258
  wsOnUpgradeHandler (res, req, context) {
259
+ const headers = {};
260
+ // Extract headers from uWS request
261
+ req.forEach((header, value) => {
262
+ headers[header] = value;
263
+ });
264
+
259
265
  res.upgrade(
260
266
  {
261
- cookie: req.getHeader('cookie'),
262
- origin: req.getHeader('origin'),
267
+ headers,
263
268
  },
264
269
  req.getHeader('sec-websocket-key'),
265
270
  req.getHeader('sec-websocket-protocol'),
@@ -273,10 +278,7 @@ class HttpWsProtocol extends Protocol {
273
278
  const connection = new ClientConnection(
274
279
  this.name,
275
280
  [ip],
276
- {
277
- cookie: socket.cookie,
278
- origin: socket.origin
279
- }
281
+ socket.headers
280
282
  );
281
283
 
282
284
  this.entryPoint.newConnection(connection);
@@ -300,6 +300,13 @@ class Plugin {
300
300
  'Controller definition must be an object');
301
301
  }
302
302
 
303
+ if (! isPlainObject(definition.actions)) {
304
+ throw assertionError.get(
305
+ 'invalid_controller_definition',
306
+ name,
307
+ 'Controller definition "actions" property must be an object');
308
+ }
309
+
303
310
  for (const [action, actionDefinition] of Object.entries(definition.actions)) {
304
311
  const actionProperties = Object.keys(actionDefinition);
305
312
 
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kuzzle",
3
- "version": "2.18.0",
3
+ "version": "2.18.1",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kuzzle",
3
3
  "author": "The Kuzzle Team <support@kuzzle.io>",
4
- "version": "2.18.0",
4
+ "version": "2.18.1",
5
5
  "description": "Kuzzle is an open-source solution that handles all the data management through a secured API, with a large choice of protocols.",
6
6
  "bin": {
7
7
  "kuzzle": "bin/start-kuzzle-server"