@yopdev/dev-server 3.0.2 → 3.0.3-alpha.2

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.
@@ -17,7 +17,9 @@ jobs:
17
17
  with:
18
18
  node-version: 20
19
19
  - run: npm ci
20
- - run: npm test
20
+ - env:
21
+ NODE_OPTIONS: --experimental-vm-modules
22
+ run: npm test
21
23
 
22
24
  publish-npm:
23
25
  needs: build
@@ -0,0 +1,89 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { newLambdaProxyFromCloudFormationTemplate } from "../src/cloudformation-lambda-http-proxy";
3
+ import { v2 } from "../src/mappers";
4
+ import { DevServer, promised } from "../src";
5
+ import axios from "axios";
6
+ import { STUB_HANDLER_CALLED_BODY } from "./stub-handlers";
7
+ import { alwaysDeny } from "./test-model";
8
+
9
+ describe("the cloudformation lambda http proxy", () => {
10
+ it("parses a lambda function", async () => {
11
+ let endpoint: string;
12
+ const devServer = await new DevServer('cfn-simple-lambda',
13
+ promised(
14
+ (c) => newLambdaProxyFromCloudFormationTemplate(
15
+ 'cfn-simple-lambda',
16
+ { protocol: 'http:', host: '127.0.0.1', port: undefined },
17
+ v2(),
18
+ {
19
+ extraRoutes: [],
20
+ template: () => './__tests__/simple-lambda-function.template',
21
+ handlers: () => '../dist/__tests__/stub-handlers.js',
22
+ handlerNameResolver: (n) => n,
23
+ },
24
+ async (url) => { endpoint = url; }
25
+ ).start(c))
26
+ ).start()
27
+
28
+ return expect(axios.get(`${endpoint}a`)
29
+ .then((r) => r.data)
30
+ .finally(async () => devServer.stop())
31
+ )
32
+ .resolves
33
+ .toEqual(STUB_HANDLER_CALLED_BODY)
34
+ })
35
+
36
+ it("parses a lambda function with authorizer", async () => {
37
+ let endpoint: string;
38
+ const devServer = await new DevServer('cfn-authorized-lambda',
39
+ promised(
40
+ (c) => newLambdaProxyFromCloudFormationTemplate(
41
+ 'cfn-authorized-lambda',
42
+ { protocol: 'http:', host: '127.0.0.1', port: undefined },
43
+ v2(),
44
+ {
45
+ extraRoutes: [],
46
+ authorizer: () => async () => alwaysDeny(),
47
+ template: () => './__tests__/simple-lambda-function.template',
48
+ handlers: () => '../dist/__tests__/stub-handlers.js',
49
+ handlerNameResolver: (n) => n,
50
+ },
51
+ async (url) => { endpoint = url; }
52
+ ).start(c))
53
+ ).start()
54
+
55
+ return expect(axios.get(`${endpoint}a`, { validateStatus: () => true })
56
+ .then((r) => r.status)
57
+ .finally(async () => devServer.stop())
58
+ )
59
+ .resolves
60
+ .toEqual(401)
61
+ })
62
+
63
+ it("disables the authorizer when auth is set to NONE", async () => {
64
+ let endpoint: string;
65
+ const devServer = await new DevServer('cfn-none-auth-lambda',
66
+ promised(
67
+ (c) => newLambdaProxyFromCloudFormationTemplate(
68
+ 'cfn-none-auth-lambda',
69
+ { protocol: 'http:', host: '127.0.0.1', port: undefined },
70
+ v2(),
71
+ {
72
+ extraRoutes: [],
73
+ authorizer: () => async () => alwaysDeny(),
74
+ template: () => './__tests__/lambda-function-with-none-auth.template',
75
+ handlers: () => '../dist/__tests__/stub-handlers.js',
76
+ handlerNameResolver: (n) => n,
77
+ },
78
+ async (url) => { endpoint = url; }
79
+ ).start(c))
80
+ ).start()
81
+
82
+ return expect(axios.get(`${endpoint}a`)
83
+ .then((r) => r.data)
84
+ .finally(async () => devServer.stop())
85
+ )
86
+ .resolves
87
+ .toEqual(STUB_HANDLER_CALLED_BODY)
88
+ })
89
+ })
@@ -0,0 +1,13 @@
1
+ Resources:
2
+ test:
3
+ Type: AWS::Serverless::Function
4
+ Properties:
5
+ Handler: stubHandler
6
+ Events:
7
+ Test:
8
+ Type: HttpApi
9
+ Properties:
10
+ Path: /{proxy+}
11
+ Method: GET
12
+ Auth:
13
+ Authorizer: NONE
@@ -1,8 +1,9 @@
1
1
  import { describe, it, expect } from "@jest/globals";
2
2
  import { DevServer } from "../src";
3
- import { UNAUTHORIZED, newLambdaHttpProxy } from "../src/lambda-http-proxy";
3
+ import { newLambdaHttpProxy } from "../src/lambda-http-proxy";
4
4
  import axios from 'axios';
5
5
  import { v2 } from "../src/mappers";
6
+ import { alwaysDeny } from "./test-model";
6
7
 
7
8
  describe("the lambda http proxy", () => {
8
9
  const payloadVersionV2Mapper = v2().mapper;
@@ -58,7 +59,7 @@ describe("the lambda http proxy", () => {
58
59
  {
59
60
  settings: { protocol: 'http:', host: '127.0.0.1', port: undefined },
60
61
  routes: [
61
- { method: /.*/, path: /.*/, weight: 0, handler: () => Promise.resolve({ statusCode: 200, body: 'handled!' }), authorizer: () => Promise.reject(UNAUTHORIZED) },
62
+ { method: /.*/, path: /.*/, weight: 0, handler: () => Promise.resolve({ statusCode: 200, body: 'handled!' }), authorizer: async () => alwaysDeny() },
62
63
  ],
63
64
  mapper: payloadVersionV2Mapper,
64
65
  },
@@ -0,0 +1,11 @@
1
+ Resources:
2
+ test:
3
+ Type: AWS::Serverless::Function
4
+ Properties:
5
+ Handler: stubHandler
6
+ Events:
7
+ Test:
8
+ Type: HttpApi
9
+ Properties:
10
+ Path: /{proxy+}
11
+ Method: GET
@@ -0,0 +1,9 @@
1
+ export const STUB_HANDLER_CALLED_BODY = 'stub handler called';
2
+
3
+ export const stubHandler = () => new StubHandler().handle();
4
+ class StubHandler {
5
+ handle = () => ({
6
+ statusCode: 200,
7
+ body: STUB_HANDLER_CALLED_BODY,
8
+ })
9
+ }
@@ -0,0 +1,11 @@
1
+ export const alwaysDeny = () => ({
2
+ principalId: '',
3
+ policyDocument: {
4
+ Version: '',
5
+ Statement: [{
6
+ Effect: 'Deny',
7
+ NotAction: [],
8
+ NotPrincipal: [],
9
+ }]
10
+ }
11
+ })
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const globals_1 = require("@jest/globals");
7
+ const cloudformation_lambda_http_proxy_1 = require("../src/cloudformation-lambda-http-proxy");
8
+ const mappers_1 = require("../src/mappers");
9
+ const src_1 = require("../src");
10
+ const axios_1 = __importDefault(require("axios"));
11
+ const stub_handlers_1 = require("./stub-handlers");
12
+ const test_model_1 = require("./test-model");
13
+ (0, globals_1.describe)("the cloudformation lambda http proxy", () => {
14
+ (0, globals_1.it)("parses a lambda function", async () => {
15
+ let endpoint;
16
+ const devServer = await new src_1.DevServer('cfn-simple-lambda', (0, src_1.promised)((c) => (0, cloudformation_lambda_http_proxy_1.newLambdaProxyFromCloudFormationTemplate)('cfn-simple-lambda', { protocol: 'http:', host: '127.0.0.1', port: undefined }, (0, mappers_1.v2)(), {
17
+ extraRoutes: [],
18
+ template: () => './__tests__/simple-lambda-function.template',
19
+ handlers: () => '../dist/__tests__/stub-handlers.js',
20
+ handlerNameResolver: (n) => n,
21
+ }, async (url) => { endpoint = url; }).start(c))).start();
22
+ return (0, globals_1.expect)(axios_1.default.get(`${endpoint}a`)
23
+ .then((r) => r.data)
24
+ .finally(async () => devServer.stop()))
25
+ .resolves
26
+ .toEqual(stub_handlers_1.STUB_HANDLER_CALLED_BODY);
27
+ });
28
+ (0, globals_1.it)("parses a lambda function with authorizer", async () => {
29
+ let endpoint;
30
+ const devServer = await new src_1.DevServer('cfn-authorized-lambda', (0, src_1.promised)((c) => (0, cloudformation_lambda_http_proxy_1.newLambdaProxyFromCloudFormationTemplate)('cfn-authorized-lambda', { protocol: 'http:', host: '127.0.0.1', port: undefined }, (0, mappers_1.v2)(), {
31
+ extraRoutes: [],
32
+ authorizer: () => async () => (0, test_model_1.alwaysDeny)(),
33
+ template: () => './__tests__/simple-lambda-function.template',
34
+ handlers: () => '../dist/__tests__/stub-handlers.js',
35
+ handlerNameResolver: (n) => n,
36
+ }, async (url) => { endpoint = url; }).start(c))).start();
37
+ return (0, globals_1.expect)(axios_1.default.get(`${endpoint}a`, { validateStatus: () => true })
38
+ .then((r) => r.status)
39
+ .finally(async () => devServer.stop()))
40
+ .resolves
41
+ .toEqual(401);
42
+ });
43
+ (0, globals_1.it)("disables the authorizer when auth is set to NONE", async () => {
44
+ let endpoint;
45
+ const devServer = await new src_1.DevServer('cfn-none-auth-lambda', (0, src_1.promised)((c) => (0, cloudformation_lambda_http_proxy_1.newLambdaProxyFromCloudFormationTemplate)('cfn-none-auth-lambda', { protocol: 'http:', host: '127.0.0.1', port: undefined }, (0, mappers_1.v2)(), {
46
+ extraRoutes: [],
47
+ authorizer: () => async () => (0, test_model_1.alwaysDeny)(),
48
+ template: () => './__tests__/lambda-function-with-none-auth.template',
49
+ handlers: () => '../dist/__tests__/stub-handlers.js',
50
+ handlerNameResolver: (n) => n,
51
+ }, async (url) => { endpoint = url; }).start(c))).start();
52
+ return (0, globals_1.expect)(axios_1.default.get(`${endpoint}a`)
53
+ .then((r) => r.data)
54
+ .finally(async () => devServer.stop()))
55
+ .resolves
56
+ .toEqual(stub_handlers_1.STUB_HANDLER_CALLED_BODY);
57
+ });
58
+ });
@@ -8,6 +8,7 @@ const src_1 = require("../src");
8
8
  const lambda_http_proxy_1 = require("../src/lambda-http-proxy");
9
9
  const axios_1 = __importDefault(require("axios"));
10
10
  const mappers_1 = require("../src/mappers");
11
+ const test_model_1 = require("./test-model");
11
12
  (0, globals_1.describe)("the lambda http proxy", () => {
12
13
  const payloadVersionV2Mapper = (0, mappers_1.v2)().mapper;
13
14
  (0, globals_1.it)("invokes the handler with the heaviest route", async () => {
@@ -47,7 +48,7 @@ const mappers_1 = require("../src/mappers");
47
48
  const tested = await new src_1.DevServer('lambda-http-proxy', (0, lambda_http_proxy_1.newLambdaHttpProxy)('service', {
48
49
  settings: { protocol: 'http:', host: '127.0.0.1', port: undefined },
49
50
  routes: [
50
- { method: /.*/, path: /.*/, weight: 0, handler: () => Promise.resolve({ statusCode: 200, body: 'handled!' }), authorizer: () => Promise.reject(lambda_http_proxy_1.UNAUTHORIZED) },
51
+ { method: /.*/, path: /.*/, weight: 0, handler: () => Promise.resolve({ statusCode: 200, body: 'handled!' }), authorizer: async () => (0, test_model_1.alwaysDeny)() },
51
52
  ],
52
53
  mapper: payloadVersionV2Mapper,
53
54
  }, async (url) => { endpoint = url; })).start();
@@ -0,0 +1,5 @@
1
+ export declare const STUB_HANDLER_CALLED_BODY = "stub handler called";
2
+ export declare const stubHandler: () => {
3
+ statusCode: number;
4
+ body: string;
5
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stubHandler = exports.STUB_HANDLER_CALLED_BODY = void 0;
4
+ exports.STUB_HANDLER_CALLED_BODY = 'stub handler called';
5
+ const stubHandler = () => new StubHandler().handle();
6
+ exports.stubHandler = stubHandler;
7
+ class StubHandler {
8
+ constructor() {
9
+ this.handle = () => ({
10
+ statusCode: 200,
11
+ body: exports.STUB_HANDLER_CALLED_BODY,
12
+ });
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ export declare const alwaysDeny: () => {
2
+ principalId: string;
3
+ policyDocument: {
4
+ Version: string;
5
+ Statement: {
6
+ Effect: string;
7
+ NotAction: any[];
8
+ NotPrincipal: any[];
9
+ }[];
10
+ };
11
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.alwaysDeny = void 0;
4
+ const alwaysDeny = () => ({
5
+ principalId: '',
6
+ policyDocument: {
7
+ Version: '',
8
+ Statement: [{
9
+ Effect: 'Deny',
10
+ NotAction: [],
11
+ NotPrincipal: [],
12
+ }]
13
+ }
14
+ });
15
+ exports.alwaysDeny = alwaysDeny;
@@ -4,8 +4,9 @@ import { HttpSettings } from './http-server';
4
4
  import { Authorizer, Route } from './lambda-http-proxy';
5
5
  import { Callback, Service, Startable } from './services';
6
6
  import { LambdaPayloadVersion } from './mappers';
7
- export declare const newLambdaProxyFromCloudFormationTemplate: <Context, AuthorizerContext, Event_1, HandlerResponse>(name: string, settings: HttpSettings, payloadVersion: LambdaPayloadVersion<AuthorizerContext, Event_1, HandlerResponse>, config: CloudFormationLambdaProxyConfig<Context, AuthorizerContext, Event_1, HandlerResponse>, callback: Callback<string>) => Startable<Service<string>>;
8
- type CloudFormationLambdaProxyConfig<Context, AuthorizerContext, Event, HandlerResponse> = {
7
+ import { APIGatewayAuthorizerResult } from 'aws-lambda';
8
+ export declare const newLambdaProxyFromCloudFormationTemplate: <Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event_1, HandlerResponse>(name: string, settings: HttpSettings, payloadVersion: LambdaPayloadVersion<AuthorizerContext, Event_1, HandlerResponse>, config: CloudFormationLambdaProxyConfig<Context, AuthorizerContext, Event_1, HandlerResponse>, callback: Callback<string>) => Startable<Service<string>>;
9
+ type CloudFormationLambdaProxyConfig<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> = {
9
10
  authorizer?: (config: DevServerConfig) => Authorizer<AuthorizerContext> | undefined;
10
11
  extraRoutes: Route<Context, AuthorizerContext, Event, HandlerResponse>[];
11
12
  prepare?: (config: DevServerConfig) => Promise<void>;
@@ -6,6 +6,7 @@ const lambda_http_proxy_1 = require("./lambda-http-proxy");
6
6
  const PATH_VARIABLE_CAPTURE = /{(.*?)}/g;
7
7
  const QUERY_STRING_OR_LOCATION_REG_EXP = '(?:([?#].*))?';
8
8
  const PROXY_PATH_PARAM = 'proxy';
9
+ const NOOP_AUTHORIZER = async () => undefined;
9
10
  const newLambdaProxyFromCloudFormationTemplate = (name, settings, payloadVersion, config, callback) => new CloudFormationLambdaProxy(name, settings, config.extraRoutes, payloadVersion, config, callback);
10
11
  exports.newLambdaProxyFromCloudFormationTemplate = newLambdaProxyFromCloudFormationTemplate;
11
12
  class CloudFormationLambdaProxy extends cloudformation_1.CloudFormationSetup {
@@ -15,6 +16,7 @@ class CloudFormationLambdaProxy extends cloudformation_1.CloudFormationSetup {
15
16
  path: new RegExp(`^${pathWithCapturingGroups}${QUERY_STRING_OR_LOCATION_REG_EXP}?$`),
16
17
  weight: this.computeWeight(pathWithCapturingGroups),
17
18
  handler: this.pathParameterCapture(new RegExp(pathWithCapturingGroups), handler, config.context),
19
+ authorizer: event.Properties?.Auth?.Authorizer === 'NONE' ? NOOP_AUTHORIZER : undefined,
18
20
  })), config.prepare, config);
19
21
  this.settings = settings;
20
22
  this.extraRoutes = extraRoutes;
@@ -22,7 +24,7 @@ class CloudFormationLambdaProxy extends cloudformation_1.CloudFormationSetup {
22
24
  this.afterStart = (name, cfg, routes) => (0, lambda_http_proxy_1.newLambdaHttpProxy)(`${name}:apigw`, {
23
25
  settings: this.settings,
24
26
  routes: this.extraRoutes.concat(routes),
25
- authorizer: (this.authorizer || (() => undefined))(cfg),
27
+ authorizer: (this.authorizer || (() => NOOP_AUTHORIZER))(cfg),
26
28
  mapper: this.mapper,
27
29
  }, this.callback);
28
30
  this.computeWeight = (path) => this.segments(path) - this.variables(path);
@@ -12,7 +12,7 @@ class CloudFormationSetup {
12
12
  this.eventFilter = eventFilter;
13
13
  this.handlerResolver = handlerResolver;
14
14
  this.beforeStart = beforeStart;
15
- this.start = async (config) => (this.beforeStart || Promise.resolve)(config)
15
+ this.start = async (config) => (this.beforeStart || (async () => undefined))(config)
16
16
  .then(this.resolvedHandlers)
17
17
  .then(async (routes) => this.afterStart(this.name, config, routes));
18
18
  this.resolvedHandlers = async () => {
@@ -1,6 +1,6 @@
1
1
  export { DevServer } from './dev-server';
2
2
  export { newHttpServer as httpServer, HttpSettings } from './http-server';
3
- export { newLambdaHttpProxy as lambdaHttpProxy, UNAUTHORIZED } from './lambda-http-proxy';
3
+ export { newLambdaHttpProxy as lambdaHttpProxy } from './lambda-http-proxy';
4
4
  export { v1 as v1LambdaProxyPayload, v2 as v2LambdaProxyPayload } from './mappers';
5
5
  export { newLambdaProxyFromCloudFormationTemplate as cloudFormationLambdaProxy } from './cloudformation-lambda-http-proxy';
6
6
  export { newDynamoDbTableFromCloudFormationTemplate as cloudFormationDynamoDbTable } from './cloudformation-dynamodb-table';
package/dist/src/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Service = exports.promised = exports.lazy = exports.oneThenOther = exports.allOf = exports.tunnel = exports.container = exports.dynamoDbTable = exports.preTrafficHooks = exports.scheduledTasks = exports.cloudFormationSnsEventHandlers = exports.eventsProxy = exports.internalQueue = exports.snsHttpProxy = exports.cloudFormationDynamoDbTable = exports.cloudFormationLambdaProxy = exports.v2LambdaProxyPayload = exports.v1LambdaProxyPayload = exports.UNAUTHORIZED = exports.lambdaHttpProxy = exports.httpServer = exports.DevServer = void 0;
3
+ exports.Service = exports.promised = exports.lazy = exports.oneThenOther = exports.allOf = exports.tunnel = exports.container = exports.dynamoDbTable = exports.preTrafficHooks = exports.scheduledTasks = exports.cloudFormationSnsEventHandlers = exports.eventsProxy = exports.internalQueue = exports.snsHttpProxy = exports.cloudFormationDynamoDbTable = exports.cloudFormationLambdaProxy = exports.v2LambdaProxyPayload = exports.v1LambdaProxyPayload = exports.lambdaHttpProxy = exports.httpServer = exports.DevServer = void 0;
4
4
  var dev_server_1 = require("./dev-server");
5
5
  Object.defineProperty(exports, "DevServer", { enumerable: true, get: function () { return dev_server_1.DevServer; } });
6
6
  var http_server_1 = require("./http-server");
7
7
  Object.defineProperty(exports, "httpServer", { enumerable: true, get: function () { return http_server_1.newHttpServer; } });
8
8
  var lambda_http_proxy_1 = require("./lambda-http-proxy");
9
9
  Object.defineProperty(exports, "lambdaHttpProxy", { enumerable: true, get: function () { return lambda_http_proxy_1.newLambdaHttpProxy; } });
10
- Object.defineProperty(exports, "UNAUTHORIZED", { enumerable: true, get: function () { return lambda_http_proxy_1.UNAUTHORIZED; } });
11
10
  var mappers_1 = require("./mappers");
12
11
  Object.defineProperty(exports, "v1LambdaProxyPayload", { enumerable: true, get: function () { return mappers_1.v1; } });
13
12
  Object.defineProperty(exports, "v2LambdaProxyPayload", { enumerable: true, get: function () { return mappers_1.v2; } });
@@ -2,8 +2,8 @@
2
2
  import { HttpSettings } from "./http-server";
3
3
  import { LambdaMapperFactory } from "./mappers";
4
4
  import { Service, Callback } from "./services";
5
- export declare const UNAUTHORIZED: Error;
6
- export declare const newLambdaHttpProxy: <Context, AuthorizerContext, Event_1, HandlerResponse>(name: string, config: {
5
+ import { APIGatewayAuthorizerResult } from "aws-lambda";
6
+ export declare const newLambdaHttpProxy: <Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event_1, HandlerResponse>(name: string, config: {
7
7
  settings: HttpSettings;
8
8
  routes: Route<Context, AuthorizerContext, Event_1, HandlerResponse>[];
9
9
  mapper: LambdaMapperFactory<AuthorizerContext, Event_1, HandlerResponse>;
@@ -18,11 +18,11 @@ export declare abstract class Response {
18
18
  constructor(statusCode: number, contentType: string | undefined, location: string | undefined, cookies: string[] | undefined);
19
19
  body: () => string | Buffer | undefined;
20
20
  }
21
- export type Route<Context, AuthorizerContext, Event, HandlerResponse> = {
21
+ export type Route<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> = {
22
22
  method: RegExp;
23
23
  path: RegExp;
24
24
  weight: number;
25
25
  authorizer?: Authorizer<AuthorizerContext>;
26
26
  handler: (event: Event, context: Context) => Promise<HandlerResponse>;
27
27
  };
28
- export type Authorizer<Context> = (authorization: string) => Promise<Context | undefined>;
28
+ export type Authorizer<Context extends APIGatewayAuthorizerResult> = (authorization: string) => Promise<Context | undefined>;
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Response = exports.newLambdaHttpProxy = exports.UNAUTHORIZED = void 0;
3
+ exports.Response = exports.newLambdaHttpProxy = void 0;
4
4
  const logging_1 = require("@yopdev/logging");
5
5
  const http_server_1 = require("./http-server");
6
6
  const responses_1 = require("./responses");
7
7
  const services_1 = require("./services");
8
- exports.UNAUTHORIZED = new Error('UNAUTHORIZED');
8
+ const UNAUTHORIZED = new Error('UNAUTHORIZED');
9
9
  const newLambdaHttpProxy = (name, config, callback) => new services_1.Service(new LambdaHttpProxy(name, config.settings, config.routes, config.mapper, config.authorizer ?? (() => Promise.resolve(undefined)), config.context), callback);
10
10
  exports.newLambdaHttpProxy = newLambdaHttpProxy;
11
11
  class LambdaHttpProxy {
@@ -19,7 +19,10 @@ class LambdaHttpProxy {
19
19
  this.stop = () => this.server.stop();
20
20
  this.handler = (authorizer, lambdaHandler) => (request, body, response) => this.mapper.newInstance(request, body)
21
21
  .then(async (mapper) => authorizer(mapper.authorization())
22
- .then(async (context) => lambdaHandler(mapper.event(context), this.context())
22
+ .then(async (context) => context?.policyDocument?.Statement?.find((s) => s.Effect === 'Deny') === undefined
23
+ ? { ...context, lambda: context?.context }
24
+ : Promise.reject(UNAUTHORIZED))
25
+ .then((context) => lambdaHandler(mapper.event(context), this.context())
23
26
  .then((lambda) => {
24
27
  const result = mapper.toResponse(lambda);
25
28
  return (0, responses_1.writeResponse)(response, result.statusCode, result.body(), result.contentType, result.location, result.cookies);
@@ -27,7 +30,7 @@ class LambdaHttpProxy {
27
30
  .catch((e) => {
28
31
  this.LOGGER.error(e, 'request failed to execute');
29
32
  (0, responses_1.internalServerError)(response, e.body);
30
- }), (e) => e === exports.UNAUTHORIZED ? (0, responses_1.writeResponse)(response, 401, '') : (0, responses_1.internalServerError)(response, e)));
33
+ }), (e) => e === UNAUTHORIZED ? (0, responses_1.writeResponse)(response, 401, '') : (0, responses_1.internalServerError)(response, e)));
31
34
  this.fallback = async (request, _, response) => Promise.resolve(this.LOGGER.warn(`FALLBACK: ${request.method} to ${request.url}`)).then(() => (0, responses_1.writeResponse)(response, 404, `no route found to handle ${request.method} to ${request.url}`));
32
35
  this.resolveRoute = (request, body, response) => (this.routes.filter((r) => r.method.test(request.method))
33
36
  .filter((r) => r.path.test(request.url))
@@ -11,7 +11,9 @@ export type LambdaPayloadVersion<AuthorizerContext, Event, HandlerResponse> = {
11
11
  export declare const v1: <AuthorizerContext>() => LambdaPayloadVersion<AuthorizerContext, APIGatewayProxyEvent, APIGatewayProxyResult>;
12
12
  export declare const v2: <AuthorizerContext>() => LambdaPayloadVersion<AuthorizerContext, APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2>;
13
13
  interface LambdaMapper<AuthorizerContext, Event, HandlerResponse> {
14
- event(context: AuthorizerContext): Event;
14
+ event(context: {
15
+ [name: string]: any;
16
+ } & AuthorizerContext): Event;
15
17
  authorization(): string;
16
18
  toResponse: (response: HandlerResponse) => Response;
17
19
  }
package/jest.config.js CHANGED
@@ -2,6 +2,6 @@
2
2
  module.exports = {
3
3
  preset: "ts-jest",
4
4
  testPathIgnorePatterns: ["/dist/"],
5
- testRegex: "/__tests__/.*$",
5
+ testRegex: "/__tests__/.*\.test\..*$",
6
6
  testTimeout: 60000,
7
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yopdev/dev-server",
3
- "version": "3.0.2",
3
+ "version": "3.0.3-alpha.2",
4
4
  "scripts": {
5
5
  "compile": "tsc",
6
6
  "pretest": "npm run compile",
@@ -4,11 +4,13 @@ import { HttpSettings } from './http-server';
4
4
  import { Authorizer, Route, newLambdaHttpProxy } from './lambda-http-proxy';
5
5
  import { Callback, Service, Startable } from './services';
6
6
  import { LambdaMapperFactory, LambdaPayloadVersion, PathParameterResolver } from './mappers';
7
+ import { APIGatewayAuthorizerResult } from 'aws-lambda';
7
8
 
8
9
  const PATH_VARIABLE_CAPTURE = /{(.*?)}/g;
9
10
  const QUERY_STRING_OR_LOCATION_REG_EXP = '(?:([?#].*))?';
10
11
  const PROXY_PATH_PARAM = 'proxy';
11
- export const newLambdaProxyFromCloudFormationTemplate = <Context, AuthorizerContext, Event, HandlerResponse>(
12
+ const NOOP_AUTHORIZER = async () => undefined
13
+ export const newLambdaProxyFromCloudFormationTemplate = <Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse>(
12
14
  name: string,
13
15
  settings: HttpSettings,
14
16
  payloadVersion: LambdaPayloadVersion<AuthorizerContext, Event, HandlerResponse>,
@@ -16,10 +18,13 @@ export const newLambdaProxyFromCloudFormationTemplate = <Context, AuthorizerCont
16
18
  callback: Callback<string>,
17
19
  ): Startable<Service<string>> => new CloudFormationLambdaProxy(name, settings, config.extraRoutes, payloadVersion, config, callback);
18
20
 
19
- class CloudFormationLambdaProxy<Context, AuthorizerContext, Event, HandlerResponse> extends CloudFormationSetup<
21
+ class CloudFormationLambdaProxy<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> extends CloudFormationSetup<
20
22
  {
21
23
  Path: string;
22
24
  Method: string;
25
+ Auth: {
26
+ Authorizer: string;
27
+ }
23
28
  },
24
29
  (event: Event) => Promise<HandlerResponse>,
25
30
  Route<Context, AuthorizerContext, Event, HandlerResponse>,
@@ -47,7 +52,8 @@ class CloudFormationLambdaProxy<Context, AuthorizerContext, Event, HandlerRespon
47
52
  path: new RegExp(`^${pathWithCapturingGroups}${QUERY_STRING_OR_LOCATION_REG_EXP}?$`),
48
53
  weight: this.computeWeight(pathWithCapturingGroups),
49
54
  handler: this.pathParameterCapture(new RegExp(pathWithCapturingGroups), handler, config.context),
50
- }),
55
+ authorizer: event.Properties?.Auth?.Authorizer === 'NONE' ? NOOP_AUTHORIZER : undefined,
56
+ } satisfies Route<Context, AuthorizerContext, Event, HandlerResponse>),
51
57
  ),
52
58
  config.prepare,
53
59
  config,
@@ -63,7 +69,7 @@ class CloudFormationLambdaProxy<Context, AuthorizerContext, Event, HandlerRespon
63
69
  {
64
70
  settings: this.settings,
65
71
  routes: this.extraRoutes.concat(routes),
66
- authorizer: (this.authorizer || (() => undefined))(cfg),
72
+ authorizer: (this.authorizer || (() => NOOP_AUTHORIZER))(cfg),
67
73
  mapper: this.mapper,
68
74
  },
69
75
  this.callback,
@@ -117,7 +123,7 @@ class CloudFormationLambdaProxy<Context, AuthorizerContext, Event, HandlerRespon
117
123
  }
118
124
  }
119
125
 
120
- type CloudFormationLambdaProxyConfig<Context, AuthorizerContext, Event, HandlerResponse> = {
126
+ type CloudFormationLambdaProxyConfig<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> = {
121
127
  authorizer?: (config: DevServerConfig) => Authorizer<AuthorizerContext> | undefined,
122
128
  extraRoutes: Route<Context, AuthorizerContext, Event, HandlerResponse>[],
123
129
  prepare?: (config: DevServerConfig) => Promise<void>,
@@ -24,7 +24,7 @@ export abstract class CloudFormationSetup<P, H, R, F> implements Startable<F> {
24
24
  this.handlerNameResolver = config.handlerNameResolver;
25
25
  }
26
26
  start = async (config: DevServerConfig) =>
27
- (this.beforeStart || Promise.resolve)(config)
27
+ (this.beforeStart || (async () => undefined))(config)
28
28
  .then(this.resolvedHandlers)
29
29
  .then(async (routes) => this.afterStart(this.name, config, routes));
30
30
 
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { DevServer } from './dev-server'
2
2
  export { newHttpServer as httpServer, HttpSettings } from './http-server'
3
- export { newLambdaHttpProxy as lambdaHttpProxy, UNAUTHORIZED } from './lambda-http-proxy'
3
+ export { newLambdaHttpProxy as lambdaHttpProxy } from './lambda-http-proxy'
4
4
  export { v1 as v1LambdaProxyPayload, v2 as v2LambdaProxyPayload } from './mappers'
5
5
  export { newLambdaProxyFromCloudFormationTemplate as cloudFormationLambdaProxy } from './cloudformation-lambda-http-proxy'
6
6
  export { newDynamoDbTableFromCloudFormationTemplate as cloudFormationDynamoDbTable } from './cloudformation-dynamodb-table'
@@ -4,9 +4,10 @@ import { IncomingMessage, ServerResponse } from "http";
4
4
  import { LambdaMapperFactory } from "./mappers";
5
5
  import { internalServerError, writeResponse } from "./responses";
6
6
  import { Lifecycle, Service, Callback } from "./services";
7
+ import { APIGatewayAuthorizerResult } from "aws-lambda";
7
8
 
8
- export const UNAUTHORIZED = new Error('UNAUTHORIZED');
9
- export const newLambdaHttpProxy = <Context, AuthorizerContext, Event, HandlerResponse>(
9
+ const UNAUTHORIZED = new Error('UNAUTHORIZED');
10
+ export const newLambdaHttpProxy = <Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse>(
10
11
  name: string,
11
12
  config: {
12
13
  settings: HttpSettings,
@@ -25,7 +26,7 @@ export const newLambdaHttpProxy = <Context, AuthorizerContext, Event, HandlerRes
25
26
  config.context,
26
27
  ), callback)
27
28
 
28
- class LambdaHttpProxy<Context, AuthorizerContext, Event, HandlerResponse> implements Lifecycle<string> {
29
+ class LambdaHttpProxy<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> implements Lifecycle<string> {
29
30
  private LOGGER: Logger
30
31
  private readonly server: HttpServer
31
32
 
@@ -55,6 +56,11 @@ class LambdaHttpProxy<Context, AuthorizerContext, Event, HandlerResponse> implem
55
56
  .then(async (mapper) =>
56
57
  authorizer(mapper.authorization())
57
58
  .then(async (context) =>
59
+ context?.policyDocument?.Statement?.find((s) => s.Effect === 'Deny') === undefined
60
+ ? { ...context, lambda: context?.context }
61
+ : Promise.reject(UNAUTHORIZED),
62
+ )
63
+ .then((context) =>
58
64
  lambdaHandler(mapper.event(context), this.context())
59
65
  .then((lambda) => {
60
66
  const result = mapper.toResponse(lambda)
@@ -100,7 +106,7 @@ export abstract class Response {
100
106
  body: () => string | Buffer | undefined
101
107
  }
102
108
 
103
- export type Route<Context, AuthorizerContext, Event, HandlerResponse> = {
109
+ export type Route<Context, AuthorizerContext extends APIGatewayAuthorizerResult, Event, HandlerResponse> = {
104
110
  method: RegExp;
105
111
  path: RegExp;
106
112
  weight: number;
@@ -108,4 +114,4 @@ export type Route<Context, AuthorizerContext, Event, HandlerResponse> = {
108
114
  handler: (event: Event, context: Context) => Promise<HandlerResponse>;
109
115
  };
110
116
 
111
- export type Authorizer<Context> = (authorization: string) => Promise<Context | undefined>;
117
+ export type Authorizer<Context extends APIGatewayAuthorizerResult> = (authorization: string) => Promise<Context | undefined>;
package/src/mappers.ts CHANGED
@@ -120,7 +120,7 @@ class DefaultLambdaMapper<AuthorizerContext> implements LambdaMapper<AuthorizerC
120
120
  return [];
121
121
  };
122
122
 
123
- event = (context: AuthorizerContext): APIGatewayProxyEvent & APIGatewayEventRequestContextV2WithGenericAuthorizer<AuthorizerContext> => ({
123
+ event = (context: { [name: string]: any; } & AuthorizerContext): APIGatewayProxyEvent & APIGatewayEventRequestContextV2WithGenericAuthorizer<AuthorizerContext> => ({
124
124
  version: '2.0',
125
125
  rawPath: this.url.pathname,
126
126
  rawQueryString: this.url.search,
@@ -190,7 +190,7 @@ class DefaultLambdaMapper<AuthorizerContext> implements LambdaMapper<AuthorizerC
190
190
  authorization = () => this.authorizationHeaderValue
191
191
  }
192
192
  interface LambdaMapper<AuthorizerContext, Event, HandlerResponse> {
193
- event(context: AuthorizerContext): Event
193
+ event(context: { [name: string]: any; } & AuthorizerContext): Event
194
194
  authorization(): string
195
195
  toResponse: (response: HandlerResponse) => Response
196
196
  }
package/tsconfig.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "esModuleInterop": true,
3
4
  "module": "NodeNext",
4
5
  "target": "ES2021",
5
6
  "moduleResolution": "NodeNext",