diff --git a/.changeset/slimy-kids-peel.md b/.changeset/slimy-kids-peel.md new file mode 100644 index 000000000000..706d01d47ccc --- /dev/null +++ b/.changeset/slimy-kids-peel.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where the edge middleware couldn't correctly compute the client IP address when calling `ctx.clientAddress()` diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts index 2ca802126678..a684d23c0174 100644 --- a/packages/astro/src/core/middleware/index.ts +++ b/packages/astro/src/core/middleware/index.ts @@ -8,6 +8,7 @@ import { import { ASTRO_VERSION, clientAddressSymbol, clientLocalsSymbol } from '../constants.js'; import { AstroCookies } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; +import { getClientIpAddress } from '../routing/request.js'; import { sequence } from './sequence.js'; function defineMiddleware(fn: MiddlewareHandler) { @@ -50,6 +51,7 @@ function createContext({ let preferredLocale: string | undefined = undefined; let preferredLocaleList: string[] | undefined = undefined; let currentLocale: string | undefined = undefined; + let clientIpAddress: string | undefined; const url = new URL(request.url); const route = url.pathname; @@ -85,10 +87,14 @@ function createContext({ }, url, get clientAddress() { - if (clientAddressSymbol in request) { - return Reflect.get(request, clientAddressSymbol) as string; + if (clientIpAddress) { + return clientIpAddress; } - throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable); + clientIpAddress = getClientIpAddress(request); + if (!clientIpAddress) { + throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable); + } + return clientIpAddress; }, get locals() { let locals = Reflect.get(request, clientLocalsSymbol); diff --git a/packages/astro/src/core/routing/request.ts b/packages/astro/src/core/routing/request.ts new file mode 100644 index 000000000000..f7e917a53cde --- /dev/null +++ b/packages/astro/src/core/routing/request.ts @@ -0,0 +1,20 @@ +/** + * Utilities for extracting information from `Request` + */ + +// Parses multiple header and returns first value if available. +export function getFirstForwardedValue(multiValueHeader?: string | string[] | null) { + return multiValueHeader + ?.toString() + ?.split(',') + .map((e) => e.trim())?.[0]; +} + +/** + * Returns the first value associated to the `x-forwarded-for` header. + * + * @param {Request} request + */ +export function getClientIpAddress(request: Request): string | undefined { + return getFirstForwardedValue(request.headers.get('x-forwarded-for')); +} diff --git a/packages/astro/test/units/routing/api-context.test.js b/packages/astro/test/units/routing/api-context.test.js new file mode 100644 index 000000000000..1e955e50cc46 --- /dev/null +++ b/packages/astro/test/units/routing/api-context.test.js @@ -0,0 +1,19 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { createContext } from '../../../dist/core/middleware/index.js'; + +describe('createAPIContext', () => { + it('should return the clientAddress', () => { + const request = new Request('http://example.com', { + headers: { + 'x-forwarded-for': '192.0.2.43, 172.16.58.3', + }, + }); + + const context = createContext({ + request, + }); + + assert.equal(context.clientAddress, '192.0.2.43'); + }); +});