diff --git a/packages/parser/src/schemas/cognito.ts b/packages/parser/src/schemas/cognito.ts index 085f5e8bae..7d2292c367 100644 --- a/packages/parser/src/schemas/cognito.ts +++ b/packages/parser/src/schemas/cognito.ts @@ -13,7 +13,8 @@ const CognitoTriggerBaseSchema = z.object({ userName: z.string().optional(), callerContext: z.object({ awsSdkVersion: z.string(), - clientId: z.string(), + // Admin API operations such as AdminConfirmSignUp send null + clientId: z.string().nullable(), }), request: z.object({}), response: z.object({}), @@ -55,7 +56,11 @@ const CognitoTriggerBaseSchema = z.object({ * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html | Amazon Cognito Developer Guide} */ const PreSignupTriggerSchema = CognitoTriggerBaseSchema.extend({ - triggerSource: z.literal('PreSignUp_SignUp'), + triggerSource: z.enum([ + 'PreSignUp_SignUp', + 'PreSignUp_AdminCreateUser', + 'PreSignUp_ExternalProvider', + ]), request: z.object({ userAttributes: z.record(z.string(), z.string()), validationData: z.record(z.string(), z.string()).nullable(), @@ -100,7 +105,10 @@ const PreSignupTriggerSchema = CognitoTriggerBaseSchema.extend({ * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html | Amazon Cognito Developer Guide} */ const PostConfirmationTriggerSchema = CognitoTriggerBaseSchema.extend({ - triggerSource: z.literal('PostConfirmation_ConfirmSignUp'), + triggerSource: z.enum([ + 'PostConfirmation_ConfirmSignUp', + 'PostConfirmation_ConfirmForgotPassword', + ]), request: z.object({ userAttributes: z.record(z.string(), z.string()), clientMetadata: z.record(z.string(), z.string()).optional(), @@ -397,19 +405,30 @@ const CustomMessageTriggerSchema = CognitoTriggerBaseSchema.extend({ * "code": "string", * "clientMetadata": { "string": "string" }, * "userAttributes": { "string": "string" } - * }, - * "response": {} + * } * } * ``` * * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-email-sender.html | Amazon Cognito Developer Guide} */ -const CustomEmailSenderTriggerSchema = CognitoTriggerBaseSchema.extend({ - triggerSource: z.literal('CustomEmailSender_SignUp'), +// Custom sender events have no response, since Cognito expects nothing back +const CustomEmailSenderTriggerSchema = CognitoTriggerBaseSchema.omit({ + response: true, +}).extend({ + triggerSource: z.enum([ + 'CustomEmailSender_SignUp', + 'CustomEmailSender_ResendCode', + 'CustomEmailSender_ForgotPassword', + 'CustomEmailSender_UpdateUserAttribute', + 'CustomEmailSender_VerifyUserAttribute', + 'CustomEmailSender_AdminCreateUser', + 'CustomEmailSender_Authentication', + 'CustomEmailSender_AccountTakeOverNotification', + ]), request: z.object({ type: z.literal('customEmailSenderRequestV1'), code: z.string(), - clientMetadata: z.record(z.string(), z.string()).optional(), + clientMetadata: z.record(z.string(), z.string()).nullish(), userAttributes: z.record(z.string(), z.string()), }), }); @@ -436,19 +455,28 @@ const CustomEmailSenderTriggerSchema = CognitoTriggerBaseSchema.extend({ * "string": "string" * }, * "userAttributes": { "string": "string" } - * }, - * "response": {} + * } * } * ``` * * @see {@link https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-sms-sender.html | Amazon Cognito Developer Guide} */ -const CustomSMSSenderTriggerSchema = CognitoTriggerBaseSchema.extend({ - triggerSource: z.literal('CustomSMSSender_SignUp'), +const CustomSMSSenderTriggerSchema = CognitoTriggerBaseSchema.omit({ + response: true, +}).extend({ + triggerSource: z.enum([ + 'CustomSMSSender_SignUp', + 'CustomSMSSender_ResendCode', + 'CustomSMSSender_ForgotPassword', + 'CustomSMSSender_UpdateUserAttribute', + 'CustomSMSSender_VerifyUserAttribute', + 'CustomSMSSender_AdminCreateUser', + 'CustomSMSSender_Authentication', + ]), request: z.object({ type: z.literal('customSMSSenderRequestV1'), code: z.string(), - clientMetadata: z.record(z.string(), z.string()).optional(), + clientMetadata: z.record(z.string(), z.string()).nullish(), userAttributes: z.record(z.string(), z.string()), }), }); @@ -513,7 +541,8 @@ const DefineAuthChallengeTriggerSchema = CognitoTriggerBaseSchema.extend({ triggerSource: z.literal('DefineAuthChallenge_Authentication'), request: z.object({ userAttributes: z.record(z.string(), z.string()), - session: z.array(ChallengeResultSchema).min(1), + // Empty on the first call of a custom auth flow without SRP + session: z.array(ChallengeResultSchema), clientMetadata: z.record(z.string(), z.string()).optional(), userNotFound: z.boolean().optional(), }), @@ -563,7 +592,8 @@ const CreateAuthChallengeTriggerSchema = CognitoTriggerBaseSchema.extend({ request: z.object({ userAttributes: z.record(z.string(), z.string()), challengeName: z.string(), - session: z.array(ChallengeResultSchema).min(1), + // Empty on the first call of a custom auth flow without SRP + session: z.array(ChallengeResultSchema), clientMetadata: z.record(z.string(), z.string()).optional(), userNotFound: z.boolean().optional(), }), @@ -614,7 +644,8 @@ const VerifyAuthChallengeTriggerSchema = CognitoTriggerBaseSchema.extend({ userNotFound: z.boolean().optional(), }), response: z.object({ - answerCorrect: z.boolean(), + // Cognito sends null; the function sets the verdict + answerCorrect: z.boolean().nullable(), }), }); diff --git a/packages/parser/tests/unit/schema/cognito.test.ts b/packages/parser/tests/unit/schema/cognito.test.ts index 4e6c1e6040..4cef95ccfd 100644 --- a/packages/parser/tests/unit/schema/cognito.test.ts +++ b/packages/parser/tests/unit/schema/cognito.test.ts @@ -13,7 +13,7 @@ import { PreTokenGenerationTriggerSchemaV1, VerifyAuthChallengeTriggerSchema, } from '../../../src/schemas/cognito.js'; -import { getTestEvent } from '../helpers/utils.js'; +import { getTestEvent, omit } from '../helpers/utils.js'; describe('Schemas: Cognito User Pool', () => { const baseEvent = getTestEvent({ @@ -21,6 +21,139 @@ describe('Schemas: Cognito User Pool', () => { filename: 'base', }); + // Shapes captured from a live user pool, derived from the base event + const userAttributes = { + sub: '11111111-2222-3333-4444-555555555555', + email_verified: 'true', + 'cognito:user_status': 'CONFIRMED', + email: 'user@example.com', + }; + const adminCallerContext = { + awsSdkVersion: 'aws-sdk-unknown-unknown', + clientId: 'CLIENT_ID_NOT_APPLICABLE', + }; + const customEmailSenderRequest = { + userAttributes, + type: 'customEmailSenderRequestV1', + code: 'AYADeIZczazd30Tm9/+4', + clientMetadata: null, + }; + const capturedEvents = [ + { + name: 'PreSignUp_AdminCreateUser', + schema: PreSignupTriggerSchema, + event: { + ...baseEvent, + callerContext: adminCallerContext, + triggerSource: 'PreSignUp_AdminCreateUser', + request: { userAttributes, validationData: null }, + response: { + autoConfirmUser: false, + autoVerifyEmail: false, + autoVerifyPhone: false, + }, + }, + }, + { + name: 'PostConfirmation_ConfirmForgotPassword', + schema: PostConfirmationTriggerSchema, + event: { + ...baseEvent, + triggerSource: 'PostConfirmation_ConfirmForgotPassword', + request: { userAttributes }, + }, + }, + { + name: 'PostConfirmation_ConfirmSignUp (admin-confirm-sign-up)', + schema: PostConfirmationTriggerSchema, + event: { + ...baseEvent, + callerContext: { + awsSdkVersion: 'aws-sdk-unknown-unknown', + clientId: null, + }, + triggerSource: 'PostConfirmation_ConfirmSignUp', + request: { userAttributes }, + }, + }, + { + name: 'CustomEmailSender_ForgotPassword', + schema: CustomEmailSenderTriggerSchema, + event: { + ...omit(['response'], baseEvent), + triggerSource: 'CustomEmailSender_ForgotPassword', + request: customEmailSenderRequest, + }, + }, + { + name: 'CustomEmailSender_AdminCreateUser', + schema: CustomEmailSenderTriggerSchema, + event: { + ...omit(['response'], baseEvent), + callerContext: adminCallerContext, + triggerSource: 'CustomEmailSender_AdminCreateUser', + request: customEmailSenderRequest, + }, + }, + { + name: 'DefineAuthChallenge_Authentication (first call)', + schema: DefineAuthChallengeTriggerSchema, + event: { + ...baseEvent, + triggerSource: 'DefineAuthChallenge_Authentication', + request: { userAttributes, session: [] }, + response: { + challengeName: null, + issueTokens: null, + failAuthentication: null, + }, + }, + }, + { + name: 'CreateAuthChallenge_Authentication (first call)', + schema: CreateAuthChallengeTriggerSchema, + event: { + ...baseEvent, + triggerSource: 'CreateAuthChallenge_Authentication', + request: { + userAttributes, + challengeName: 'CUSTOM_CHALLENGE', + session: [], + }, + response: { + publicChallengeParameters: null, + privateChallengeParameters: null, + challengeMetadata: null, + }, + }, + }, + { + name: 'VerifyAuthChallengeResponse_Authentication', + schema: VerifyAuthChallengeTriggerSchema, + event: { + ...baseEvent, + triggerSource: 'VerifyAuthChallengeResponse_Authentication', + request: { + userAttributes, + privateChallengeParameters: { answer: '42' }, + challengeAnswer: '42', + }, + response: { answerCorrect: null }, + }, + }, + ]; + + it.each(capturedEvents)( + 'parses a $name event captured from a user pool', + ({ schema, event }) => { + // Act + const result = schema.parse(event); + + // Assess + expect(result).toStrictEqual(event); + } + ); + it('parses a valid pre-signup event', () => { // Prepare const event = structuredClone(baseEvent); @@ -230,6 +363,7 @@ describe('Schemas: Cognito User Pool', () => { it('parses a valid custom message event with custom email sender', () => { // Prepare const event = structuredClone(baseEvent); + delete event.response; event.triggerSource = 'CustomEmailSender_SignUp'; event.request = { type: 'customEmailSenderRequestV1', @@ -261,6 +395,7 @@ describe('Schemas: Cognito User Pool', () => { it('parses a valid custom message event with custom SMS sender', () => { // Prepare const event = structuredClone(baseEvent); + delete event.response; event.triggerSource = 'CustomSMSSender_SignUp'; event.request = { type: 'customSMSSenderRequestV1',