diff --git a/.changeset/honest-games-bathe.md b/.changeset/honest-games-bathe.md new file mode 100644 index 000000000000..928bf0662abe --- /dev/null +++ b/.changeset/honest-games-bathe.md @@ -0,0 +1,5 @@ +--- +"@astrojs/vercel": minor +--- + +Implements the vercel skew protection diff --git a/packages/integrations/vercel/src/serverless/adapter.ts b/packages/integrations/vercel/src/serverless/adapter.ts index 185d3df465bb..1090555cc350 100644 --- a/packages/integrations/vercel/src/serverless/adapter.ts +++ b/packages/integrations/vercel/src/serverless/adapter.ts @@ -72,16 +72,18 @@ function getAdapter({ edgeMiddleware, functionPerRoute, middlewareSecret, + skewProtection, }: { edgeMiddleware: boolean; functionPerRoute: boolean; middlewareSecret: string; + skewProtection: boolean; }): AstroAdapter { return { name: PACKAGE_NAME, serverEntrypoint: `${PACKAGE_NAME}/entrypoint`, exports: ['default'], - args: { middlewareSecret }, + args: { middlewareSecret, skewProtection }, adapterFeatures: { edgeMiddleware, functionPerRoute, @@ -139,6 +141,10 @@ export interface VercelServerlessConfig { /** Whether to cache on-demand rendered pages in the same way as static files. */ isr?: boolean | VercelISRConfig; + /** + * It enables Vercel skew protection: https://vercel.com/docs/deployments/skew-protection + */ + skewProtection?: boolean; } interface VercelISRConfig { @@ -180,6 +186,7 @@ export default function vercelServerless({ edgeMiddleware = false, maxDuration, isr = false, + skewProtection = false, }: VercelServerlessConfig = {}): AstroIntegration { if (maxDuration) { if (typeof maxDuration !== 'number') { @@ -277,7 +284,9 @@ export default function vercelServerless({ ); } - setAdapter(getAdapter({ functionPerRoute, edgeMiddleware, middlewareSecret })); + setAdapter( + getAdapter({ functionPerRoute, edgeMiddleware, middlewareSecret, skewProtection }) + ); _config = config; _buildTempFolder = config.build.server; diff --git a/packages/integrations/vercel/src/serverless/entrypoint.ts b/packages/integrations/vercel/src/serverless/entrypoint.ts index 67047a7ed85e..a8e8ebc41c9f 100644 --- a/packages/integrations/vercel/src/serverless/entrypoint.ts +++ b/packages/integrations/vercel/src/serverless/entrypoint.ts @@ -12,7 +12,7 @@ applyPolyfills(); export const createExports = ( manifest: SSRManifest, - { middlewareSecret }: { middlewareSecret: string } + { middlewareSecret, skewProtection }: { middlewareSecret: string; skewProtection: boolean } ) => { const app = new NodeApp(manifest); const handler = async (req: IncomingMessage, res: ServerResponse) => { @@ -38,6 +38,11 @@ export const createExports = ( // hide the secret from the rest of user code delete req.headers[ASTRO_MIDDLEWARE_SECRET_HEADER]; + // https://vercel.com/docs/deployments/skew-protection#supported-frameworks + if (skewProtection && process.env.VERCEL_SKEW_PROTECTION_ENABLED === '1') { + req.headers['x-deployment-id'] = process.env.VERCEL_DEPLOYMENT_ID; + } + const webResponse = await app.render(req, { addCookieHeader: true, clientAddress, locals }); await NodeApp.writeResponse(webResponse, res); }; diff --git a/packages/integrations/vercel/test/edge-middleware.test.js.snap b/packages/integrations/vercel/test/edge-middleware.test.js.snap deleted file mode 100644 index 754aebc8fb60..000000000000 --- a/packages/integrations/vercel/test/edge-middleware.test.js.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Middleware with handler file 1`] = ` -"// test/fixtures/middleware-with-edge-file/src/vercel-edge-middleware.js -function vercel_edge_middleware_default({ request, context }) { - return { - title: \\"Hello world\\" - }; -} - -// test/fixtures/middleware-with-edge-file/dist/middleware2.mjs -var onRequest = async (context, next) => { - const response = await next(); - return response; -}; - -// -import { createContext, trySerializeLocals } from \\"astro/middleware\\"; -async function middleware(request, context) { - const url = new URL(request.url); - const ctx = createContext({ - request, - params: {} - }); - ctx.locals = vercel_edge_middleware_default({ request, context }); - const next = async () => { - const response = await fetch(url, { - headers: { - \\"x-astro-locals\\": trySerializeLocals(ctx.locals) - } - }); - return response; - }; - return onRequest(ctx, next); -} -export { - middleware as default -}; -" -`; - -exports[`Middleware without handler file 1`] = ` -"// test/fixtures/middleware-without-edge-file/dist/middleware2.mjs -var onRequest = async (context, next) => { - const response = await next(); - return response; -}; - -// -import { createContext, trySerializeLocals } from \\"astro/middleware\\"; -async function middleware(request, context) { - const url = new URL(request.url); - const ctx = createContext({ - request, - params: {} - }); - ctx.locals = {}; - const next = async () => { - const response = await fetch(url, { - headers: { - \\"x-astro-locals\\": trySerializeLocals(ctx.locals) - } - }); - return response; - }; - return onRequest(ctx, next); -} -export { - middleware as default -}; -" -`;