diff --git a/README.md b/README.md index 8f3605bb..637da686 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Construct | Description -- | -- [basic-auth](packages/basic-auth) | [cloudfront-security-headers](packages/cloudfront-security-headers) | +[feature-env-handlers](packages/feature-env-handlers) | Lambda@Edge handlers to support feature environments [geoip-redirect](packages/geoip-redirect) | [graphql-server](packages/graphql-server) | [lambda-at-edge-handlers](packages/lambda-at-edge-handlers) | diff --git a/package-lock.json b/package-lock.json index 527cab4f..90ec1e5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,10 @@ "resolved": "packages/esbuild", "link": true }, + "node_modules/@aligent/cdk-feature-env-handlers": { + "resolved": "packages/feature-env-handlers", + "link": true + }, "node_modules/@aligent/cdk-geoip-redirect": { "resolved": "packages/geoip-redirect", "link": true @@ -9774,6 +9778,16 @@ "typescript": "~5.3.2" } }, + "packages/feature-env-handlers": { + "version": "0.1.0", + "license": "GPL-3.0-only", + "dependencies": { + "source-map-support": "^0.5.21" + }, + "devDependencies": { + "@types/aws-lambda": "^8.10.122" + } + }, "packages/geoip-redirect": { "name": "@aligent/cdk-geoip-redirect", "version": "0.1.0", diff --git a/packages/feature-env-handlers/README.md b/packages/feature-env-handlers/README.md new file mode 100644 index 00000000..25ab9ae4 --- /dev/null +++ b/packages/feature-env-handlers/README.md @@ -0,0 +1,11 @@ +# Feature Environment Lambda@Edge Handlers + +A collection of Lambda@Edge functions to handle routing in feature environments. + +Feature environments are temporary environments that are created on a pull request. They function almost the same as regular statically hosted environments, however, there are multiple frontends hosted in the same bucket. Therefore, we require a method to route to the correct sub-path depending on which subdomain the user has visited. + +# Common issues + +Error config must be **disabled** for these functions to work. See the below diagram for the reasoning. + +![error config diagram](docs/error_config.png) diff --git a/packages/feature-env-handlers/docs/error_config.png b/packages/feature-env-handlers/docs/error_config.png new file mode 100644 index 00000000..f1e2d6b3 Binary files /dev/null and b/packages/feature-env-handlers/docs/error_config.png differ diff --git a/packages/feature-env-handlers/index.ts b/packages/feature-env-handlers/index.ts new file mode 100644 index 00000000..05f50778 --- /dev/null +++ b/packages/feature-env-handlers/index.ts @@ -0,0 +1,4 @@ +import { handler as OriginRequest } from "./lib/origin-request"; +import { handler as ViwerRequest } from "./lib/viewer-request"; + +export { OriginRequest, ViwerRequest }; diff --git a/packages/feature-env-handlers/lib/origin-request.ts b/packages/feature-env-handlers/lib/origin-request.ts new file mode 100644 index 00000000..cb588ad5 --- /dev/null +++ b/packages/feature-env-handlers/lib/origin-request.ts @@ -0,0 +1,26 @@ +import { CloudFrontRequestEvent, CloudFrontRequest } from "aws-lambda"; +import "source-map-support/register"; + +const RETURN_INDEX = /(^.\w*$)|(\/$)|(\.html$)/i; + +export const handler = async ( + event: CloudFrontRequestEvent +): Promise => { + const { request } = event.Records[0].cf; + + const host = request.headers["x-forwarded-host"][0].value; + const matches = host.match(/^([^.]+)/); + + if (matches) { + const branch = matches.pop(); + if (request.origin?.s3) { + request.origin.s3.path = `/${branch}`; + } + } + + if (RETURN_INDEX.test(request.uri)) { + request.uri = "/index.html"; + } + + return request; +}; diff --git a/packages/feature-env-handlers/lib/viewer-request.ts b/packages/feature-env-handlers/lib/viewer-request.ts new file mode 100644 index 00000000..74dcfdb2 --- /dev/null +++ b/packages/feature-env-handlers/lib/viewer-request.ts @@ -0,0 +1,17 @@ +import { CloudFrontRequest, CloudFrontRequestEvent } from "aws-lambda"; +import "source-map-support/register"; + +export const handler = async ( + event: CloudFrontRequestEvent +): Promise => { + const { request } = event.Records[0].cf; + + request.headers["x-forwarded-host"] = [ + { + value: request.headers.host[0].value, + key: "X-Forwarded-Host", + }, + ]; + + return request; +}; diff --git a/packages/feature-env-handlers/package-lock.json b/packages/feature-env-handlers/package-lock.json new file mode 100644 index 00000000..6d78e486 --- /dev/null +++ b/packages/feature-env-handlers/package-lock.json @@ -0,0 +1,85 @@ +{ + "name": "@aligent/cdk-lambda-at-edge-handlers", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@aligent/cdk-lambda-at-edge-handlers", + "version": "0.1.0", + "license": "GPL-3.0-only", + "dependencies": { + "@types/aws-lambda": "^8.10.77", + "esbuild": "0.12.15", + "source-map-support": "^0.5.16" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.109", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.109.tgz", + "integrity": "sha512-/ME92FneNyXQzrAfcnQQlW1XkCZGPDlpi2ao1MJwecN+6SbeonKeggU8eybv1DfKli90FAVT1MlIZVXfwVuCyg==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/esbuild": { + "version": "0.12.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz", + "integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + }, + "dependencies": { + "@types/aws-lambda": { + "version": "8.10.109", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.109.tgz", + "integrity": "sha512-/ME92FneNyXQzrAfcnQQlW1XkCZGPDlpi2ao1MJwecN+6SbeonKeggU8eybv1DfKli90FAVT1MlIZVXfwVuCyg==" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "esbuild": { + "version": "0.12.15", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.12.15.tgz", + "integrity": "sha512-72V4JNd2+48eOVCXx49xoSWHgC3/cCy96e7mbXKY+WOWghN00cCmlGnwVLRhRHorvv0dgCyuMYBZlM2xDM5OQw==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } +} diff --git a/packages/feature-env-handlers/package.json b/packages/feature-env-handlers/package.json new file mode 100644 index 00000000..f25f7b26 --- /dev/null +++ b/packages/feature-env-handlers/package.json @@ -0,0 +1,26 @@ +{ + "name": "@aligent/cdk-feature-env-handlers", + "version": "0.1.0", + "description": "Cloudfront Lambda@Edge handlers to allow feature environments to function", + "main": "index.js", + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "echo \"No test specified\" && exit 0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/aligent/aws-cdk-constructs.git" + }, + "license": "GPL-3.0-only", + "bugs": { + "url": "https://github.com/aligent/cdk-constructs/issues" + }, + "homepage": "https://github.com/aligent/cdk-constructs#readme", + "devDependencies": { + "@types/aws-lambda": "^8.10.122" + }, + "dependencies": { + "source-map-support": "^0.5.21" + } +} diff --git a/packages/feature-env-handlers/tsconfig.json b/packages/feature-env-handlers/tsconfig.json new file mode 100644 index 00000000..4082f16a --- /dev/null +++ b/packages/feature-env-handlers/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +}