diff --git a/packages/lambda-tiler/src/__tests__/index.test.ts b/packages/lambda-tiler/src/__tests__/index.test.ts index a61268a60..617a6dc80 100644 --- a/packages/lambda-tiler/src/__tests__/index.test.ts +++ b/packages/lambda-tiler/src/__tests__/index.test.ts @@ -3,7 +3,7 @@ import { afterEach, describe, it } from 'node:test'; import { ConfigProviderMemory } from '@basemaps/config'; -import { handler } from '../index.js'; +import { getClientIp, handler } from '../index.js'; import { ConfigLoader } from '../util/config.loader.js'; import { mockRequest } from './xyz.util.js'; @@ -13,6 +13,29 @@ describe('LambdaXyz index', () => { assert.equal(typeof foo.handler, 'function'); }); + describe('getClientIp', () => { + const ip = (addr?: string): string | undefined => { + return getClientIp(mockRequest('/v1/version', 'get', addr ? { 'cloudfront-viewer-address': addr } : {})); + }; + + it('should strip the port from ipv4', () => { + assert.equal(ip('203.0.113.10:46532'), '203.0.113.10'); + }); + + it('should strip the port from ipv6 and keep brackets', () => { + assert.equal(ip('[2001:db8::1]:46532'), '[2001:db8::1]'); + }); + + it('should handle missing ports', () => { + assert.equal(ip('203.0.113.10'), '203.0.113.10'); + assert.equal(ip('[2001:db8::1]'), '[2001:db8::1]'); + }); + + it('should return undefined when header is missing', () => { + assert.equal(ip(), undefined); + }); + }); + describe('version', () => { afterEach(() => { delete process.env['GIT_VERSION']; diff --git a/packages/lambda-tiler/src/index.ts b/packages/lambda-tiler/src/index.ts index ae5c12062..8fd37303d 100644 --- a/packages/lambda-tiler/src/index.ts +++ b/packages/lambda-tiler/src/index.ts @@ -42,6 +42,21 @@ function randomTrace(req: LambdaHttpRequest): void { else req.log.level = 'info'; } +/** + * Get the viewer's IP address from the CloudFront-Viewer-Address header, removing the port + * + * @example + * "203.0.113.10:46532" => "203.0.113.10" + * "[2001:db8::1]:46532" => "[2001:db8::1]" + */ +export function getClientIp(req: LambdaHttpRequest): string | undefined { + const addr = req.header('cloudfront-viewer-address'); + if (addr == null) return undefined; + const portIndex = addr.lastIndexOf(':'); + if (portIndex === -1 || addr.endsWith(']')) return addr; + return addr.slice(0, portIndex); +} + handler.router.hook('request', (req) => { LogStorage.enterWith({ log: req.log }); FsaLog.reset(); @@ -50,6 +65,9 @@ handler.router.hook('request', (req) => { req.set('name', 'LambdaTiler'); + const clientIp = getClientIp(req); + if (clientIp) req.set('clientIp', clientIp); + req.log.debug({ ...req.logContext }, 'Lambda:Start'); });