From 57c9ea60f5afa08407c300c2a91d302879f5a473 Mon Sep 17 00:00:00 2001 From: Andrew Hughes-Onslow Date: Wed, 7 Aug 2024 17:27:01 +0100 Subject: [PATCH] ATO-814: Added step function error handling and ecs client error handling. --- .../src/handlers/step-functions/new-client.ts | 3 +- .../handlers/step-functions/new-service.ts | 3 +- .../step-functions/step-function-handler.ts | 16 +- .../handlers/step-functions/update-client.ts | 3 +- .../handlers/step-functions/update-service.ts | 3 +- express/src/services/cognito/CognitoClient.ts | 347 +++++++++++------- 6 files changed, 232 insertions(+), 143 deletions(-) diff --git a/backend/api/src/handlers/step-functions/new-client.ts b/backend/api/src/handlers/step-functions/new-client.ts index aa04a994b..7b4a13e71 100644 --- a/backend/api/src/handlers/step-functions/new-client.ts +++ b/backend/api/src/handlers/step-functions/new-client.ts @@ -2,6 +2,5 @@ import {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from "aws-lambda"; import {stepFunctionHandler} from "./step-function-handler"; export const newClientHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - console.info("In newClientHandler() callback from step function. Context (" + JSON.stringify(context) + ")"); - return stepFunctionHandler(event); + return stepFunctionHandler(event, context); }; diff --git a/backend/api/src/handlers/step-functions/new-service.ts b/backend/api/src/handlers/step-functions/new-service.ts index 8e04039d7..f24d864cb 100644 --- a/backend/api/src/handlers/step-functions/new-service.ts +++ b/backend/api/src/handlers/step-functions/new-service.ts @@ -2,6 +2,5 @@ import {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from "aws-lambda"; import {stepFunctionHandler} from "./step-function-handler"; export const newServiceHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - console.info("In newServiceHandler() callback from step function. Context (" + JSON.stringify(context) + ")"); - return stepFunctionHandler(event); + return stepFunctionHandler(event, context); }; diff --git a/backend/api/src/handlers/step-functions/step-function-handler.ts b/backend/api/src/handlers/step-functions/step-function-handler.ts index 7da8163fc..6803d31f7 100644 --- a/backend/api/src/handlers/step-functions/step-function-handler.ts +++ b/backend/api/src/handlers/step-functions/step-function-handler.ts @@ -1,10 +1,18 @@ import {SFNClient, StartSyncExecutionCommand, SyncExecutionStatus} from "@aws-sdk/client-sfn"; -import {APIGatewayProxyEvent, APIGatewayProxyResult} from "aws-lambda"; +import {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from "aws-lambda"; import * as process from "process"; +import {Logger} from "@aws-lambda-powertools/logger"; + +export const logger = new Logger({ + serviceName: "self-service-experience" +}); const stepFunctionsClient = new SFNClient({region: "eu-west-2"}); -export const stepFunctionHandler = async (event: APIGatewayProxyEvent): Promise => { +export const stepFunctionHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { + logger.addContext(context); + logger.info("Starting step function."); + const payload = event?.body ? JSON.parse(event.body as string) : event; const stateMachineArn = process.env.STATE_MACHINE_ARN as string; const input = JSON.stringify(payload); @@ -23,11 +31,11 @@ export const stepFunctionHandler = async (event: APIGatewayProxyEvent): Promise< } else if (result.status == SyncExecutionStatus.TIMED_OUT) { statusCode = 408; resultBody = "Function timed out"; - console.error(resultBody); + logger.error(resultBody); } else { statusCode = 400; resultBody = "Function returned error: " + result.error + ", cause: " + result.cause; - console.error(resultBody); + logger.error(resultBody); } return {statusCode: statusCode, body: resultBody}; diff --git a/backend/api/src/handlers/step-functions/update-client.ts b/backend/api/src/handlers/step-functions/update-client.ts index 94b1d9322..881f6c996 100644 --- a/backend/api/src/handlers/step-functions/update-client.ts +++ b/backend/api/src/handlers/step-functions/update-client.ts @@ -2,6 +2,5 @@ import {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from "aws-lambda"; import {stepFunctionHandler} from "./step-function-handler"; export const doUpdateClientHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - console.info("In doUpdateClientHandler() callback from step function. Context (" + JSON.stringify(context) + ")"); - return stepFunctionHandler(event); + return stepFunctionHandler(event, context); }; diff --git a/backend/api/src/handlers/step-functions/update-service.ts b/backend/api/src/handlers/step-functions/update-service.ts index 94c6083f3..ee0b93f9e 100644 --- a/backend/api/src/handlers/step-functions/update-service.ts +++ b/backend/api/src/handlers/step-functions/update-service.ts @@ -2,6 +2,5 @@ import {APIGatewayProxyEvent, APIGatewayProxyResult, Context} from "aws-lambda"; import {stepFunctionHandler} from "./step-function-handler"; export const doUpdateServiceHandler = async (event: APIGatewayProxyEvent, context: Context): Promise => { - console.info("In doUpdateServiceHandler() callback from step function. Context (" + JSON.stringify(context) + ")"); - return stepFunctionHandler(event); + return stepFunctionHandler(event, context); }; diff --git a/express/src/services/cognito/CognitoClient.ts b/express/src/services/cognito/CognitoClient.ts index 5eb284102..f20316a67 100644 --- a/express/src/services/cognito/CognitoClient.ts +++ b/express/src/services/cognito/CognitoClient.ts @@ -90,16 +90,21 @@ export default class CognitoClient implements CognitoInterface { .padStart(6, "0"); } - await this.sendCommand(AdminCreateUserCommand, { - DesiredDeliveryMediums: ["EMAIL"], - Username: cognitoUserName, - UserPoolId: this.userPoolId, - TemporaryPassword: temporaryPassword, - UserAttributes: [ - {Name: "email", Value: cognitoUserName}, - {Name: "custom:signup_status", Value: ""} - ] - }); + try { + await this.sendCommand(AdminCreateUserCommand, { + DesiredDeliveryMediums: ["EMAIL"], + Username: cognitoUserName, + UserPoolId: this.userPoolId, + TemporaryPassword: temporaryPassword, + UserAttributes: [ + {Name: "email", Value: cognitoUserName}, + {Name: "custom:signup_status", Value: ""} + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async recoverUser(email: string): Promise { @@ -107,19 +112,24 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(email); - await this.sendCommand(AdminCreateUserCommand, { - DesiredDeliveryMediums: ["EMAIL"], - MessageAction: "SUPPRESS", - Username: cognitoUserName, - UserPoolId: this.userPoolId, - TemporaryPassword: Math.floor(Math.random() * 100_000) - .toString() - .padStart(6, "0"), - UserAttributes: [ - {Name: "email", Value: cognitoUserName}, - {Name: "custom:signup_status", Value: "HasEmail,HasPassword,HasPhoneNumber,HasTextCode"} - ] - }); + try { + await this.sendCommand(AdminCreateUserCommand, { + DesiredDeliveryMediums: ["EMAIL"], + MessageAction: "SUPPRESS", + Username: cognitoUserName, + UserPoolId: this.userPoolId, + TemporaryPassword: Math.floor(Math.random() * 100_000) + .toString() + .padStart(6, "0"), + UserAttributes: [ + {Name: "email", Value: cognitoUserName}, + {Name: "custom:signup_status", Value: "HasEmail,HasPassword,HasPhoneNumber,HasTextCode"} + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async resendEmailAuthCode(email: string): Promise { @@ -136,14 +146,19 @@ export default class CognitoClient implements CognitoInterface { .padStart(6, "0"); } - await this.sendCommand(AdminCreateUserCommand, { - DesiredDeliveryMediums: ["EMAIL"], - Username: cognitoUserName, - UserPoolId: this.userPoolId, - MessageAction: "RESEND", - TemporaryPassword: temporaryPassword, - UserAttributes: [{Name: "email", Value: cognitoUserName}] - }); + try { + await this.sendCommand(AdminCreateUserCommand, { + DesiredDeliveryMediums: ["EMAIL"], + Username: cognitoUserName, + UserPoolId: this.userPoolId, + MessageAction: "RESEND", + TemporaryPassword: temporaryPassword, + UserAttributes: [{Name: "email", Value: cognitoUserName}] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } login(email: string, password: string): Promise { @@ -163,15 +178,25 @@ export default class CognitoClient implements CognitoInterface { } async globalSignOut(accessToken: string): Promise { - return await this.sendCommand(GlobalSignOutCommand, { - AccessToken: accessToken - }); + try { + return await this.sendCommand(GlobalSignOutCommand, { + AccessToken: accessToken + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async getUser(accessToken: string): Promise { - return await this.sendCommand(GetUserCommand, { - AccessToken: accessToken - }); + try { + return await this.sendCommand(GetUserCommand, { + AccessToken: accessToken + }); + } catch (error) { + console.error(error as Error); + throw error; + } } setNewPassword(email: string, password: string, session: string): Promise { @@ -193,11 +218,16 @@ export default class CognitoClient implements CognitoInterface { async changePassword(accessToken: string, previousPassword: string, proposedPassword: string): Promise { console.info("In CognitoClient:changePassword()"); - await this.sendCommand(ChangePasswordCommand, { - AccessToken: accessToken, - PreviousPassword: previousPassword, - ProposedPassword: proposedPassword - }); + try { + await this.sendCommand(ChangePasswordCommand, { + AccessToken: accessToken, + PreviousPassword: previousPassword, + ProposedPassword: proposedPassword + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async forgotPassword(email: string, protocol: string, host: string, useRecoveredAccountURL: boolean): Promise { @@ -205,16 +235,21 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(email); - await this.sendCommand(ForgotPasswordCommand, { - ClientId: this.clientId, - Username: cognitoUserName, - ClientMetadata: { - protocol, - host: host.split("").join("/"), - username: cognitoUserName, - use_recovered_account_url: useRecoveredAccountURL ? "true" : "false" - } - }); + try { + await this.sendCommand(ForgotPasswordCommand, { + ClientId: this.clientId, + Username: cognitoUserName, + ClientMetadata: { + protocol, + host: host.split("").join("/"), + username: cognitoUserName, + use_recovered_account_url: useRecoveredAccountURL ? "true" : "false" + } + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async confirmForgotPassword(emailAddress: string, password: string, confirmationCode: string): Promise { @@ -222,15 +257,20 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(emailAddress); - await this.sendCommand(ConfirmForgotPasswordCommand, { - ClientId: this.clientId, - Username: cognitoUserName, - Password: password, - ConfirmationCode: confirmationCode, - ClientMetadata: { - email: cognitoUserName - } - }); + try { + await this.sendCommand(ConfirmForgotPasswordCommand, { + ClientId: this.clientId, + Username: cognitoUserName, + Password: password, + ConfirmationCode: confirmationCode, + ClientMetadata: { + email: cognitoUserName + } + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async setUserPassword(emailAddress: string, password: string): Promise { @@ -238,12 +278,17 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(emailAddress); - await this.sendCommand(AdminSetUserPasswordCommand, { - Password: password, - Permanent: true, - Username: cognitoUserName, - UserPoolId: this.userPoolId - }); + try { + await this.sendCommand(AdminSetUserPasswordCommand, { + Password: password, + Permanent: true, + Username: cognitoUserName, + UserPoolId: this.userPoolId + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async setEmailAsVerified(email: string): Promise { @@ -251,16 +296,21 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(email); - await this.sendCommand(AdminUpdateUserAttributesCommand, { - UserPoolId: this.userPoolId, - Username: cognitoUserName, - UserAttributes: [ - { - Name: "email_verified", - Value: "true" - } - ] - }); + try { + await this.sendCommand(AdminUpdateUserAttributesCommand, { + UserPoolId: this.userPoolId, + Username: cognitoUserName, + UserAttributes: [ + { + Name: "email_verified", + Value: "true" + } + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async setSignUpStatus(emailAddress: string, status: string): Promise { @@ -268,16 +318,21 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(emailAddress); - await this.sendCommand(AdminUpdateUserAttributesCommand, { - UserPoolId: this.userPoolId, - Username: cognitoUserName, - UserAttributes: [ - { - Name: "custom:signup_status", - Value: status - } - ] - }); + try { + await this.sendCommand(AdminUpdateUserAttributesCommand, { + UserPoolId: this.userPoolId, + Username: cognitoUserName, + UserAttributes: [ + { + Name: "custom:signup_status", + Value: status + } + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async adminGetUserCommandOutput(emailAddress: string): Promise { @@ -298,16 +353,21 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(emailAddress); - await this.sendCommand(AdminUpdateUserAttributesCommand, { - UserPoolId: this.userPoolId, - Username: cognitoUserName, - UserAttributes: [ - { - Name: "phone_number_verified", - Value: "true" - } - ] - }); + try { + await this.sendCommand(AdminUpdateUserAttributesCommand, { + UserPoolId: this.userPoolId, + Username: cognitoUserName, + UserAttributes: [ + { + Name: "phone_number_verified", + Value: "true" + } + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async setPhoneNumber(emailAddress: string, phoneNumber: string): Promise { @@ -315,25 +375,35 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(emailAddress); - await this.sendCommand(AdminUpdateUserAttributesCommand, { - UserPoolId: this.userPoolId, - Username: cognitoUserName, - UserAttributes: [ - { - Name: "phone_number", - Value: phoneNumber - } - ] - }); + try { + await this.sendCommand(AdminUpdateUserAttributesCommand, { + UserPoolId: this.userPoolId, + Username: cognitoUserName, + UserAttributes: [ + { + Name: "phone_number", + Value: phoneNumber + } + ] + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async sendMobileNumberVerificationCode(accessToken: string): Promise { console.info("In CognitoClient:sendMobileNumberVerification()"); - await this.sendCommand(GetUserAttributeVerificationCodeCommand, { - AccessToken: accessToken, - AttributeName: "phone_number" - }); + try { + await this.sendCommand(GetUserAttributeVerificationCodeCommand, { + AccessToken: accessToken, + AttributeName: "phone_number" + }); + } catch (error) { + console.error(error as Error); + throw error; + } } /** @@ -345,11 +415,16 @@ export default class CognitoClient implements CognitoInterface { if (isFixedOTPCredential(emailAddress)) { verifyMobileUsingOTPCode(emailAddress, code); } else { - await this.sendCommand(VerifyUserAttributeCommand, { - AccessToken: accessToken, - AttributeName: "phone_number", - Code: code - }); + try { + await this.sendCommand(VerifyUserAttributeCommand, { + AccessToken: accessToken, + AttributeName: "phone_number", + Code: code + }); + } catch (error) { + console.error(error as Error); + throw error; + } } } @@ -358,14 +433,19 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(userName); - await this.sendCommand(AdminSetUserMFAPreferenceCommand, { - SMSMfaSettings: { - Enabled: true, - PreferredMfa: true - }, - Username: cognitoUserName, - UserPoolId: this.userPoolId - }); + try { + await this.sendCommand(AdminSetUserMFAPreferenceCommand, { + SMSMfaSettings: { + Enabled: true, + PreferredMfa: true + }, + Username: cognitoUserName, + UserPoolId: this.userPoolId + }); + } catch (error) { + console.error(error as Error); + throw error; + } } async resetMfaPreference(username: string): Promise { @@ -373,13 +453,18 @@ export default class CognitoClient implements CognitoInterface { const cognitoUserName = this.translatePseudonymisedEmailAddress(username); - await this.sendCommand(AdminSetUserMFAPreferenceCommand, { - SMSMfaSettings: { - Enabled: false - }, - Username: cognitoUserName, - UserPoolId: this.userPoolId - }); + try { + await this.sendCommand(AdminSetUserMFAPreferenceCommand, { + SMSMfaSettings: { + Enabled: false + }, + Username: cognitoUserName, + UserPoolId: this.userPoolId + }); + } catch (error) { + console.error(error as Error); + throw error; + } } respondToMfaChallenge(emailAddress: string, mfaCode: string, session: string): Promise {