Skip to content

Commit

Permalink
feat(netlify): verification for edge middleware (#152)
Browse files Browse the repository at this point in the history
* feat(netlify): verification for edge middleware

* add changeset

* remove unused folder
  • Loading branch information
lilnasy authored Feb 6, 2024
1 parent 7a5c3b0 commit 816bdc4
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 4 deletions.
7 changes: 7 additions & 0 deletions .changeset/odd-shoes-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astrojs/netlify': minor
---

Implements verification for edge middleware. This is a security measure to ensure that your serverless functions are only ever called by your edge middleware and not by a third party.

When `edgeMiddleware` is enabled, the serverless function will now respond with `403 Forbidden` for requests that are not verified to have come from the generated edge middleware. No user action is necessary.
9 changes: 8 additions & 1 deletion packages/netlify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { AstroConfig, AstroIntegration, RouteData } from 'astro';
import { AstroError } from 'astro/errors';
import { build } from 'esbuild';
import { appendFile, mkdir, readFile, rm, writeFile } from 'fs/promises';
import type { Args } from "./ssr-function.js"

const { version: packageVersion } = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8')
Expand Down Expand Up @@ -75,6 +76,8 @@ export default function netlifyIntegration(
let outDir: URL;
let rootDir: URL;
let astroMiddlewareEntryPoint: URL | undefined = undefined;
// Secret used to verify that the caller is the astro-generated edge middleware and not a third-party
const middlewareSecret = crypto.randomUUID();

const ssrOutputDir = () => new URL('./.netlify/functions-internal/ssr/', rootDir);
const middlewareOutputDir = () => new URL('.netlify/edge-functions/middleware/', rootDir);
Expand Down Expand Up @@ -136,6 +139,7 @@ export default function netlifyIntegration(
const next = () => {
const { netlify, ...otherLocals } = ctx.locals;
request.headers.set("x-astro-locals", trySerializeLocals(otherLocals));
request.headers.set("x-astro-middleware-secret", "${middlewareSecret}");
return context.next();
};
Expand Down Expand Up @@ -270,15 +274,18 @@ export default function netlifyIntegration(
'See https://github.com/withastro/adapters/tree/main/packages/netlify#image-cdn for more.'
);
}

const edgeMiddleware = integrationConfig?.edgeMiddleware ?? false;

setAdapter({
name: '@astrojs/netlify',
serverEntrypoint: '@astrojs/netlify/ssr-function.js',
exports: ['default'],
adapterFeatures: {
functionPerRoute: false,
edgeMiddleware: integrationConfig?.edgeMiddleware ?? false,
edgeMiddleware,
},
args: { middlewareSecret } satisfies Args,
supportedAstroFeatures: {
hybridOutput: 'stable',
staticOutput: 'stable',
Expand Down
12 changes: 10 additions & 2 deletions packages/netlify/src/ssr-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { applyPolyfills } from 'astro/app/node';
applyPolyfills();

// biome-ignore lint/complexity/noBannedTypes: safe to use in this case
export type Args = {};
export interface Args {
middlewareSecret: string;
};

const clientAddressSymbol = Symbol.for('astro.clientAddress');

export const createExports = (manifest: SSRManifest, _args: Args) => {
export const createExports = (manifest: SSRManifest, { middlewareSecret }: Args) => {
const app = new App(manifest);

function createHandler(integrationConfig: {
Expand All @@ -27,7 +29,13 @@ export const createExports = (manifest: SSRManifest, _args: Args) => {
let locals: Record<string, unknown> = {};

const astroLocalsHeader = request.headers.get('x-astro-locals');
const middlewareSecretHeader = request.headers.get('x-astro-middleware-secret');
if (astroLocalsHeader) {
if (middlewareSecretHeader !== middlewareSecret) {
return new Response("Forbidden", { status: 403 })
}
// hide the secret from the rest of user and library code
request.headers.delete('x-astro-middleware-secret');
locals = JSON.parse(astroLocalsHeader);
}

Expand Down

This file was deleted.

0 comments on commit 816bdc4

Please sign in to comment.