serverless-offline 8.4.0 → 8.7.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.
Files changed (28) hide show
  1. package/README.md +81 -9
  2. package/dist/ServerlessOffline.js +12 -0
  3. package/dist/events/{http/authCanExecuteResource.js → authCanExecuteResource.js} +0 -0
  4. package/dist/events/{http/authFunctionNameExtractor.js → authFunctionNameExtractor.js} +1 -1
  5. package/dist/events/{http/authMatchPolicyResource.js → authMatchPolicyResource.js} +0 -0
  6. package/dist/events/http/HttpServer.js +48 -10
  7. package/dist/events/http/authValidateContext.js +48 -0
  8. package/dist/events/http/createAuthScheme.js +23 -12
  9. package/dist/events/http/lambda-events/LambdaIntegrationEvent.js +24 -0
  10. package/dist/events/http/lambda-events/LambdaProxyIntegrationEvent.js +21 -1
  11. package/dist/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +28 -2
  12. package/dist/events/websocket/WebSocketClients.js +259 -26
  13. package/dist/events/websocket/WebSocketServer.js +50 -4
  14. package/dist/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +99 -0
  15. package/dist/events/websocket/lambda-events/index.js +8 -0
  16. package/dist/lambda/LambdaFunction.js +3 -2
  17. package/dist/lambda/handler-runner/HandlerRunner.js +2 -1
  18. package/dist/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +9 -1
  19. package/dist/lambda/handler-runner/docker-runner/DockerContainer.js +4 -1
  20. package/dist/lambda/handler-runner/in-process-runner/InProcessRunner.js +22 -7
  21. package/dist/lambda/handler-runner/java-runner/JavaRunner.js +1 -2
  22. package/dist/lambda/handler-runner/ruby-runner/RubyRunner.js +0 -2
  23. package/dist/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +2 -0
  24. package/dist/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +3 -2
  25. package/dist/lambda/routes/invocations/invocationsRoute.js +2 -1
  26. package/dist/utils/splitHandlerPathAndName.js +13 -9
  27. package/package.json +13 -11
  28. 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)
@@ -243,7 +244,7 @@ to calling it via `aws-sdk`.
243
244
 
244
245
  ## The `process.env.IS_OFFLINE` variable
245
246
 
246
- Will be `"true"` in your handlers and thorough the plugin.
247
+ Will be `"true"` in your handlers and throughout the plugin.
247
248
 
248
249
  ## Docker and Layers
249
250
 
@@ -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
 
@@ -508,8 +531,6 @@ Where the `event` is received in the lambda handler function.
508
531
 
509
532
  There's support for [websocketsApiRouteSelectionExpression](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html) in it's basic form: `$request.body.x.y.z`, where the default value is `$request.body.action`.
510
533
 
511
- Authorizers and wss:// are currently not supported.
512
-
513
534
  ## Usage with Webpack
514
535
 
515
536
  Use [serverless-webpack](https://github.com/serverless-heaven/serverless-webpack) to compile and bundle your ES-next code
@@ -573,7 +594,54 @@ For each debug run:
573
594
 
574
595
  The system will start in wait status. This will also automatically start the chrome browser and wait for you to set breakpoints for inspection. Set the breakpoints as needed and, then, click the play button for the debugging to continue.
575
596
 
576
- Depending on the breakpoint, you may need to call the URL path for your function in seperate browser window for your serverless function to be run and made available for debugging.
597
+ Depending on the breakpoint, you may need to call the URL path for your function in separate browser window for your serverless function to be run and made available for debugging.
598
+
599
+ ### Interactive Debugging with Visual Studio Code (VSC)
600
+
601
+ With newer versions of node (6.3+) the node inspector is already part of your node environment and you can take advantage of debugging inside your IDE with source-map support. Here is the example configuration to debug interactively with VSC. It has two steps.
602
+
603
+ #### Step 1 : Adding a launch configuration in IDE
604
+
605
+ Add a new [launch configuration](https://code.visualstudio.com/docs/editor/debugging) to VSC like this:
606
+
607
+ ```json
608
+ {
609
+
610
+ "type": "node",
611
+ "request": "launch",
612
+ "name": "Debug Serverless Offline",
613
+ "cwd": "${workspaceFolder}",
614
+ "runtimeExecutable": "npm",
615
+ "runtimeArgs": [
616
+ "run",
617
+ "debug"
618
+ ],
619
+ "sourceMaps": true
620
+ }
621
+
622
+ ```
623
+
624
+ #### Step2 : Adding a debug script
625
+
626
+ You will also need to add a `debug` script reference in your `package.json file`
627
+
628
+ Add this to the `scripts` section:
629
+
630
+ > Unix/Mac: `"debug" : "export SLS_DEBUG=* && node --inspect /usr/local/bin/serverless offline"`
631
+
632
+ > Windows: `"debug": "SET SLS_DEBUG=* && node --inspect node_modules\\serverless\\bin\\serverless offline"`
633
+
634
+ Example:
635
+
636
+ ```json
637
+ ....
638
+ "scripts": {
639
+ "debug" : "SET SLS_DEBUG=* && node --inspect node_modules\\serverless\\bin\\serverless offline"
640
+ }
641
+ ```
642
+
643
+ In VSC, you can, then, add breakpoints to your code. To start a debug sessions you can either start your script in `package.json` by clicking the hovering debug intellisense icon or by going to your debug pane and selecting the Debug Serverless Offline configuration.
644
+
577
645
 
578
646
  ## Resource permissions and AWS profile
579
647
 
@@ -744,6 +812,10 @@ We try to follow [Airbnb's JavaScript Style Guide](https://github.com/airbnb/jav
744
812
  | :------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------: |
745
813
  | [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
814
 
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) |
815
+ | [<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
816
  | :---------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------: | ------------------------------------- |
749
817
  | [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) |
818
+
819
+ | [<img alt="ericctsf" src="https://avatars.githubusercontent.com/u/42775388?s=400&v=4" width="117">](https://github.com/ericctsf) | | | | |
820
+ | :------------------------------------------------------------------------------------------------------------------------------: | :-: | :-: | :-: | :-: |
821
+ | [ericctsf](https://github.com/erictsf) | | | | |
@@ -96,6 +96,10 @@ class ServerlessOffline {
96
96
  lifecycleEvents: ['init', 'ready', 'end'],
97
97
  options: _index2.commandOptions,
98
98
  usage: 'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.'
99
+ },
100
+ functionsUpdated: {
101
+ type: 'entrypoint',
102
+ lifecycleEvents: ['cleanup']
99
103
  }
100
104
  },
101
105
  lifecycleEvents: ['start'],
@@ -106,6 +110,7 @@ class ServerlessOffline {
106
110
  this.hooks = {
107
111
  'offline:start:init': this.start.bind(this),
108
112
  'offline:start:ready': this.ready.bind(this),
113
+ 'offline:functionsUpdated:cleanup': this.cleanupFunctions.bind(this),
109
114
  'offline:start': this._startWithExplicitEnd.bind(this),
110
115
  'offline:start:end': this.end.bind(this)
111
116
  };
@@ -203,6 +208,13 @@ class ServerlessOffline {
203
208
  process.exit(0);
204
209
  }
205
210
  }
211
+
212
+ async cleanupFunctions() {
213
+ if (_classPrivateFieldLooseBase(this, _lambda)[_lambda]) {
214
+ (0, _serverlessLog.default)('Forcing cleanup of Lambda functions');
215
+ await _classPrivateFieldLooseBase(this, _lambda)[_lambda].cleanup();
216
+ }
217
+ }
206
218
  /**
207
219
  * Entry point for the plugin (sls offline) when running 'sls offline'
208
220
  * The call to this.end() would terminate the process before 'offline:start:end' could be consumed
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = authFunctionNameExtractor;
7
7
 
8
- var _serverlessLog2 = _interopRequireDefault(require("../../serverlessLog.js"));
8
+ var _serverlessLog2 = _interopRequireDefault(require("../serverlessLog.js"));
9
9
 
10
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
11
 
@@ -9,13 +9,15 @@ 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 _authFunctionNameExtractor = _interopRequireDefault(require("./authFunctionNameExtractor.js"));
18
+ var _module = require("module");
19
+
20
+ var _authFunctionNameExtractor = _interopRequireDefault(require("../authFunctionNameExtractor.js"));
19
21
 
20
22
  var _authJWTSettingsExtractor = _interopRequireDefault(require("./authJWTSettingsExtractor.js"));
21
23
 
@@ -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
 
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = authValidateContext;
7
+
8
+ var _boom = _interopRequireDefault(require("@hapi/boom"));
9
+
10
+ var _serverlessLog = _interopRequireDefault(require("../../serverlessLog.js"));
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ function internalServerError(message) {
15
+ const errorType = 'AuthorizerConfigurationException';
16
+
17
+ const error = _boom.default.internal();
18
+
19
+ error.output.payload.message = message;
20
+ error.output.payload.error = errorType;
21
+ error.output.headers['x-amzn-ErrorType'] = errorType;
22
+ return error;
23
+ }
24
+
25
+ function isValidContext(context) {
26
+ return Object.values(context).every(i => typeof i === 'string' || typeof i === 'boolean' || typeof i === 'number');
27
+ }
28
+
29
+ function transform(context) {
30
+ Object.keys(context).forEach(i => {
31
+ context[i] = context[i].toString();
32
+ });
33
+ return context;
34
+ }
35
+
36
+ function authValidateContext(context, authFunName) {
37
+ if (typeof context !== 'object') {
38
+ return internalServerError('Authorizer response context must be an object');
39
+ }
40
+
41
+ if (!isValidContext(context)) {
42
+ const error = 'Authorizer response context values must be of type string, number, or boolean';
43
+ (0, _serverlessLog.default)(`Detected invalid value types returned in authorizer context: (λ: ${authFunName}). ${error}. ` + 'More info: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html');
44
+ return internalServerError(error);
45
+ }
46
+
47
+ return transform(context);
48
+ }
@@ -7,7 +7,9 @@ exports.default = createAuthScheme;
7
7
 
8
8
  var _boom = _interopRequireDefault(require("@hapi/boom"));
9
9
 
10
- var _authCanExecuteResource = _interopRequireDefault(require("./authCanExecuteResource.js"));
10
+ var _authCanExecuteResource = _interopRequireDefault(require("../authCanExecuteResource.js"));
11
+
12
+ var _authValidateContext = _interopRequireDefault(require("./authValidateContext.js"));
11
13
 
12
14
  var _debugLog = _interopRequireDefault(require("../../debugLog.js"));
13
15
 
@@ -113,11 +115,9 @@ function createAuthScheme(authorizerOptions, provider, lambda, {
113
115
 
114
116
  try {
115
117
  const result = await lambdaFunction.runHandler();
116
- if (result === 'Unauthorized') return _boom.default.unauthorized('Unauthorized'); // return processResponse(null, result)
117
-
118
- const policy = result; // Validate that the policy document has the principalId set
118
+ if (result === 'Unauthorized') return _boom.default.unauthorized('Unauthorized'); // Validate that the policy document has the principalId set
119
119
 
120
- if (!policy.principalId) {
120
+ if (!result.principalId) {
121
121
  if (log) {
122
122
  log.notice(`Authorization response did not include a principalId: (λ: ${authFunName})`);
123
123
  } else {
@@ -127,7 +127,7 @@ function createAuthScheme(authorizerOptions, provider, lambda, {
127
127
  return _boom.default.forbidden('No principalId set on the Response');
128
128
  }
129
129
 
130
- if (!(0, _authCanExecuteResource.default)(policy.policyDocument, event.methodArn)) {
130
+ if (!(0, _authCanExecuteResource.default)(result.policyDocument, event.methodArn)) {
131
131
  if (log) {
132
132
  log.notice(`Authorization response didn't authorize user to access resource: (λ: ${authFunName})`);
133
133
  } else {
@@ -135,6 +135,18 @@ function createAuthScheme(authorizerOptions, provider, lambda, {
135
135
  }
136
136
 
137
137
  return _boom.default.forbidden('User is not authorized to access this resource');
138
+ } // validate the resulting context, ensuring that all
139
+ // values are either string, number, or boolean types
140
+
141
+
142
+ if (result.context) {
143
+ const validationResult = (0, _authValidateContext.default)(result.context, authFunName);
144
+
145
+ if (validationResult instanceof Error) {
146
+ return validationResult;
147
+ }
148
+
149
+ result.context = validationResult;
138
150
  }
139
151
 
140
152
  if (log) {
@@ -145,17 +157,16 @@ function createAuthScheme(authorizerOptions, provider, lambda, {
145
157
 
146
158
  const authorizer = {
147
159
  integrationLatency: '42',
148
- principalId: policy.principalId,
149
- ...policy.context
160
+ principalId: result.principalId,
161
+ ...result.context
150
162
  }; // Set the credentials for the rest of the pipeline
151
- // return resolve(
152
163
 
153
164
  return h.authenticated({
154
165
  credentials: {
155
166
  authorizer,
156
- context: policy.context,
157
- principalId: policy.principalId,
158
- usageIdentifierKey: policy.usageIdentifierKey
167
+ context: result.context,
168
+ principalId: result.principalId,
169
+ usageIdentifierKey: result.usageIdentifierKey
159
170
  }
160
171
  });
161
172
  } catch (err) {
@@ -17,6 +17,10 @@ var id = 0;
17
17
 
18
18
  function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
19
19
 
20
+ const {
21
+ parse
22
+ } = JSON;
23
+
20
24
  var _path = /*#__PURE__*/_classPrivateFieldLooseKey("path");
21
25
 
22
26
  var _request = /*#__PURE__*/_classPrivateFieldLooseKey("request");
@@ -51,6 +55,26 @@ class LambdaIntegrationEvent {
51
55
  }
52
56
 
53
57
  create() {
58
+ if (process.env.AUTHORIZER) {
59
+ try {
60
+ const authorizerContext = parse(process.env.AUTHORIZER);
61
+
62
+ if (authorizerContext) {
63
+ _classPrivateFieldLooseBase(this, _request)[_request].auth = { ..._classPrivateFieldLooseBase(this, _request)[_request].auth,
64
+ credentials: {
65
+ authorizer: authorizerContext
66
+ }
67
+ };
68
+ }
69
+ } catch (error) {
70
+ if (this.log) {
71
+ this.log.error('Could not parse process.env.AUTHORIZER, make sure it is correct JSON');
72
+ } else {
73
+ console.error('Serverless-offline: Could not parse process.env.AUTHORIZER, make sure it is correct JSON.');
74
+ }
75
+ }
76
+ }
77
+
54
78
  const velocityContext = new _VelocityContext.default(_classPrivateFieldLooseBase(this, _request)[_request], _classPrivateFieldLooseBase(this, _stage)[_stage], _classPrivateFieldLooseBase(this, _request)[_request].payload || {}, _classPrivateFieldLooseBase(this, _path)[_path]).getContext();
55
79
  const event = (0, _renderVelocityTemplateObject.default)(_classPrivateFieldLooseBase(this, _requestTemplate)[_requestTemplate], velocityContext, this.v3Utils);
56
80
  return event;
@@ -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
@@ -150,7 +169,13 @@ class LambdaProxyIntegrationEventV2 {
150
169
  const httpMethod = method.toUpperCase();
151
170
  const requestTime = (0, _index.formatToClfTime)(received);
152
171
  const requestTimeEpoch = received;
153
- const cookies = Object.entries(_classPrivateFieldLooseBase(this, _request)[_request].state).map(([key, value]) => `${key}=${value}`);
172
+ const cookies = Object.entries(_classPrivateFieldLooseBase(this, _request)[_request].state).flatMap(([key, value]) => {
173
+ if (Array.isArray(value)) {
174
+ return value.map(v => `${key}=${v}`);
175
+ }
176
+
177
+ return `${key}=${value}`;
178
+ });
154
179
  return {
155
180
  version: '2.0',
156
181
  routeKey: _classPrivateFieldLooseBase(this, _routeKey)[_routeKey],
@@ -177,6 +202,7 @@ class LambdaProxyIntegrationEventV2 {
177
202
  sourceIp: remoteAddress,
178
203
  userAgent: _headers['user-agent'] || ''
179
204
  },
205
+ operationName: _classPrivateFieldLooseBase(this, _additionalRequestContext)[_additionalRequestContext].operationName,
180
206
  requestId: 'offlineContext_resourceId',
181
207
  routeKey: _classPrivateFieldLooseBase(this, _routeKey)[_routeKey],
182
208
  stage: _classPrivateFieldLooseBase(this, _stage)[_stage],