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 +32 -5
- package/dist/events/http/HttpServer.js +47 -9
- package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +21 -1
- package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +21 -1
- package/dist/events/websocket/WebSocketClients.js +66 -25
- package/dist/events/websocket/WebSocketServer.js +50 -4
- package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +1 -1
- package/dist/lambda/routes/invocations/invocationsRoute.js +2 -1
- package/package.json +9 -7
- package/CHANGELOG.md +0 -21
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
|
|
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,
|
|
138
|
-
key: (0, _fs.readFileSync)((0,
|
|
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,
|
|
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
|
-
});
|
|
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
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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 (!
|
|
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);
|
|
231
|
+
lambdaFunction.setEvent(event);
|
|
198
232
|
|
|
199
233
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
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,
|
|
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,
|
|
318
|
+
addRoute(functionKey, definition) {
|
|
281
319
|
// set the route name
|
|
282
|
-
_classPrivateFieldLooseBase(this, _webSocketRoutes)[_webSocketRoutes].set(route,
|
|
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 '${
|
|
326
|
+
this.log.notice(`route '${definition}'`);
|
|
286
327
|
} else {
|
|
287
|
-
(0, _serverlessLog.default)(`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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
+
"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.
|
|
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.
|
|
232
|
+
"ws": "^7.5.7"
|
|
231
233
|
},
|
|
232
234
|
"devDependencies": {
|
|
233
|
-
"@babel/cli": "^7.
|
|
234
|
-
"@babel/core": "^7.
|
|
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.
|
|
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)
|