serverless-offline 8.4.0 → 8.5.0

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.
package/README.md CHANGED
@@ -49,6 +49,7 @@ This plugin is updated by its users, I just do maintenance and ensure that PRs a
49
49
  - [Custom authorizers](#custom-authorizers)
50
50
  - [Remote authorizers](#remote-authorizers)
51
51
  - [JWT authorizers](#jwt-authorizers)
52
+ - [Serverless plugin authorizers](#serverless-plugin-authorizers)
52
53
  - [Custom headers](#custom-headers)
53
54
  - [Environment variables](#environment-variables)
54
55
  - [AWS API Gateway Features](#aws-api-gateway-features)
@@ -360,14 +361,36 @@ defined in the `serverless.yml` can be used to validate the token and scopes in
360
361
  the signature of the JWT is not validated with the defined issuer. Since this is a security risk, this feature is
361
362
  only enabled with the `--ignoreJWTSignature` flag. Make sure to only set this flag for local development work.
362
363
 
364
+ ## Serverless plugin authorizers
365
+
366
+ If your authentication needs are custom and not satisfied by the existing capabilities of the Serverless offline project, you can inject your own authentication strategy. To inject a custom strategy for Lambda invocation, you define a custom variable under `serverless-offline` called `authenticationProvider` in the serverless.yml file. The value of the custom variable will be used to `require(your authenticationProvider value)` where the location is expected to return a function with the following signature.
367
+
368
+ ```js
369
+ module.exports = function (endpoint, functionKey, method, path) {
370
+ return {
371
+ name: 'your strategy name',
372
+ scheme: 'your scheme name',
373
+
374
+ getAuthenticateFunction: () => ({
375
+ async authenticate(request, h) {
376
+ // your implementation
377
+ },
378
+ }),
379
+ }
380
+ }
381
+ ```
382
+
383
+ A working example of injecting a custom authorization provider can be found in the projects integration tests under the folder `custom-authentication`.
384
+
363
385
  ## Custom headers
364
386
 
365
387
  You are able to use some custom headers in your request to gain more control over the requestContext object.
366
388
 
367
- | Header | Event key |
368
- | ------------------------------- | ----------------------------------------------------------- |
369
- | cognito-identity-id | event.requestContext.identity.cognitoIdentityId |
370
- | cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider |
389
+ | Header | Event key | Example |
390
+ | ------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
391
+ | cognito-identity-id | event.requestContext.identity.cognitoIdentityId | |
392
+ | cognito-authentication-provider | event.requestContext.identity.cognitoAuthenticationProvider | |
393
+ | sls-offline-authorizer-override | event.requestContext.authorizer | { "iam": {"cognitoUser": { "amr": ["unauthenticated"], "identityId": "abc123" }}} |
371
394
 
372
395
  By doing this you are now able to change those values using a custom header. This can help you with easier authentication or retrieving the userId from a `cognitoAuthenticationProvider` value.
373
396
 
@@ -744,6 +767,10 @@ We try to follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/jav
744
767
  | :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: |
745
768
  | [lteacher](https://github.com/lteacher) | [martinmicunda](https://github.com/martinmicunda) | [nori3tsu](https://github.com/nori3tsu) | [ppasmanik](https://github.com/ppasmanik) | [ryanzyy](https://github.com/ryanzyy) |
746
769
 
747
- | [<img alt="m0ppers" src="https://avatars3.githubusercontent.com/u/819421?v=4&s=117" width="117">](https://github.com/m0ppers) | [<img alt="footballencarta" src="https://avatars0.githubusercontent.com/u/1312258?v=4&s=117" width="117">](https://github.com/footballencarta) | [<img alt="bryanvaz" src="https://avatars0.githubusercontent.com/u/9157498?v=4&s=117" width="117">](https://github.com/bryanvaz) | [<img alt="njyjn" src="https://avatars.githubusercontent.com/u/10694375?v=4&s=117" width="117">](https://github.com/njyjn) |
770
+ | [<img alt="m0ppers" src="https://avatars3.githubusercontent.com/u/819421?v=4&s=117" width="117">](https://github.com/m0ppers) | [<img alt="footballencarta" src="https://avatars0.githubusercontent.com/u/1312258?v=4&s=117" width="117">](https://github.com/footballencarta) | [<img alt="bryanvaz" src="https://avatars0.githubusercontent.com/u/9157498?v=4&s=117" width="117">](https://github.com/bryanvaz) | [<img alt="njyjn" src="https://avatars.githubusercontent.com/u/10694375?v=4&s=117" width="117">](https://github.com/njyjn) | |
748
771
  | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | ------------------------------------- |
749
772
  | [m0ppers](https://github.com/m0ppers) | [footballencarta](https://github.com/footballencarta) | [bryanvaz](https://github.com/bryanvaz) | [njyjn](https://github.com/njyjn) | [kdybicz](https://github.com/kdybicz) |
773
+
774
+ | [<img alt="ericctsf" src="https://avatars.githubusercontent.com/u/42775388?s=400&v=4" width="117">](https://github.com/ericctsf) | | | | |
775
+ | :------------------------------------------------------------------------------------------------------------------------------: | :-: | :-: | :-: | :-: |
776
+ | [ericctsf](https://github.com/erictsf) | | | | |
@@ -9,12 +9,14 @@ var _buffer = require("buffer");
9
9
 
10
10
  var _fs = require("fs");
11
11
 
12
- var _path = require("path");
12
+ var pathUtils = _interopRequireWildcard(require("path"));
13
13
 
14
14
  var _h2o = _interopRequireDefault(require("@hapi/h2o2"));
15
15
 
16
16
  var _hapi = require("@hapi/hapi");
17
17
 
18
+ var _module = require("module");
19
+
18
20
  var _authFunctionNameExtractor = _interopRequireDefault(require("./authFunctionNameExtractor.js"));
19
21
 
20
22
  var _authJWTSettingsExtractor = _interopRequireDefault(require("./authJWTSettingsExtractor.js"));
@@ -39,12 +41,12 @@ var _index2 = require("../../utils/index.js");
39
41
 
40
42
  var _LambdaProxyIntegrationEventV = _interopRequireDefault(require("./lambda-events/LambdaProxyIntegrationEventV2.js"));
41
43
 
44
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
45
+
42
46
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
43
47
 
44
48
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
45
49
 
46
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
47
-
48
50
  function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
49
51
 
50
52
  var id = 0;
@@ -134,8 +136,8 @@ class HttpServer {
134
136
 
135
137
  if (typeof httpsProtocol === 'string' && httpsProtocol.length > 0) {
136
138
  serverOptions.tls = {
137
- cert: (0, _fs.readFileSync)((0, _path.resolve)(httpsProtocol, 'cert.pem'), 'ascii'),
138
- key: (0, _fs.readFileSync)((0, _path.resolve)(httpsProtocol, 'key.pem'), 'ascii')
139
+ cert: (0, _fs.readFileSync)((0, pathUtils.resolve)(httpsProtocol, 'cert.pem'), 'ascii'),
140
+ key: (0, _fs.readFileSync)((0, pathUtils.resolve)(httpsProtocol, 'key.pem'), 'ascii')
139
141
  };
140
142
  } // Hapijs server creation
141
143
 
@@ -428,6 +430,36 @@ class HttpServer {
428
430
  return authStrategyName;
429
431
  }
430
432
 
433
+ _setAuthorizationStrategy(endpoint, functionKey, method, path) {
434
+ var _customizations$offli;
435
+
436
+ /*
437
+ * The authentication strategy can be provided outside of this project
438
+ * by injecting the provider through a custom variable in the serverless.yml.
439
+ *
440
+ * see the example in the tests for more details
441
+ * /tests/integration/custom-authentication
442
+ */
443
+ const customizations = _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.custom;
444
+
445
+ if (customizations && (_customizations$offli = customizations.offline) !== null && _customizations$offli !== void 0 && _customizations$offli.customAuthenticationProvider) {
446
+ const root = pathUtils.resolve(_classPrivateFieldLooseBase(this, _serverless)[_serverless].serviceDir, 'require-resolver');
447
+ const customRequire = (0, _module.createRequire)(root);
448
+ const provider = customRequire(customizations.offline.customAuthenticationProvider);
449
+ const strategy = provider(endpoint, functionKey, method, path);
450
+
451
+ _classPrivateFieldLooseBase(this, _server)[_server].auth.scheme(strategy.scheme, strategy.getAuthenticateFunction);
452
+
453
+ _classPrivateFieldLooseBase(this, _server)[_server].auth.strategy(strategy.name, strategy.scheme);
454
+
455
+ return strategy.name;
456
+ } // If the endpoint has an authorization function, create an authStrategy for the route
457
+
458
+
459
+ const authStrategyName = _classPrivateFieldLooseBase(this, _options)[_options].noAuth ? null : this._configureJWTAuthorization(endpoint, functionKey, method, path) || this._configureAuthorization(endpoint, functionKey, method, path);
460
+ return authStrategyName;
461
+ }
462
+
431
463
  createRoutes(functionKey, httpEvent, handler) {
432
464
  const [handlerPath] = (0, _index2.splitHandlerPathAndName)(handler);
433
465
  let method;
@@ -455,7 +487,7 @@ class HttpServer {
455
487
  hapiPath = (0, _index2.generateHapiPath)(path, _classPrivateFieldLooseBase(this, _options)[_options], _classPrivateFieldLooseBase(this, _serverless)[_serverless]);
456
488
  }
457
489
 
458
- const endpoint = new _Endpoint.default((0, _path.join)(_classPrivateFieldLooseBase(this, _serverless)[_serverless].config.servicePath, handlerPath), httpEvent, this.v3Utils);
490
+ const endpoint = new _Endpoint.default((0, pathUtils.join)(_classPrivateFieldLooseBase(this, _serverless)[_serverless].config.servicePath, handlerPath), httpEvent, this.v3Utils);
459
491
  const stage = endpoint.isHttpApi ? '$default' : _classPrivateFieldLooseBase(this, _options)[_options].stage || _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.provider.stage;
460
492
  const protectedRoutes = [];
461
493
 
@@ -477,10 +509,10 @@ class HttpServer {
477
509
  server,
478
510
  stage: endpoint.isHttpApi || _classPrivateFieldLooseBase(this, _options)[_options].noPrependStageInUrl ? null : stage,
479
511
  invokePath: `/2015-03-31/functions/${functionKey}/invocations`
480
- }); // If the endpoint has an authorization function, create an authStrategy for the route
512
+ });
481
513
 
514
+ const authStrategyName = this._setAuthorizationStrategy(endpoint, functionKey, method, path);
482
515
 
483
- const authStrategyName = _classPrivateFieldLooseBase(this, _options)[_options].noAuth ? null : this._configureJWTAuthorization(endpoint, functionKey, method, path) || this._configureAuthorization(endpoint, functionKey, method, path);
484
516
  let cors = null;
485
517
 
486
518
  if (endpoint.cors) {
@@ -538,6 +570,12 @@ class HttpServer {
538
570
  };
539
571
  }
540
572
 
573
+ const additionalRequestContext = {};
574
+
575
+ if (httpEvent.operationId) {
576
+ additionalRequestContext.operationName = httpEvent.operationId;
577
+ }
578
+
541
579
  hapiOptions.tags = ['api'];
542
580
 
543
581
  const hapiHandler = async (request, h) => {
@@ -682,7 +720,7 @@ class HttpServer {
682
720
  }
683
721
  } else if (integration === 'AWS_PROXY') {
684
722
  const stageVariables = _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.custom ? _classPrivateFieldLooseBase(this, _serverless)[_serverless].service.custom.stageVariables : null;
685
- const lambdaProxyIntegrationEvent = endpoint.isHttpApi && endpoint.payload === '2.0' ? new _LambdaProxyIntegrationEventV.default(request, stage, endpoint.routeKey, stageVariables, this.v3Utils) : new _index.LambdaProxyIntegrationEvent(request, stage, requestPath, stageVariables, endpoint.isHttpApi ? endpoint.routeKey : null, this.v3Utils);
723
+ const lambdaProxyIntegrationEvent = endpoint.isHttpApi && endpoint.payload === '2.0' ? new _LambdaProxyIntegrationEventV.default(request, stage, endpoint.routeKey, stageVariables, additionalRequestContext, this.v3Utils) : new _index.LambdaProxyIntegrationEvent(request, stage, requestPath, stageVariables, endpoint.isHttpApi ? endpoint.routeKey : null, additionalRequestContext, this.v3Utils);
686
724
  event = lambdaProxyIntegrationEvent.create();
687
725
  }
688
726
 
@@ -39,8 +39,10 @@ var _stage = /*#__PURE__*/_classPrivateFieldLooseKey("stage");
39
39
 
40
40
  var _stageVariables = /*#__PURE__*/_classPrivateFieldLooseKey("stageVariables");
41
41
 
42
+ var _additionalRequestContext = /*#__PURE__*/_classPrivateFieldLooseKey("additionalRequestContext");
43
+
42
44
  class LambdaProxyIntegrationEvent {
43
- constructor(request, stage, path, stageVariables, routeKey = null, v3Utils) {
45
+ constructor(request, stage, path, stageVariables, routeKey = null, additionalRequestContext = null, v3Utils) {
44
46
  Object.defineProperty(this, _path, {
45
47
  writable: true,
46
48
  value: null
@@ -61,11 +63,16 @@ class LambdaProxyIntegrationEvent {
61
63
  writable: true,
62
64
  value: null
63
65
  });
66
+ Object.defineProperty(this, _additionalRequestContext, {
67
+ writable: true,
68
+ value: null
69
+ });
64
70
  _classPrivateFieldLooseBase(this, _path)[_path] = path;
65
71
  _classPrivateFieldLooseBase(this, _routeKey)[_routeKey] = routeKey;
66
72
  _classPrivateFieldLooseBase(this, _request)[_request] = request;
67
73
  _classPrivateFieldLooseBase(this, _stage)[_stage] = stage;
68
74
  _classPrivateFieldLooseBase(this, _stageVariables)[_stageVariables] = stageVariables;
75
+ _classPrivateFieldLooseBase(this, _additionalRequestContext)[_additionalRequestContext] = additionalRequestContext || {};
69
76
 
70
77
  if (v3Utils) {
71
78
  this.log = v3Utils.log;
@@ -103,6 +110,18 @@ class LambdaProxyIntegrationEvent {
103
110
 
104
111
  const headers = (0, _index.parseHeaders)(rawHeaders || []) || {};
105
112
 
113
+ if (headers['sls-offline-authorizer-override']) {
114
+ try {
115
+ authAuthorizer = parse(headers['sls-offline-authorizer-override']);
116
+ } catch (error) {
117
+ if (this.log) {
118
+ this.log.error('Could not parse header sls-offline-authorizer-override, make sure it is correct JSON');
119
+ } else {
120
+ console.error('Serverless-offline: Could not parse header sls-offline-authorizer-override make sure it is correct JSON.');
121
+ }
122
+ }
123
+ }
124
+
106
125
  if (body) {
107
126
  if (typeof body !== 'string') {
108
127
  // this.#request.payload is NOT the same as the rawPayload
@@ -205,6 +224,7 @@ class LambdaProxyIntegrationEvent {
205
224
  userAgent: _headers['user-agent'] || '',
206
225
  userArn: 'offlineContext_userArn'
207
226
  },
227
+ operationName: _classPrivateFieldLooseBase(this, _additionalRequestContext)[_additionalRequestContext].operationName,
208
228
  path: _classPrivateFieldLooseBase(this, _path)[_path],
209
229
  protocol: 'HTTP/1.1',
210
230
  requestId: (0, _index.createUniqueId)(),
@@ -36,8 +36,10 @@ var _stage = /*#__PURE__*/_classPrivateFieldLooseKey("stage");
36
36
 
37
37
  var _stageVariables = /*#__PURE__*/_classPrivateFieldLooseKey("stageVariables");
38
38
 
39
+ var _additionalRequestContext = /*#__PURE__*/_classPrivateFieldLooseKey("additionalRequestContext");
40
+
39
41
  class LambdaProxyIntegrationEventV2 {
40
- constructor(request, stage, routeKey, stageVariables, v3Utils) {
42
+ constructor(request, stage, routeKey, stageVariables, additionalRequestContext, v3Utils) {
41
43
  Object.defineProperty(this, _routeKey, {
42
44
  writable: true,
43
45
  value: null
@@ -54,10 +56,15 @@ class LambdaProxyIntegrationEventV2 {
54
56
  writable: true,
55
57
  value: null
56
58
  });
59
+ Object.defineProperty(this, _additionalRequestContext, {
60
+ writable: true,
61
+ value: null
62
+ });
57
63
  _classPrivateFieldLooseBase(this, _routeKey)[_routeKey] = routeKey;
58
64
  _classPrivateFieldLooseBase(this, _request)[_request] = request;
59
65
  _classPrivateFieldLooseBase(this, _stage)[_stage] = stage;
60
66
  _classPrivateFieldLooseBase(this, _stageVariables)[_stageVariables] = stageVariables;
67
+ _classPrivateFieldLooseBase(this, _additionalRequestContext)[_additionalRequestContext] = additionalRequestContext || {};
61
68
 
62
69
  if (v3Utils) {
63
70
  this.log = v3Utils.log;
@@ -92,6 +99,18 @@ class LambdaProxyIntegrationEventV2 {
92
99
 
93
100
  const headers = (0, _index.parseHeaders)(rawHeaders || []) || {};
94
101
 
102
+ if (headers['sls-offline-authorizer-override']) {
103
+ try {
104
+ authAuthorizer = parse(headers['sls-offline-authorizer-override']);
105
+ } catch (error) {
106
+ if (this.log) {
107
+ this.log.error('Could not parse header sls-offline-authorizer-override, make sure it is correct JSON');
108
+ } else {
109
+ console.error('Serverless-offline: Could not parse header sls-offline-authorizer-override make sure it is correct JSON.');
110
+ }
111
+ }
112
+ }
113
+
95
114
  if (body) {
96
115
  if (typeof body !== 'string') {
97
116
  // this.#request.payload is NOT the same as the rawPayload
@@ -177,6 +196,7 @@ class LambdaProxyIntegrationEventV2 {
177
196
  sourceIp: remoteAddress,
178
197
  userAgent: _headers['user-agent'] || ''
179
198
  },
199
+ operationName: _classPrivateFieldLooseBase(this, _additionalRequestContext)[_additionalRequestContext].operationName,
180
200
  requestId: 'offlineContext_resourceId',
181
201
  routeKey: _classPrivateFieldLooseBase(this, _routeKey)[_routeKey],
182
202
  stage: _classPrivateFieldLooseBase(this, _stage)[_stage],
@@ -160,14 +160,53 @@ class WebSocketClients {
160
160
  clearTimeout(timeoutId);
161
161
  }
162
162
 
163
- async _processEvent(websocketClient, connectionId, route, event) {
164
- let functionKey = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get(route);
163
+ async verifyClient(connectionId, request) {
164
+ const route = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get('$connect');
165
+
166
+ if (!route) {
167
+ return {
168
+ verified: false,
169
+ statusCode: 502
170
+ };
171
+ }
172
+
173
+ const connectEvent = new _index.WebSocketConnectEvent(connectionId, request, _classPrivateFieldLooseBase(this, _options)[_options]).create();
174
+
175
+ const lambdaFunction = _classPrivateFieldLooseBase(this, _lambda)[_lambda].get(route.functionKey);
176
+
177
+ lambdaFunction.setEvent(connectEvent);
178
+
179
+ try {
180
+ const {
181
+ statusCode
182
+ } = await lambdaFunction.runHandler();
183
+ const verified = statusCode >= 200 && statusCode < 300;
184
+ return {
185
+ verified,
186
+ statusCode
187
+ };
188
+ } catch (err) {
189
+ if (this.log) {
190
+ this.log.debug(`Error in route handler '${route.functionKey}'`, err);
191
+ } else {
192
+ (0, _debugLog.default)(`Error in route handler '${route.functionKey}'`, err);
193
+ }
165
194
 
166
- if (!functionKey && route !== '$connect' && route !== '$disconnect') {
167
- functionKey = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get('$default');
195
+ return {
196
+ verified: false,
197
+ statusCode: 502
198
+ };
199
+ }
200
+ }
201
+
202
+ async _processEvent(websocketClient, connectionId, routeKey, event) {
203
+ let route = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get(routeKey);
204
+
205
+ if (!route && routeKey !== '$disconnect') {
206
+ route = _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].get('$default');
168
207
  }
169
208
 
170
- if (!functionKey) {
209
+ if (!route) {
171
210
  return;
172
211
  }
173
212
 
@@ -178,27 +217,30 @@ class WebSocketClients {
178
217
  message: 'Internal server error',
179
218
  requestId: '1234567890'
180
219
  }));
181
- } // mimic AWS behaviour (close connection) when the $connect route handler throws
182
-
183
-
184
- if (route === '$connect') {
185
- websocketClient.close();
186
220
  }
187
221
 
188
222
  if (this.log) {
189
- this.log.debug(`Error in route handler '${functionKey}'`, err);
223
+ this.log.debug(`Error in route handler '${route.functionKey}'`, err);
190
224
  } else {
191
- (0, _debugLog.default)(`Error in route handler '${functionKey}'`, err);
225
+ (0, _debugLog.default)(`Error in route handler '${route.functionKey}'`, err);
192
226
  }
193
227
  };
194
228
 
195
- const lambdaFunction = _classPrivateFieldLooseBase(this, _lambda)[_lambda].get(functionKey);
229
+ const lambdaFunction = _classPrivateFieldLooseBase(this, _lambda)[_lambda].get(route.functionKey);
196
230
 
197
- lambdaFunction.setEvent(event); // let result
231
+ lambdaFunction.setEvent(event);
198
232
 
199
233
  try {
200
- /* result = */
201
- await lambdaFunction.runHandler(); // TODO what to do with "result"?
234
+ const {
235
+ body
236
+ } = await lambdaFunction.runHandler();
237
+
238
+ if (body && routeKey !== '$disconnect' && route.definition.routeResponseSelectionExpression === '$default') {
239
+ // https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-route-response-selection-expressions
240
+ // TODO: Once API gateway supports RouteResponses, this will need to change to support that functionality
241
+ // For now, send body back to the client
242
+ this.send(connectionId, body);
243
+ }
202
244
  } catch (err) {
203
245
  if (this.log) {
204
246
  this.log.error(err);
@@ -230,13 +272,9 @@ class WebSocketClients {
230
272
  return route || _index2.DEFAULT_WEBSOCKETS_ROUTE;
231
273
  }
232
274
 
233
- addClient(webSocketClient, request, connectionId) {
275
+ addClient(webSocketClient, connectionId) {
234
276
  this._addWebSocketClient(webSocketClient, connectionId);
235
277
 
236
- const connectEvent = new _index.WebSocketConnectEvent(connectionId, request, _classPrivateFieldLooseBase(this, _options)[_options]).create();
237
-
238
- this._processEvent(webSocketClient, connectionId, '$connect', connectEvent);
239
-
240
278
  webSocketClient.on('close', () => {
241
279
  if (this.log) {
242
280
  this.log.debug(`disconnect:${connectionId}`);
@@ -277,14 +315,17 @@ class WebSocketClients {
277
315
  });
278
316
  }
279
317
 
280
- addRoute(functionKey, route) {
318
+ addRoute(functionKey, definition) {
281
319
  // set the route name
282
- _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].set(route, functionKey);
320
+ _classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].set(definition.route, {
321
+ functionKey,
322
+ definition
323
+ });
283
324
 
284
325
  if (this.log) {
285
- this.log.notice(`route '${route}'`);
326
+ this.log.notice(`route '${definition}'`);
286
327
  } else {
287
- (0, _serverlessLog.default)(`route '${route}'`);
328
+ (0, _serverlessLog.default)(`route '${definition}'`);
288
329
  }
289
330
  }
290
331
 
@@ -25,6 +25,8 @@ var _options = /*#__PURE__*/_classPrivateFieldLooseKey("options");
25
25
 
26
26
  var _webSocketClients = /*#__PURE__*/_classPrivateFieldLooseKey("webSocketClients");
27
27
 
28
+ var _connectionIds = /*#__PURE__*/_classPrivateFieldLooseKey("connectionIds");
29
+
28
30
  class WebSocketServer {
29
31
  constructor(options, webSocketClients, sharedServer, v3Utils) {
30
32
  Object.defineProperty(this, _options, {
@@ -35,6 +37,10 @@ class WebSocketServer {
35
37
  writable: true,
36
38
  value: null
37
39
  });
40
+ Object.defineProperty(this, _connectionIds, {
41
+ writable: true,
42
+ value: new Map()
43
+ });
38
44
  _classPrivateFieldLooseBase(this, _options)[_options] = options;
39
45
  _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients] = webSocketClients;
40
46
 
@@ -46,7 +52,42 @@ class WebSocketServer {
46
52
  }
47
53
 
48
54
  const server = new _ws.Server({
49
- server: sharedServer
55
+ server: sharedServer,
56
+ verifyClient: ({
57
+ req
58
+ }, cb) => {
59
+ const connectionId = (0, _index.createUniqueId)();
60
+ const {
61
+ headers
62
+ } = req;
63
+ const key = headers['sec-websocket-key'];
64
+
65
+ if (this.log) {
66
+ this.log.debug(`verifyClient:${key} ${connectionId}`);
67
+ } else {
68
+ (0, _debugLog.default)(`verifyClient:${key} ${connectionId}`);
69
+ } // Use the websocket key to coorelate connection IDs
70
+
71
+
72
+ _classPrivateFieldLooseBase(this, _connectionIds)[_connectionIds][key] = connectionId;
73
+
74
+ _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients].verifyClient(connectionId, req).then(({
75
+ verified,
76
+ statusCode
77
+ }) => {
78
+ try {
79
+ if (!verified) {
80
+ cb(false, statusCode);
81
+ return;
82
+ }
83
+
84
+ cb(true);
85
+ } catch (e) {
86
+ (0, _debugLog.default)(`Error verifying`, e);
87
+ cb(false);
88
+ }
89
+ });
90
+ }
50
91
  });
51
92
  server.on('connection', (webSocketClient, request) => {
52
93
  if (this.log) {
@@ -55,7 +96,12 @@ class WebSocketServer {
55
96
  console.log('received connection');
56
97
  }
57
98
 
58
- const connectionId = (0, _index.createUniqueId)();
99
+ const {
100
+ headers
101
+ } = request;
102
+ const key = headers['sec-websocket-key'];
103
+
104
+ const connectionId = _classPrivateFieldLooseBase(this, _connectionIds)[_connectionIds][key];
59
105
 
60
106
  if (this.log) {
61
107
  this.log.debug(`connect:${connectionId}`);
@@ -63,7 +109,7 @@ class WebSocketServer {
63
109
  (0, _debugLog.default)(`connect:${connectionId}`);
64
110
  }
65
111
 
66
- _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients].addClient(webSocketClient, request, connectionId);
112
+ _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients].addClient(webSocketClient, connectionId);
67
113
  });
68
114
  }
69
115
 
@@ -85,7 +131,7 @@ class WebSocketServer {
85
131
  stop() {}
86
132
 
87
133
  addRoute(functionKey, webSocketEvent) {
88
- _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients].addRoute(functionKey, webSocketEvent.route); // serverlessLog(`route '${route}'`)
134
+ _classPrivateFieldLooseBase(this, _webSocketClients)[_webSocketClients].addRoute(functionKey, webSocketEvent); // serverlessLog(`route '${route}'`)
89
135
 
90
136
  }
91
137
 
@@ -74,7 +74,7 @@ const clearModule = (fP, opts) => {
74
74
  cleanup = false;
75
75
 
76
76
  for (const fn of Object.keys(require.cache)) {
77
- if (require.cache[fn].id !== '.' && require.cache[fn].parent && require.cache[fn].parent.id !== '.' && !require.cache[require.cache[fn].parent.id]) {
77
+ if (require.cache[fn] && require.cache[fn].id !== '.' && require.cache[fn].parent && require.cache[fn].parent.id !== '.' && !require.cache[require.cache[fn].parent.id]) {
78
78
  delete require.cache[fn];
79
79
  cleanup = true;
80
80
  }
@@ -53,7 +53,8 @@ function invocationsRoute(lambda, options, v3Utils) {
53
53
  let functionError = null;
54
54
 
55
55
  if (invokeResults) {
56
- resultPayload = invokeResults.Payload || '';
56
+ const isPayloadDefined = typeof invokeResults.Payload !== 'undefined';
57
+ resultPayload = isPayloadDefined ? invokeResults.Payload : '';
57
58
  statusCode = invokeResults.StatusCode || 200;
58
59
  functionError = invokeResults.FunctionError || null;
59
60
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dedicatedTo": "Blue, a great migrating bird.",
3
3
  "name": "serverless-offline",
4
- "version": "8.4.0",
4
+ "version": "8.5.0",
5
5
  "description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
6
6
  "license": "MIT",
7
7
  "main": "dist/main.js",
@@ -149,6 +149,7 @@
149
149
  "Stewart Gleadow (https://github.com/sgleadow)",
150
150
  "Thales Minussi (https://github.com/tminussi)",
151
151
  "Thang Minh Vu (https://github.com/ittus)",
152
+ "Tom St. Clair (https://github.com/tom-stclair)",
152
153
  "Trevor Leach (https://github.com/trevor-leach)",
153
154
  "Tuan Minh Huynh (https://github.com/tuanmh)",
154
155
  "Utku Turunc (https://github.com/utkuturunc)",
@@ -156,7 +157,8 @@
156
157
  "Dima Krutolianov (https://github.com/dimadk24)",
157
158
  "Bryan Vaz (https://github.com/bryanvaz)",
158
159
  "Justin Ng (https://github.com/njyjn)",
159
- "Fernando Alvarez (https://github.com/jefer590)"
160
+ "Fernando Alvarez (https://github.com/jefer590)",
161
+ "Eric Carter (https://github.com/ericctsf)"
160
162
  ],
161
163
  "husky": {
162
164
  "hooks": {
@@ -202,7 +204,7 @@
202
204
  "@hapi/boom": "^9.1.4",
203
205
  "@hapi/h2o2": "^9.1.0",
204
206
  "@hapi/hapi": "^20.2.1",
205
- "aws-sdk": "^2.1065.0",
207
+ "aws-sdk": "^2.1076.0",
206
208
  "boxen": "^5.1.2",
207
209
  "chalk": "^4.1.2",
208
210
  "cuid": "^2.1.8",
@@ -227,17 +229,17 @@
227
229
  "semver": "^7.3.5",
228
230
  "update-notifier": "^5.1.0",
229
231
  "velocityjs": "^2.0.6",
230
- "ws": "^7.5.6"
232
+ "ws": "^7.5.7"
231
233
  },
232
234
  "devDependencies": {
233
- "@babel/cli": "^7.16.8",
234
- "@babel/core": "^7.16.12",
235
+ "@babel/cli": "^7.17.3",
236
+ "@babel/core": "^7.17.5",
235
237
  "@babel/plugin-proposal-class-properties": "^7.16.7",
236
238
  "@babel/plugin-proposal-dynamic-import": "^7.16.7",
237
239
  "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
238
240
  "@babel/plugin-proposal-optional-chaining": "^7.16.7",
239
241
  "@babel/plugin-transform-modules-commonjs": "^7.16.8",
240
- "@babel/register": "^7.16.9",
242
+ "@babel/register": "^7.17.0",
241
243
  "archiver": "^5.3.0",
242
244
  "babel-eslint": "^10.1.0",
243
245
  "copyfiles": "^2.4.1",
package/CHANGELOG.md DELETED
@@ -1,21 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
-
5
- ## [8.4.0](https://github.com/dherault/serverless-offline/compare/v8.3.1...v8.4.0) (2022-01-28)
6
-
7
- ### Features
8
-
9
- - `go-runner` implementation ([#1320](https://github.com/dherault/serverless-offline/issues/1320)) ([6bb54fd](https://github.com/dherault/serverless-offline/commit/6bb54fdccebd3db61221a9b8f709414876086324))
10
- - `--disableScheduledEvents` CLI param support ([#1185](https://github.com/dherault/serverless-offline/issues/1185)) ([4503567](https://github.com/dherault/serverless-offline/commit/4503567cdb8fa31ac9df98b667a403b0408f8444))
11
-
12
- ### Bug Fixes
13
-
14
- - Handle custom authorizer 401 in non in-process runners ([#1319](https://github.com/dherault/serverless-offline/issues/1319)) ([8d61bde](https://github.com/dherault/serverless-offline/commit/8d61bde74cdfb37410a5c1952ca608e815eeb1cf))
15
- - Support `httpApi` payload override on function level ([#1312](https://github.com/dherault/serverless-offline/issues/1312)) ([8db63dd](https://github.com/dherault/serverless-offline/commit/8db63dda6054198775ed3b567dc3c1dbf73eb574))
16
-
17
- ### [8.3.1](https://github.com/dherault/serverless-offline/compare/v8.3.0...v8.3.1) (2021-11-25)
18
-
19
- ### Bug Fixes
20
-
21
- - Fix handling of modern logs (`Cannot read properties of undefined (reading 'notice')` error)