Skip to content

Commit 6880a90

Browse files
committed
feat(lambda): limited CustomMessage lambda support
Only supports the ForgotPassword flow so far
1 parent ca56796 commit 6880a90

16 files changed

+498
-42
lines changed

integration-tests/aws-sdk/setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const withCognitoSdk = (
5353
{
5454
cognitoClient,
5555
messageDelivery: mockCodeDelivery,
56-
messages: new MessagesService(),
56+
messages: new MessagesService(triggers),
5757
otp,
5858
triggers,
5959
},

src/__tests__/testDataBuilder.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { User } from "../services/userPoolClient";
2+
3+
export const user = (): User => ({
4+
Attributes: [],
5+
Enabled: true,
6+
Password: "Password123!",
7+
UserCreateDate: new Date().getTime(),
8+
UserLastModifiedDate: new Date().getTime(),
9+
Username: "Username",
10+
UserStatus: "CONFIRMED",
11+
});

src/server/defaults.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const createDefaultServer = async (logger: Logger): Promise<Server> => {
3939
messageDelivery: new MessageDeliveryService(
4040
new ConsoleMessageSender(logger)
4141
),
42-
messages: new MessagesService(),
42+
messages: new MessagesService(triggers),
4343
otp,
4444
triggers,
4545
},

src/services/lambda.test.ts

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ describe("Lambda function invoker", () => {
4747
).rejects.toEqual(new Error("UserMigration trigger not configured"));
4848
});
4949

50-
describe("UserMigration_Authentication", () => {
51-
it("invokes the lambda", async () => {
50+
describe("when lambda successful", () => {
51+
it("returns string payload as json", async () => {
5252
const response = Promise.resolve({
5353
StatusCode: 200,
54-
Payload: '{ "some": "json" }',
54+
Payload: '{ "response": "value" }',
5555
});
5656
mockLambdaClient.invoke.mockReturnValue({
5757
promise: () => response,
@@ -64,7 +64,7 @@ describe("Lambda function invoker", () => {
6464
MockLogger
6565
);
6666

67-
await lambda.invoke("UserMigration", {
67+
const result = await lambda.invoke("UserMigration", {
6868
clientId: "clientId",
6969
password: "password",
7070
triggerSource: "UserMigration_Authentication",
@@ -73,32 +73,13 @@ describe("Lambda function invoker", () => {
7373
userAttributes: {},
7474
});
7575

76-
expect(mockLambdaClient.invoke).toHaveBeenCalledWith({
77-
FunctionName: "MyLambdaName",
78-
InvocationType: "RequestResponse",
79-
Payload: JSON.stringify({
80-
version: 0,
81-
userName: "username",
82-
callerContext: { awsSdkVersion: "2.656.0", clientId: "clientId" },
83-
region: "local",
84-
userPoolId: "userPoolId",
85-
triggerSource: "UserMigration_Authentication",
86-
request: {
87-
userAttributes: {},
88-
password: "password",
89-
validationData: {},
90-
},
91-
response: {},
92-
}),
93-
});
76+
expect(result).toEqual("value");
9477
});
95-
});
9678

97-
describe("when lambda successful", () => {
98-
it("returns string payload as json", async () => {
79+
it("returns Buffer payload as json", async () => {
9980
const response = Promise.resolve({
10081
StatusCode: 200,
101-
Payload: '{ "response": "value" }',
82+
Payload: Buffer.from('{ "response": "value" }'),
10283
});
10384
mockLambdaClient.invoke.mockReturnValue({
10485
promise: () => response,
@@ -122,11 +103,13 @@ describe("Lambda function invoker", () => {
122103

123104
expect(result).toEqual("value");
124105
});
106+
});
125107

126-
it("returns Buffer payload as json", async () => {
108+
describe("UserMigration_Authentication", () => {
109+
it("invokes the lambda", async () => {
127110
const response = Promise.resolve({
128111
StatusCode: 200,
129-
Payload: Buffer.from('{ "response": "value" }'),
112+
Payload: '{ "some": "json" }',
130113
});
131114
mockLambdaClient.invoke.mockReturnValue({
132115
promise: () => response,
@@ -139,7 +122,7 @@ describe("Lambda function invoker", () => {
139122
MockLogger
140123
);
141124

142-
const result = await lambda.invoke("UserMigration", {
125+
await lambda.invoke("UserMigration", {
143126
clientId: "clientId",
144127
password: "password",
145128
triggerSource: "UserMigration_Authentication",
@@ -148,7 +131,79 @@ describe("Lambda function invoker", () => {
148131
userAttributes: {},
149132
});
150133

151-
expect(result).toEqual("value");
134+
expect(mockLambdaClient.invoke).toHaveBeenCalledWith({
135+
FunctionName: "MyLambdaName",
136+
InvocationType: "RequestResponse",
137+
Payload: JSON.stringify({
138+
version: 0,
139+
userName: "username",
140+
callerContext: { awsSdkVersion: "2.656.0", clientId: "clientId" },
141+
region: "local",
142+
userPoolId: "userPoolId",
143+
triggerSource: "UserMigration_Authentication",
144+
request: {
145+
userAttributes: {},
146+
password: "password",
147+
validationData: {},
148+
},
149+
response: {},
150+
}),
151+
});
152+
});
153+
});
154+
155+
describe.each([
156+
"CustomMessage_SignUp",
157+
"CustomMessage_AdminCreateUser",
158+
"CustomMessage_ResendCode",
159+
"CustomMessage_ForgotPassword",
160+
"CustomMessage_UpdateUserAttribute",
161+
"CustomMessage_VerifyUserAttribute",
162+
"CustomMessage_Authentication",
163+
] as const)("%s", (source) => {
164+
it("invokes the lambda function with the code parameter", async () => {
165+
const response = Promise.resolve({
166+
StatusCode: 200,
167+
Payload: '{ "some": "json" }',
168+
});
169+
mockLambdaClient.invoke.mockReturnValue({
170+
promise: () => response,
171+
} as any);
172+
const lambda = new LambdaService(
173+
{
174+
CustomMessage: "MyLambdaName",
175+
},
176+
mockLambdaClient,
177+
MockLogger
178+
);
179+
180+
await lambda.invoke("CustomMessage", {
181+
clientId: "clientId",
182+
code: "1234",
183+
triggerSource: source,
184+
userAttributes: {},
185+
username: "username",
186+
userPoolId: "userPoolId",
187+
});
188+
189+
expect(mockLambdaClient.invoke).toHaveBeenCalledWith({
190+
FunctionName: "MyLambdaName",
191+
InvocationType: "RequestResponse",
192+
Payload: JSON.stringify({
193+
version: 0,
194+
userName: "username",
195+
callerContext: { awsSdkVersion: "2.656.0", clientId: "clientId" },
196+
region: "local",
197+
userPoolId: "userPoolId",
198+
triggerSource: source,
199+
request: {
200+
userAttributes: {},
201+
usernameParameter: "username",
202+
codeParameter: "1234",
203+
},
204+
response: {},
205+
}),
206+
});
152207
});
153208
});
154209
});

src/services/lambda.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ import { UnexpectedLambdaExceptionError } from "../errors";
55
import { version as awsSdkVersion } from "aws-sdk/package.json";
66
import { Logger } from "../log";
77

8+
interface CustomMessageEvent {
9+
userPoolId: string;
10+
clientId: string;
11+
username: string;
12+
code: string;
13+
userAttributes: Record<string, string>;
14+
triggerSource:
15+
| "CustomMessage_SignUp"
16+
| "CustomMessage_AdminCreateUser"
17+
| "CustomMessage_ResendCode"
18+
| "CustomMessage_ForgotPassword"
19+
| "CustomMessage_UpdateUserAttribute"
20+
| "CustomMessage_VerifyUserAttribute"
21+
| "CustomMessage_Authentication";
22+
}
23+
824
interface UserMigrationEvent {
925
userPoolId: string;
1026
clientId: string;
@@ -27,15 +43,24 @@ interface PostConfirmationEvent {
2743
export type CognitoUserPoolResponse = CognitoUserPoolEvent["response"];
2844

2945
export interface FunctionConfig {
46+
CustomMessage?: string;
3047
UserMigration?: string;
3148
PostConfirmation?: string;
3249
}
3350

3451
export interface Lambda {
3552
enabled(lambda: keyof FunctionConfig): boolean;
3653
invoke(
37-
trigger: keyof FunctionConfig,
38-
event: UserMigrationEvent | PostConfirmationEvent
54+
lambda: "CustomMessage",
55+
event: CustomMessageEvent
56+
): Promise<CognitoUserPoolResponse>;
57+
invoke(
58+
lambda: "UserMigration",
59+
event: UserMigrationEvent
60+
): Promise<CognitoUserPoolResponse>;
61+
invoke(
62+
lambda: "PostConfirmation",
63+
event: PostConfirmationEvent
3964
): Promise<CognitoUserPoolResponse>;
4065
}
4166

@@ -60,8 +85,8 @@ export class LambdaService implements Lambda {
6085

6186
public async invoke(
6287
trigger: keyof FunctionConfig,
63-
event: UserMigrationEvent | PostConfirmationEvent
64-
): Promise<CognitoUserPoolResponse> {
88+
event: CustomMessageEvent | UserMigrationEvent | PostConfirmationEvent
89+
) {
6590
const functionName = this.config[trigger];
6691
if (!functionName) {
6792
throw new Error(`${trigger} trigger not configured`);
@@ -86,6 +111,17 @@ export class LambdaService implements Lambda {
86111
if (event.triggerSource === "UserMigration_Authentication") {
87112
lambdaEvent.request.password = event.password;
88113
lambdaEvent.request.validationData = {};
114+
} else if (
115+
event.triggerSource === "CustomMessage_SignUp" ||
116+
event.triggerSource === "CustomMessage_AdminCreateUser" ||
117+
event.triggerSource === "CustomMessage_ResendCode" ||
118+
event.triggerSource === "CustomMessage_ForgotPassword" ||
119+
event.triggerSource === "CustomMessage_UpdateUserAttribute" ||
120+
event.triggerSource === "CustomMessage_VerifyUserAttribute" ||
121+
event.triggerSource === "CustomMessage_Authentication"
122+
) {
123+
lambdaEvent.request.usernameParameter = event.username;
124+
lambdaEvent.request.codeParameter = event.code;
89125
}
90126

91127
this.logger.debug(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { MockLogger } from "../../__tests__/mockLogger";
2+
import { ConsoleMessageSender } from "./consoleMessageSender";
3+
import * as TDB from "../../__tests__/testDataBuilder";
4+
5+
describe("consoleMessageSender", () => {
6+
const user = TDB.user();
7+
const destination = "example@example.com";
8+
const mockLog = MockLogger;
9+
const sender = new ConsoleMessageSender(mockLog);
10+
11+
beforeEach(() => {
12+
jest.resetAllMocks();
13+
});
14+
15+
describe.each(["sendEmail", "sendSms"] as const)("%s", (fn) => {
16+
it("prints the message to the console", async () => {
17+
await sender[fn](user, destination, {
18+
__code: "1234",
19+
});
20+
21+
expect(mockLog.info).toHaveBeenCalledWith(
22+
expect.stringMatching(/Username:\s+Username/)
23+
);
24+
expect(mockLog.info).toHaveBeenCalledWith(
25+
expect.stringMatching(/Destination:\s+example@example.com/)
26+
);
27+
expect(mockLog.info).toHaveBeenCalledWith(
28+
expect.stringMatching(/Code:\s+1234/)
29+
);
30+
});
31+
32+
it("doesn't print undefined fields", async () => {
33+
await sender[fn](user, destination, {
34+
__code: "1234",
35+
emailMessage: undefined,
36+
});
37+
38+
expect(mockLog.info).not.toHaveBeenCalledWith(
39+
expect.stringMatching(/Email Message/)
40+
);
41+
});
42+
43+
it("prints additional fields", async () => {
44+
await sender[fn](user, destination, {
45+
__code: "1234",
46+
emailMessage: "this is the email message",
47+
});
48+
49+
expect(mockLog.info).toHaveBeenCalledWith(
50+
expect.stringMatching(/Email Message:\s+this is the email message/)
51+
);
52+
});
53+
});
54+
});

0 commit comments

Comments
 (0)