Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 47 additions & 16 deletions packages/parser/src/schemas/cognito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({}),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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()),
}),
});
Expand All @@ -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()),
}),
});
Expand Down Expand Up @@ -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(),
}),
Expand Down Expand Up @@ -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(),
}),
Expand Down Expand Up @@ -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(),
}),
});

Expand Down
137 changes: 136 additions & 1 deletion packages/parser/tests/unit/schema/cognito.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,147 @@ 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({
eventsPath: 'cognito',
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);
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
Loading