From ef018fc977cf7410d0dea354a0e6dc706c6c1526 Mon Sep 17 00:00:00 2001 From: Daniel ABIB Date: Fri, 29 Aug 2025 09:48:06 -0300 Subject: [PATCH 1/3] feat(parser): add IPv6 support for sourceIp in API Gateway schemas - Updated api-gateway.ts to support both IPv4 and IPv6 addresses - Updated api-gatewayv2.ts to support both IPv4 and IPv6 addresses - Updated appsync-shared.ts to support both IPv4 and IPv6 addresses - Added comprehensive tests for IPv6 support in all affected schemas Closes #4348 --- packages/parser/src/schemas/api-gateway.ts | 4 ++- packages/parser/src/schemas/api-gatewayv2.ts | 2 +- packages/parser/src/schemas/appsync-shared.ts | 2 +- .../parser/tests/unit/schema/apigw.test.ts | 34 ++++++++++++++++++ .../parser/tests/unit/schema/apigwv2.test.ts | 35 +++++++++++++++++++ .../parser/tests/unit/schema/appsync.test.ts | 34 ++++++++++++++++++ 6 files changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/parser/src/schemas/api-gateway.ts b/packages/parser/src/schemas/api-gateway.ts index 9baa7c322e..98cbe70ae1 100644 --- a/packages/parser/src/schemas/api-gateway.ts +++ b/packages/parser/src/schemas/api-gateway.ts @@ -30,7 +30,9 @@ const APIGatewayEventIdentity = z.object({ * * See aws-powertools/powertools-lambda-python#1562 for more information. */ - sourceIp: z.union([z.ipv4(), z.literal('test-invoke-source-ip')]).optional(), + sourceIp: z + .union([z.ipv4(), z.ipv6(), z.literal('test-invoke-source-ip')]) + .optional(), user: z.string().nullish(), userAgent: z.string().nullish(), userArn: z.string().nullish(), diff --git a/packages/parser/src/schemas/api-gatewayv2.ts b/packages/parser/src/schemas/api-gatewayv2.ts index bff5fdfdd6..5bfb0c3a59 100644 --- a/packages/parser/src/schemas/api-gatewayv2.ts +++ b/packages/parser/src/schemas/api-gatewayv2.ts @@ -110,7 +110,7 @@ const APIGatewayRequestContextV2Schema = z.object({ method: APIGatewayHttpMethod, path: z.string(), protocol: z.string(), - sourceIp: z.ipv4(), + sourceIp: z.union([z.ipv4(), z.ipv6()]), userAgent: z.string(), }), requestId: z.string(), diff --git a/packages/parser/src/schemas/appsync-shared.ts b/packages/parser/src/schemas/appsync-shared.ts index 7e7278bf49..548a27a6a9 100644 --- a/packages/parser/src/schemas/appsync-shared.ts +++ b/packages/parser/src/schemas/appsync-shared.ts @@ -16,7 +16,7 @@ const AppSyncCognitoIdentity = z.object({ issuer: z.string(), username: z.string(), claims: z.record(z.string(), z.unknown()), - sourceIp: z.array(z.ipv4()), + sourceIp: z.array(z.union([z.ipv4(), z.ipv6()])), defaultAuthStrategy: z.string().nullable(), groups: z.array(z.string()).nullable(), }); diff --git a/packages/parser/tests/unit/schema/apigw.test.ts b/packages/parser/tests/unit/schema/apigw.test.ts index 62f92aabec..afd78c0d1a 100644 --- a/packages/parser/tests/unit/schema/apigw.test.ts +++ b/packages/parser/tests/unit/schema/apigw.test.ts @@ -98,6 +98,40 @@ describe('Schema: API Gateway REST', () => { // Assess expect(parsedEvent).toEqual(event); }); + it('parses an event with IPv6 sourceIp', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'no-auth', + }) as any; + // Add IPv6 address to the event + event.requestContext.identity.sourceIp = + '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent.requestContext.identity.sourceIp).toEqual( + '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + ); + }); + + it('parses an event with shortened IPv6 sourceIp', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'no-auth', + }) as any; + // Add shortened IPv6 address to the event + event.requestContext.identity.sourceIp = '::1'; + + // Act + const parsedEvent = APIGatewayProxyEventSchema.parse(event); + + // Assess + expect(parsedEvent.requestContext.identity.sourceIp).toEqual('::1'); + }); }); describe('APIGatewayRequestAuthorizerEventSchema', () => { diff --git a/packages/parser/tests/unit/schema/apigwv2.test.ts b/packages/parser/tests/unit/schema/apigwv2.test.ts index a205470143..d6bee31ceb 100644 --- a/packages/parser/tests/unit/schema/apigwv2.test.ts +++ b/packages/parser/tests/unit/schema/apigwv2.test.ts @@ -45,6 +45,41 @@ describe('Schema: API Gateway HTTP (v2)', () => { expect(parsedEvent).toEqual(event); }); + it('parses an event with IPv6 sourceIp', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'no-auth', + }) as any; + // Add IPv6 address to the event + event.requestContext.http.sourceIp = + '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; + + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent.requestContext.http.sourceIp).toEqual( + '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + ); + }); + + it('parses an event with shortened IPv6 sourceIp', () => { + // Prepare + const event = getTestEvent({ + eventsPath, + filename: 'no-auth', + }) as any; + // Add shortened IPv6 address to the event + event.requestContext.http.sourceIp = '::1'; + + // Act + const parsedEvent = APIGatewayProxyEventV2Schema.parse(event); + + // Assess + expect(parsedEvent.requestContext.http.sourceIp).toEqual('::1'); + }); + it('parses an event with a JWT authorizer', () => { // Prepare const event = getTestEvent({ diff --git a/packages/parser/tests/unit/schema/appsync.test.ts b/packages/parser/tests/unit/schema/appsync.test.ts index 1b12d2bf2c..df849ae06f 100644 --- a/packages/parser/tests/unit/schema/appsync.test.ts +++ b/packages/parser/tests/unit/schema/appsync.test.ts @@ -112,6 +112,40 @@ describe('Schema: AppSync Resolver', () => { }, }, }, + { + name: 'cognito identity with IPv6 sourceIp', + event: { + ...appSyncResolverEvent, + identity: { + claims: { + sub: '192879fc-a240-4bf1-ab5a-d6a00f3063f9', + }, + defaultAuthStrategy: 'ALLOW', + groups: null, + issuer: + 'https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx', + sourceIp: ['2001:0db8:85a3:0000:0000:8a2e:0370:7334'], + sub: '192879fc-a240-4bf1-ab5a-d6a00f3063f9', + username: 'jdoe', + }, + }, + }, + { + name: 'iam identity with mixed IPv4 and IPv6 sourceIp', + event: { + ...appSyncResolverEvent, + identity: { + accountId: '012345678901', + cognitoIdentityAuthProvider: null, + cognitoIdentityAuthType: null, + cognitoIdentityId: null, + cognitoIdentityPoolId: null, + sourceIp: ['1.1.1.1', '::1', '2001:db8::8a2e:370:7334'], + userArn: 'arn:aws:sts::012345678901:assumed-role/role', + username: 'AROAXYKJUOW6FHGUSK5FA:username', + }, + }, + }, ]; it.each(events)('parses an AppSyn resolver event with $name', ({ event }) => { From 9bdf124e9bc1d39b40e5a67b35499dbc89985ee3 Mon Sep 17 00:00:00 2001 From: Daniel ABIB Date: Fri, 29 Aug 2025 10:39:16 -0300 Subject: [PATCH 2/3] fix(parser): remove IPv6 support from AppSync as per review feedback - Reverted IPv6 support in appsync-shared.ts (AppSync doesn't support IPv6 yet) - Removed IPv6 test cases from appsync.test.ts - Kept IPv6 support for API Gateway and API Gateway v2 only As mentioned in issue #4348, AppSync does not currently support IPv6 --- packages/parser/src/schemas/appsync-shared.ts | 2 +- .../parser/tests/unit/schema/appsync.test.ts | 34 ------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/packages/parser/src/schemas/appsync-shared.ts b/packages/parser/src/schemas/appsync-shared.ts index 548a27a6a9..7e7278bf49 100644 --- a/packages/parser/src/schemas/appsync-shared.ts +++ b/packages/parser/src/schemas/appsync-shared.ts @@ -16,7 +16,7 @@ const AppSyncCognitoIdentity = z.object({ issuer: z.string(), username: z.string(), claims: z.record(z.string(), z.unknown()), - sourceIp: z.array(z.union([z.ipv4(), z.ipv6()])), + sourceIp: z.array(z.ipv4()), defaultAuthStrategy: z.string().nullable(), groups: z.array(z.string()).nullable(), }); diff --git a/packages/parser/tests/unit/schema/appsync.test.ts b/packages/parser/tests/unit/schema/appsync.test.ts index df849ae06f..1b12d2bf2c 100644 --- a/packages/parser/tests/unit/schema/appsync.test.ts +++ b/packages/parser/tests/unit/schema/appsync.test.ts @@ -112,40 +112,6 @@ describe('Schema: AppSync Resolver', () => { }, }, }, - { - name: 'cognito identity with IPv6 sourceIp', - event: { - ...appSyncResolverEvent, - identity: { - claims: { - sub: '192879fc-a240-4bf1-ab5a-d6a00f3063f9', - }, - defaultAuthStrategy: 'ALLOW', - groups: null, - issuer: - 'https://cognito-idp.us-west-2.amazonaws.com/us-west-xxxxxxxxxxx', - sourceIp: ['2001:0db8:85a3:0000:0000:8a2e:0370:7334'], - sub: '192879fc-a240-4bf1-ab5a-d6a00f3063f9', - username: 'jdoe', - }, - }, - }, - { - name: 'iam identity with mixed IPv4 and IPv6 sourceIp', - event: { - ...appSyncResolverEvent, - identity: { - accountId: '012345678901', - cognitoIdentityAuthProvider: null, - cognitoIdentityAuthType: null, - cognitoIdentityId: null, - cognitoIdentityPoolId: null, - sourceIp: ['1.1.1.1', '::1', '2001:db8::8a2e:370:7334'], - userArn: 'arn:aws:sts::012345678901:assumed-role/role', - username: 'AROAXYKJUOW6FHGUSK5FA:username', - }, - }, - }, ]; it.each(events)('parses an AppSyn resolver event with $name', ({ event }) => { From 7269bc5264a73df30610a17e0fca6051f70d193f Mon Sep 17 00:00:00 2001 From: Daniel ABIB Date: Fri, 29 Aug 2025 11:00:39 -0300 Subject: [PATCH 3/3] fix(parser): remove 'as any' casting per review feedback - Replace 'as any' with proper TypeScript types in test files - Use APIGatewayProxyEvent type for API Gateway tests - Use APIGatewayProxyEventV2 type for API Gateway v2 tests - Import types from '../../../src/types/schema.js' This addresses code review feedback for better type safety --- packages/parser/tests/unit/schema/apigw.test.ts | 9 +++++---- packages/parser/tests/unit/schema/apigwv2.test.ts | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/parser/tests/unit/schema/apigw.test.ts b/packages/parser/tests/unit/schema/apigw.test.ts index afd78c0d1a..4f6567348a 100644 --- a/packages/parser/tests/unit/schema/apigw.test.ts +++ b/packages/parser/tests/unit/schema/apigw.test.ts @@ -4,6 +4,7 @@ import { APIGatewayRequestAuthorizerEventSchema, APIGatewayTokenAuthorizerEventSchema, } from '../../../src/schemas/index.js'; +import type { APIGatewayProxyEvent } from '../../../src/types/schema.js'; import { getTestEvent } from '../helpers/utils.js'; describe('Schema: API Gateway REST', () => { @@ -100,10 +101,10 @@ describe('Schema: API Gateway REST', () => { }); it('parses an event with IPv6 sourceIp', () => { // Prepare - const event = getTestEvent({ + const event = getTestEvent({ eventsPath, filename: 'no-auth', - }) as any; + }); // Add IPv6 address to the event event.requestContext.identity.sourceIp = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; @@ -119,10 +120,10 @@ describe('Schema: API Gateway REST', () => { it('parses an event with shortened IPv6 sourceIp', () => { // Prepare - const event = getTestEvent({ + const event = getTestEvent({ eventsPath, filename: 'no-auth', - }) as any; + }); // Add shortened IPv6 address to the event event.requestContext.identity.sourceIp = '::1'; diff --git a/packages/parser/tests/unit/schema/apigwv2.test.ts b/packages/parser/tests/unit/schema/apigwv2.test.ts index d6bee31ceb..e470bbec19 100644 --- a/packages/parser/tests/unit/schema/apigwv2.test.ts +++ b/packages/parser/tests/unit/schema/apigwv2.test.ts @@ -47,10 +47,10 @@ describe('Schema: API Gateway HTTP (v2)', () => { it('parses an event with IPv6 sourceIp', () => { // Prepare - const event = getTestEvent({ + const event = getTestEvent({ eventsPath, filename: 'no-auth', - }) as any; + }); // Add IPv6 address to the event event.requestContext.http.sourceIp = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; @@ -66,10 +66,10 @@ describe('Schema: API Gateway HTTP (v2)', () => { it('parses an event with shortened IPv6 sourceIp', () => { // Prepare - const event = getTestEvent({ + const event = getTestEvent({ eventsPath, filename: 'no-auth', - }) as any; + }); // Add shortened IPv6 address to the event event.requestContext.http.sourceIp = '::1';