From 3daf64d71ba116a6ad1648f68ce491ce2d000788 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 10:41:43 -0700 Subject: [PATCH 1/9] Create migration for Cognito user pool changes --- BUILDING.md | 6 +- cloudformation/template.yaml | 152 ++++++++++-------- .../__tests__/index.js | 28 ---- lambdas/cognito-pre-signup-trigger/index.js | 37 ----- .../cognito-user-migration-trigger/index.js | 45 ++++++ scripts/internal/deploy-template.js | 4 +- scripts/internal/get-deployer-config.js | 1 + 7 files changed, 140 insertions(+), 133 deletions(-) delete mode 100644 lambdas/cognito-pre-signup-trigger/__tests__/index.js delete mode 100644 lambdas/cognito-pre-signup-trigger/index.js create mode 100644 lambdas/cognito-user-migration-trigger/index.js diff --git a/BUILDING.md b/BUILDING.md index 00574aac0..e3ccfccc2 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -81,7 +81,11 @@ The ARN of the ACM certificate corresponding to the custom domain to use to host *Default: `'DevPortalIdentityPool'`* -The name of the generated Cognito identity pool. +The name of the generated Cognito identity pool and user pool. + +### `legacyCognitoIdentityPoolName: string` + +The name of the old generated Cognito identity pool and user pool. Set this only if you have an existing instance to fail over to and before updating it. You don't need to set this if you're setting it up for the first time. ### `customersTableName: string` diff --git a/cloudformation/template.yaml b/cloudformation/template.yaml index bd23462fb..78d76f969 100644 --- a/cloudformation/template.yaml +++ b/cloudformation/template.yaml @@ -18,7 +18,7 @@ Metadata: Label: default: "Dev Portal Customer Configuration" Parameters: - - CognitoIdentityPoolName + - CognitoIdentityPoolName2 - DevPortalCustomersTableName - AccountRegistrationMode # Marketplace support is currently broken @@ -42,6 +42,12 @@ Metadata: Parameters: - DevPortalAdminEmail - DevPortalFeedbackTableName + - + Label: + default: "Dev Portal Migration Configuration" + Parameters: + - CognitoIdentityPoolName + - EdgeLambdaRebuildToken Parameters: ArtifactsS3BucketName: @@ -100,6 +106,11 @@ Parameters: # Default: DevPortalMarketplaceSubscriptionTopic CognitoIdentityPoolName: + Type: String + Description: The name for your legacy identity pool. Set this only if you have an existing instance to fail over to and before updating it. + Default: '' + + CognitoIdentityPoolName2: Type: String Description: The name for your Cognito Identity Pool. Default: 'DevPortalIdentityPool' @@ -170,6 +181,7 @@ Conditions: NotDevelopmentMode: !Not [!Condition DevelopmentMode] InUSEastOne: !Equals [!Ref 'AWS::Region', 'us-east-1'] InviteAccountRegistrationMode: !Equals [!Ref AccountRegistrationMode, 'invite'] + HasLegacyUserPool: !Equals [!Ref CognitoIdentityPoolName, ''] Resources: ApiGatewayApi: @@ -839,9 +851,9 @@ Resources: - cognito-idp:AdminDeleteUser - cognito-idp:AdminGetUser - cognito-idp:AdminListGroupsForUser - Resource: !GetAtt CognitoUserPool.Arn + Resource: !GetAtt CognitoUserPool0.Arn - CognitoPreSignupTriggerExecutionRole: + CognitoPostConfirmationTriggerExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: @@ -863,8 +875,16 @@ Resources: - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* + - Effect: Allow + Action: + - dynamodb:PutItem + Resource: !GetAtt PreLoginAccountsTable.Arn + - Effect: Allow + Action: + - cognito-idp:AdminAddUserToGroup + Resource: !GetAtt CognitoUserPool0.Arn - CognitoPostConfirmationTriggerExecutionRole: + CognitoPostAuthenticationTriggerExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: @@ -888,14 +908,20 @@ Resources: Resource: arn:aws:logs:*:*:* - Effect: Allow Action: + - dynamodb:Scan + - dynamodb:PutItem + Resource: !GetAtt CustomersTable.Arn + - Effect: Allow + Action: + - dynamodb:GetItem - dynamodb:PutItem Resource: !GetAtt PreLoginAccountsTable.Arn - Effect: Allow Action: - cognito-idp:AdminAddUserToGroup - Resource: !GetAtt CognitoUserPool.Arn + Resource: !GetAtt CognitoUserPool0.Arn - CognitoPostAuthenticationTriggerExecutionRole: + CognitoUserMigrationTriggerExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: @@ -919,18 +945,9 @@ Resources: Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - - dynamodb:Scan - - dynamodb:PutItem - Resource: !GetAtt CustomersTable.Arn - - Effect: Allow - Action: - - dynamodb:GetItem - - dynamodb:PutItem - Resource: !GetAtt PreLoginAccountsTable.Arn - - Effect: Allow - Action: - - cognito-idp:AdminAddUserToGroup - Resource: !GetAtt CognitoUserPool.Arn + - cognito-idp:AdminInitiateAuth + - cognito-idp:AdminGetUser + Resource: !GetAtt CognitoUserPool0.Arn CatalogUpdaterLambdaExecutionRole: Type: AWS::IAM::Role @@ -1100,21 +1117,6 @@ Resources: - !Ref ApiGatewayApi - '/*/*' - CognitoPreSignupTriggerFnExecutionPermission: - Type: AWS::Lambda::Permission - Properties: - Action: lambda:InvokeFunction - FunctionName: !GetAtt CognitoPreSignupTriggerFn.Arn - Principal: cognito-idp.amazonaws.com - SourceArn: !Join - - '' - - - 'arn:aws:cognito-idp:' - - !Ref 'AWS::Region' - - ':' - - !Ref 'AWS::AccountId' - - ':userpool/' - - !Ref CognitoUserPool - CognitoPostConfirmationTriggerFnExecutionPermission: Type: AWS::Lambda::Permission Properties: @@ -1128,7 +1130,7 @@ Resources: - ':' - !Ref 'AWS::AccountId' - ':userpool/' - - !Ref CognitoUserPool + - !Ref CognitoUserPool0 CognitoPostAuthenticationTriggerFnExecutionPermission: Type: AWS::Lambda::Permission @@ -1143,7 +1145,7 @@ Resources: - ':' - !Ref 'AWS::AccountId' - ':userpool/' - - !Ref CognitoUserPool + - !Ref CognitoUserPool0 # Marketplace support is currently broken # LambdaSNSExecutionPermission: @@ -1199,7 +1201,7 @@ Resources: FeedbackTableName: !Ref DevPortalFeedbackTableName FeedbackSnsTopicArn: !If [EnableFeedbackSubmission, !Ref FeedbackSubmittedSNSTopic, ''] - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 AdminsGroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']] RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup' DevelopmentMode: !Ref DevelopmentMode @@ -1233,22 +1235,6 @@ Resources: # Layers: # - !Ref LambdaCommonLayer - CognitoPreSignupTriggerFn: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Sub '${AWS::StackName}-CognitoPreSignupTriggerFn' - CodeUri: ../lambdas/cognito-pre-signup-trigger - Handler: index.handler - MemorySize: 128 - Role: !GetAtt CognitoPreSignupTriggerExecutionRole.Arn - Runtime: nodejs12.x - Timeout: 3 - Environment: - Variables: - AccountRegistrationMode: !Ref AccountRegistrationMode - Layers: - - !Ref LambdaCommonLayer - CognitoPostConfirmationTriggerFn: Type: AWS::Serverless::Function Properties: @@ -1285,12 +1271,42 @@ Resources: Layers: - !Ref LambdaCommonLayer + CognitoUserMigrationTriggerFn: + Type: AWS::Serverless::Function + Condition: HasLegacyUserPool + Properties: + FunctionName: !Sub '${AWS::StackName}-CognitoUserMigrationTriggerFn' + CodeUri: ../lambdas/cognito-user-migration-trigger + Handler: index.handler + MemorySize: 128 + Role: !GetAtt CognitoUserMigrationTriggerExecutionRole.Arn + Runtime: nodejs12.x + Timeout: 3 + + # Adjust this and the next pool accordingly any time you make a breaking change to pools. CognitoUserPool: Type: AWS::Cognito::UserPool DeletionPolicy: Retain UpdateReplacePolicy: Retain + Condition: HasLegacyUserPool Properties: UserPoolName: !Ref CognitoIdentityPoolName + Policies: + PasswordPolicy: + MinimumLength: 12 + RequireLowercase: true + RequireNumbers: true + Schema: + - AttributeDataType: String + Name: email + Required: false + + CognitoUserPool0: + Type: AWS::Cognito::UserPool + DeletionPolicy: Retain + UpdateReplacePolicy: Retain + Properties: + UserPoolName: !Ref CognitoIdentityPoolName2 # Lambda trigger caveats: # # - We can't use the functions' ARNs here, because there would be a @@ -1303,10 +1319,6 @@ Resources: # reading: and # LambdaConfig: - PreSignUp: !Join - - '' - - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:' - - !Sub '${AWS::StackName}-CognitoPreSignupTriggerFn' PostConfirmation: !Join - '' - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:' @@ -1315,6 +1327,13 @@ Resources: - '' - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:' - !Sub '${AWS::StackName}-CognitoPostAuthenticationTriggerFn' + UserMigration: !If + - HasLegacyUserPool + - !Join + - '' + - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:' + - !Sub '${AWS::StackName}-CognitoUserMigrationTriggerFn' + - '' Policies: PasswordPolicy: MinimumLength: 12 @@ -1323,6 +1342,7 @@ Resources: Schema: - AttributeDataType: String Name: email + Required: false AdminCreateUserConfig: AllowAdminCreateUserOnly: !If [ InviteAccountRegistrationMode, true, false, @@ -1362,7 +1382,7 @@ Resources: # However, when this is updated and changes, the CUPCS custom resource doesn't re-run, and so a bunch of vital # settings won't be set, e.g., CallbackURL. Properties: - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 ClientName: CognitoIdentityPool GenerateSecret: false RefreshTokenValidity: 30 @@ -1417,7 +1437,7 @@ Resources: Properties: Timeout: 360 ServiceToken: !GetAtt CognitoUserPoolClientSettingsBackingFn.Arn - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 UserPoolClientId: !Ref CognitoUserPoolClient SupportedIdentityProviders: [ "COGNITO" ] # should (eventually) allow people to add values CallbackURL: !If [ LocalDevelopmentMode, @@ -1501,13 +1521,13 @@ Resources: Properties: Timeout: 360 ServiceToken: !GetAtt CognitoUserPoolDomainBackingFn.Arn - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 Domain: !Ref CognitoDomainNameOrPrefix CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Properties: - IdentityPoolName: !Ref CognitoIdentityPoolName + IdentityPoolName: !Ref CognitoIdentityPoolName2 AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref CognitoUserPoolClient @@ -1516,7 +1536,7 @@ Resources: - - cognito-idp. - !Ref 'AWS::Region' - .amazonaws.com/ - - !Ref CognitoUserPool + - !Ref CognitoUserPool0 CognitoIdentityPoolRoles: Type: AWS::Cognito::IdentityPoolRoleAttachment @@ -1650,7 +1670,7 @@ Resources: # since admin group has a precedence of 0, it takes priority Precedence: 0 RoleArn: !GetAtt CognitoAdminRole.Arn - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 CognitoRegisteredGroup: Type: AWS::Cognito::UserPoolGroup @@ -1659,7 +1679,7 @@ Resources: GroupName: !Sub '${AWS::StackName}-RegisteredGroup' Precedence: 1 RoleArn: !GetAtt CognitoRegisteredRole.Arn - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 CatalogUpdaterLambdaFunction: Type: AWS::Serverless::Function @@ -1709,7 +1729,7 @@ Resources: RestApiId: !Ref ApiGatewayApi Region: !Ref 'AWS::Region' IdentityPoolId: !Ref CognitoIdentityPool - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 UserPoolClientId: !Ref CognitoUserPoolClient UserPoolDomain: !GetAtt CognitoUserPoolDomain.FullUrl # Marketplace support is currently broken @@ -1918,7 +1938,7 @@ Resources: Environment: Variables: CustomersTableName: !Ref DevPortalCustomersTableName - UserPoolId: !Ref CognitoUserPool + UserPoolId: !Ref CognitoUserPool0 AdminsGroupName: !Ref CognitoAdminsGroup Layers: - !Ref LambdaCommonLayer @@ -1959,7 +1979,7 @@ Resources: Action: - cognito-idp:ListUsers - cognito-idp:ListUsersInGroup - Resource: !GetAtt CognitoUserPool.Arn + Resource: !GetAtt CognitoUserPool0.Arn Outputs: WebsiteURL: diff --git a/lambdas/cognito-pre-signup-trigger/__tests__/index.js b/lambdas/cognito-pre-signup-trigger/__tests__/index.js deleted file mode 100644 index e8673d688..000000000 --- a/lambdas/cognito-pre-signup-trigger/__tests__/index.js +++ /dev/null @@ -1,28 +0,0 @@ -const index = require('../index') - -describe('Cognito pre-signup trigger', () => { - test('should always confirm users with valid emails', async () => { - const event = { - userName: 'username', - request: { userAttributes: { email: 'username@example.com' } } - } - const result = await index.handler(event) - expect(result).toEqual(event) - }) - - test('should reject users with no emails', async () => { - const event = { - userName: 'username', - request: { userAttributes: {} } - } - expect(index.handler(event)).rejects.toThrow('Email is required.') - }) - - test('should reject users with invalid emails', async () => { - const event = { - userName: 'username', - request: { userAttributes: { email: 'username' } } - } - expect(index.handler(event)).rejects.toThrow('Email is invalid.') - }) -}) diff --git a/lambdas/cognito-pre-signup-trigger/index.js b/lambdas/cognito-pre-signup-trigger/index.js deleted file mode 100644 index c9f2aba24..000000000 --- a/lambdas/cognito-pre-signup-trigger/index.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict' -// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -// This lambda function is attached to the Cognito User Pool's "Pre Sign-up" -// Lambda Trigger, which determines whether a user should be allowed to sign -// up. -// -// Note that, in Invite mode, the `AllowAdminCreateUserOnly` option is -// configured on the user pool's `AllowAdminCreateUserOnly` property. So the -// hosted UI will block the user from signing up, and this lambda will never -// run. - -// Pulled from https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail) and -// optimized in a few ways for size: -// - Classes of `[A-Za-z0-9]` were shortened to the equivalent `[^_\W]`. -// - Other instances of `0-9` in classes were converted to the shorthand `\d`. -// - The whole regexp was made case-insensitive to avoid the need for `A-Za-z` in classes. -// - As we're only testing, I replaced all the non-capturing groups with capturing ones. -// -// This is the same regexp as is used in dev-portal/src/pages/Admin/Accounts/PendingInvites.jsx. -const validEmailRegex = - /^[\w.!#$%&'*+\/=?^`{|}~-]+@[^_\W]([a-z\d-]{0,61}[^_\W])?(\.[^_\W]([a-z\d-]{0,61}[^_\W])?)*$/i - -exports.handler = async event => { - const email = event.request.userAttributes.email - if (email == null) throw new Error('Email is required.') - if (!validEmailRegex.test(email)) throw new Error('Email is invalid.') - - // To block the sign-up from occurring, throw an error. The message will be - // displayed to the user when they attempt to sign up, before Cognito asks - // for confirmation. - - console.info(`In Pre Signup Trigger for username=[${event.userName}] and email=[${email}]`) - - return event -} diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js new file mode 100644 index 000000000..0eab7c34d --- /dev/null +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -0,0 +1,45 @@ +'use strict' + +const AWS = require('aws-sdk') +const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ + apiVersion: '2016-04-18' +}) + +exports.handler = async (event) => { + let authReq + if (event.triggerSource === 'UserMigration_Authentication') { + authReq = cognitoIdentityServiceProvider.adminInitiateAuth({ + AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', + AuthParameters: { + USERNAME: event.userName, + PASSWORD: event.request.password + }, + ClientId: event.callerContext.clientId, + UserPoolId: event.userPoolId + }).promise().then(resp => { + if (resp.code && resp.message) throw new Error('Bad password') + event.response.finalUserStatus = 'CONFIRMED' + }) + } else if (event.triggerSource === 'UserMigration_ForgotPassword') { + // Non-error response required to enable password-reset code to be sent to user + // `event.response.finalUserStatus` is left alone to trigger reset + } else { + throw new Error('Bad triggerSource ' + event.triggerSource) + } + + const [userInfoRequest] = await Promise.all([ + cognitoIdentityServiceProvider.adminGetUser({ + UserPoolId: event.userPoolId, + Username: event.userName + }).promise().then(resp => { + if (resp.code && resp.message) throw new Error('Bad password') + }), + authReq + ]) + + const email = userInfoRequest.UserAttributes.find(e => e.Name === 'email').Value + + event.response.userAttributes = { email, email_verified: 'true' } + event.response.messageAction = 'SUPPRESS' + return event +} diff --git a/scripts/internal/deploy-template.js b/scripts/internal/deploy-template.js index 714b76ec3..265c1d334 100644 --- a/scripts/internal/deploy-template.js +++ b/scripts/internal/deploy-template.js @@ -20,6 +20,7 @@ module.exports = async () => { preLoginAccountsTableName, feedbackTableName, cognitoIdentityPoolName, + legacyCognitoIdentityPoolName, // FIXME: Marketplace support is currently broken // marketplaceSubscriptionTopic, accountRegistrationMode, @@ -54,7 +55,8 @@ module.exports = async () => { ...(customersTableName ? [`DevPortalCustomersTableName=${customersTableName}`] : []), ...(preLoginAccountsTableName ? [`DevPortalPreLoginAccountsTableName=${preLoginAccountsTableName}`] : []), ...(feedbackTableName ? [`DevPortalFeedbackTableName=${feedbackTableName}`] : []), - ...(cognitoIdentityPoolName ? [`CognitoIdentityPoolName=${cognitoIdentityPoolName}`] : []), + ...(cognitoIdentityPoolName ? [`CognitoIdentityPoolName2=${cognitoIdentityPoolName}`] : []), + ...(legacyCognitoIdentityPoolName ? [`CognitoIdentityPoolName=${legacyCognitoIdentityPoolName}`] : []), ...(developmentMode ? [`LocalDevelopmentMode=${developmentMode}`] : []), ...(cognitoDomainName ? [`CognitoDomainNameOrPrefix=${cognitoDomainName}`] : []), // FIXME: Marketplace support is currently broken diff --git a/scripts/internal/get-deployer-config.js b/scripts/internal/get-deployer-config.js index eafa72d77..3570dec89 100644 --- a/scripts/internal/get-deployer-config.js +++ b/scripts/internal/get-deployer-config.js @@ -46,6 +46,7 @@ exports.customersTableName = getOptional('customersTableName') exports.preLoginAccountsTableName = getOptional('preLoginAccountsTableName') exports.feedbackTableName = getOptional('feedbackTableName') exports.cognitoIdentityPoolName = getOptional('cognitoIdentityPoolName') +exports.legacyCognitoIdentityPoolName = getOptional('legacyCognitoIdentityPoolName') // FIXME: Marketplace support is currently broken // exports.marketplaceSubscriptionTopic = getOptional('marketplaceSubscriptionTopic') exports.accountRegistrationMode = getOptional('accountRegistrationMode') From 4a7f479db9099a550e3592c1428beb90a5e03213 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 14:30:00 -0700 Subject: [PATCH 2/9] Make much fuller --- .../cognito-user-migration-trigger/index.js | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index 0eab7c34d..2d9fc6cf4 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -1,14 +1,16 @@ 'use strict' const AWS = require('aws-sdk') -const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ + +exports.cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' }) exports.handler = async (event) => { let authReq + if (event.triggerSource === 'UserMigration_Authentication') { - authReq = cognitoIdentityServiceProvider.adminInitiateAuth({ + authReq = exports.cognitoIdentityServiceProvider.adminInitiateAuth({ AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', AuthParameters: { USERNAME: event.userName, @@ -17,8 +19,30 @@ exports.handler = async (event) => { ClientId: event.callerContext.clientId, UserPoolId: event.userPoolId }).promise().then(resp => { - if (resp.code && resp.message) throw new Error('Bad password') - event.response.finalUserStatus = 'CONFIRMED' + if (resp.AuthenticationResult != null) { + event.response.finalUserStatus = 'CONFIRMED' + } else { + // Username exists, but for whatever reason, the password wasn't sufficient. + // Let's simplify and just force a password reset here. + } + }, e => { + // Mask the obvious errors. + if ( + e.code === 'NotAuthorizedException' || + e.code === 'UserLambdaValidationException' || + e.code === 'UserNotConfirmedException' || + e.code === 'UserNotFoundException' || + e.code === 'MFAMethodNotFoundException' + ) { + throw new Error('Bad username or password') + } + + // Let's go through the standard "forgot password" flow. + if (e.code === 'PasswordResetRequiredException') return + if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') + if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') + console.error(e) + throw new Error('Internal error occurred.') }) } else if (event.triggerSource === 'UserMigration_ForgotPassword') { // Non-error response required to enable password-reset code to be sent to user @@ -27,19 +51,32 @@ exports.handler = async (event) => { throw new Error('Bad triggerSource ' + event.triggerSource) } - const [userInfoRequest] = await Promise.all([ - cognitoIdentityServiceProvider.adminGetUser({ + const [getUserResp] = await Promise.all([ + exports.cognitoIdentityServiceProvider.adminGetUser({ UserPoolId: event.userPoolId, Username: event.userName - }).promise().then(resp => { - if (resp.code && resp.message) throw new Error('Bad password') + }).promise().catch(e => { + // Mask the obvious errors. + if ( + e.code === 'NotAuthorizedException' || + e.code === 'UserNotFoundException' + ) { + throw new Error('Bad username or password') + } + + if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') + if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') + console.error(e) + throw new Error('Internal error occurred.') }), authReq ]) - const email = userInfoRequest.UserAttributes.find(e => e.Name === 'email').Value + const email = getUserResp.UserAttributes.find(e => e.Name === 'email').Value event.response.userAttributes = { email, email_verified: 'true' } event.response.messageAction = 'SUPPRESS' + event.response.desiredDeliveryMediums = 'email' + event.response.forceAliasCreation = true return event } From d52650dcb06f0609756d5bb37c586294683931b0 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 14:32:31 -0700 Subject: [PATCH 3/9] Drop a period --- lambdas/cognito-user-migration-trigger/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index 2d9fc6cf4..dac175bdf 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -42,7 +42,7 @@ exports.handler = async (event) => { if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') console.error(e) - throw new Error('Internal error occurred.') + throw new Error('Internal error occurred') }) } else if (event.triggerSource === 'UserMigration_ForgotPassword') { // Non-error response required to enable password-reset code to be sent to user From 775ce7584815d0c637355837496f26deb355db77 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 14:33:34 -0700 Subject: [PATCH 4/9] Drop a period --- lambdas/cognito-user-migration-trigger/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index dac175bdf..c218703e9 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -67,7 +67,7 @@ exports.handler = async (event) => { if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') console.error(e) - throw new Error('Internal error occurred.') + throw new Error('Internal error occurred') }), authReq ]) From fb79035fc603febce7659df6509d2607628fdda7 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 14:59:17 -0700 Subject: [PATCH 5/9] Streamline error handling --- .../cognito-user-migration-trigger/index.js | 82 +++++++++---------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index c218703e9..8cca7ede2 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -19,30 +19,15 @@ exports.handler = async (event) => { ClientId: event.callerContext.clientId, UserPoolId: event.userPoolId }).promise().then(resp => { - if (resp.AuthenticationResult != null) { - event.response.finalUserStatus = 'CONFIRMED' - } else { + if (resp.AuthenticationResult == null) { // Username exists, but for whatever reason, the password wasn't sufficient. // Let's simplify and just force a password reset here. - } - }, e => { - // Mask the obvious errors. - if ( - e.code === 'NotAuthorizedException' || - e.code === 'UserLambdaValidationException' || - e.code === 'UserNotConfirmedException' || - e.code === 'UserNotFoundException' || - e.code === 'MFAMethodNotFoundException' - ) { - throw new Error('Bad username or password') + const error = new Error() + error.code = 'PasswordResetRequiredException' + throw error } - // Let's go through the standard "forgot password" flow. - if (e.code === 'PasswordResetRequiredException') return - if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') - if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') - console.error(e) - throw new Error('Internal error occurred') + event.response.finalUserStatus = 'CONFIRMED' }) } else if (event.triggerSource === 'UserMigration_ForgotPassword') { // Non-error response required to enable password-reset code to be sent to user @@ -51,32 +36,39 @@ exports.handler = async (event) => { throw new Error('Bad triggerSource ' + event.triggerSource) } - const [getUserResp] = await Promise.all([ - exports.cognitoIdentityServiceProvider.adminGetUser({ - UserPoolId: event.userPoolId, - Username: event.userName - }).promise().catch(e => { - // Mask the obvious errors. - if ( - e.code === 'NotAuthorizedException' || - e.code === 'UserNotFoundException' - ) { - throw new Error('Bad username or password') - } + try { + const [getUserResp] = await Promise.all([ + exports.cognitoIdentityServiceProvider.adminGetUser({ + UserPoolId: event.userPoolId, + Username: event.userName + }).promise(), + authReq + ]) - if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') - if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') - console.error(e) - throw new Error('Internal error occurred') - }), - authReq - ]) + const email = getUserResp.UserAttributes.find(e => e.Name === 'email').Value - const email = getUserResp.UserAttributes.find(e => e.Name === 'email').Value + event.response.userAttributes = { email, email_verified: 'true' } + event.response.messageAction = 'SUPPRESS' + event.response.desiredDeliveryMediums = 'email' + event.response.forceAliasCreation = true + return event + } catch (e) { + // Mask the obvious errors. + if ( + e.code === 'NotAuthorizedException' || + e.code === 'UserLambdaValidationException' || + e.code === 'UserNotConfirmedException' || + e.code === 'UserNotFoundException' || + e.code === 'MFAMethodNotFoundException' + ) { + throw new Error('Bad username or password') + } - event.response.userAttributes = { email, email_verified: 'true' } - event.response.messageAction = 'SUPPRESS' - event.response.desiredDeliveryMediums = 'email' - event.response.forceAliasCreation = true - return event + // Let's go through the standard "forgot password" flow. + if (e.code === 'PasswordResetRequiredException') return + if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') + if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') + console.error(e) + throw new Error('Internal error occurred') + } } From b27eed7a78fddd9697c2966b69c472a4962be9fc Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 15:03:13 -0700 Subject: [PATCH 6/9] Fix names --- cloudformation/template.yaml | 42 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cloudformation/template.yaml b/cloudformation/template.yaml index 78d76f969..e56fb31da 100644 --- a/cloudformation/template.yaml +++ b/cloudformation/template.yaml @@ -18,7 +18,7 @@ Metadata: Label: default: "Dev Portal Customer Configuration" Parameters: - - CognitoIdentityPoolName2 + - CognitoIdentityPoolNameV2 - DevPortalCustomersTableName - AccountRegistrationMode # Marketplace support is currently broken @@ -110,7 +110,7 @@ Parameters: Description: The name for your legacy identity pool. Set this only if you have an existing instance to fail over to and before updating it. Default: '' - CognitoIdentityPoolName2: + CognitoIdentityPoolNameV2: Type: String Description: The name for your Cognito Identity Pool. Default: 'DevPortalIdentityPool' @@ -851,7 +851,7 @@ Resources: - cognito-idp:AdminDeleteUser - cognito-idp:AdminGetUser - cognito-idp:AdminListGroupsForUser - Resource: !GetAtt CognitoUserPool0.Arn + Resource: !GetAtt CognitoUserPoolV2.Arn CognitoPostConfirmationTriggerExecutionRole: Type: AWS::IAM::Role @@ -882,7 +882,7 @@ Resources: - Effect: Allow Action: - cognito-idp:AdminAddUserToGroup - Resource: !GetAtt CognitoUserPool0.Arn + Resource: !GetAtt CognitoUserPoolV2.Arn CognitoPostAuthenticationTriggerExecutionRole: Type: AWS::IAM::Role @@ -919,7 +919,7 @@ Resources: - Effect: Allow Action: - cognito-idp:AdminAddUserToGroup - Resource: !GetAtt CognitoUserPool0.Arn + Resource: !GetAtt CognitoUserPoolV2.Arn CognitoUserMigrationTriggerExecutionRole: Type: AWS::IAM::Role @@ -947,7 +947,7 @@ Resources: Action: - cognito-idp:AdminInitiateAuth - cognito-idp:AdminGetUser - Resource: !GetAtt CognitoUserPool0.Arn + Resource: !GetAtt CognitoUserPoolV2.Arn CatalogUpdaterLambdaExecutionRole: Type: AWS::IAM::Role @@ -1130,7 +1130,7 @@ Resources: - ':' - !Ref 'AWS::AccountId' - ':userpool/' - - !Ref CognitoUserPool0 + - !Ref CognitoUserPoolV2 CognitoPostAuthenticationTriggerFnExecutionPermission: Type: AWS::Lambda::Permission @@ -1145,7 +1145,7 @@ Resources: - ':' - !Ref 'AWS::AccountId' - ':userpool/' - - !Ref CognitoUserPool0 + - !Ref CognitoUserPoolV2 # Marketplace support is currently broken # LambdaSNSExecutionPermission: @@ -1201,7 +1201,7 @@ Resources: FeedbackTableName: !Ref DevPortalFeedbackTableName FeedbackSnsTopicArn: !If [EnableFeedbackSubmission, !Ref FeedbackSubmittedSNSTopic, ''] - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 AdminsGroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']] RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup' DevelopmentMode: !Ref DevelopmentMode @@ -1301,12 +1301,12 @@ Resources: Name: email Required: false - CognitoUserPool0: + CognitoUserPoolV2: Type: AWS::Cognito::UserPool DeletionPolicy: Retain UpdateReplacePolicy: Retain Properties: - UserPoolName: !Ref CognitoIdentityPoolName2 + UserPoolName: !Ref CognitoIdentityPoolNameV2 # Lambda trigger caveats: # # - We can't use the functions' ARNs here, because there would be a @@ -1382,7 +1382,7 @@ Resources: # However, when this is updated and changes, the CUPCS custom resource doesn't re-run, and so a bunch of vital # settings won't be set, e.g., CallbackURL. Properties: - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 ClientName: CognitoIdentityPool GenerateSecret: false RefreshTokenValidity: 30 @@ -1437,7 +1437,7 @@ Resources: Properties: Timeout: 360 ServiceToken: !GetAtt CognitoUserPoolClientSettingsBackingFn.Arn - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 UserPoolClientId: !Ref CognitoUserPoolClient SupportedIdentityProviders: [ "COGNITO" ] # should (eventually) allow people to add values CallbackURL: !If [ LocalDevelopmentMode, @@ -1521,13 +1521,13 @@ Resources: Properties: Timeout: 360 ServiceToken: !GetAtt CognitoUserPoolDomainBackingFn.Arn - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 Domain: !Ref CognitoDomainNameOrPrefix CognitoIdentityPool: Type: AWS::Cognito::IdentityPool Properties: - IdentityPoolName: !Ref CognitoIdentityPoolName2 + IdentityPoolName: !Ref CognitoIdentityPoolNameV2 AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - ClientId: !Ref CognitoUserPoolClient @@ -1536,7 +1536,7 @@ Resources: - - cognito-idp. - !Ref 'AWS::Region' - .amazonaws.com/ - - !Ref CognitoUserPool0 + - !Ref CognitoUserPoolV2 CognitoIdentityPoolRoles: Type: AWS::Cognito::IdentityPoolRoleAttachment @@ -1670,7 +1670,7 @@ Resources: # since admin group has a precedence of 0, it takes priority Precedence: 0 RoleArn: !GetAtt CognitoAdminRole.Arn - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 CognitoRegisteredGroup: Type: AWS::Cognito::UserPoolGroup @@ -1679,7 +1679,7 @@ Resources: GroupName: !Sub '${AWS::StackName}-RegisteredGroup' Precedence: 1 RoleArn: !GetAtt CognitoRegisteredRole.Arn - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 CatalogUpdaterLambdaFunction: Type: AWS::Serverless::Function @@ -1729,7 +1729,7 @@ Resources: RestApiId: !Ref ApiGatewayApi Region: !Ref 'AWS::Region' IdentityPoolId: !Ref CognitoIdentityPool - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 UserPoolClientId: !Ref CognitoUserPoolClient UserPoolDomain: !GetAtt CognitoUserPoolDomain.FullUrl # Marketplace support is currently broken @@ -1938,7 +1938,7 @@ Resources: Environment: Variables: CustomersTableName: !Ref DevPortalCustomersTableName - UserPoolId: !Ref CognitoUserPool0 + UserPoolId: !Ref CognitoUserPoolV2 AdminsGroupName: !Ref CognitoAdminsGroup Layers: - !Ref LambdaCommonLayer @@ -1979,7 +1979,7 @@ Resources: Action: - cognito-idp:ListUsers - cognito-idp:ListUsersInGroup - Resource: !GetAtt CognitoUserPool0.Arn + Resource: !GetAtt CognitoUserPoolV2.Arn Outputs: WebsiteURL: From ddaa3d36076b04a65d8357b801a9275bea51546c Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 15:09:56 -0700 Subject: [PATCH 7/9] Correct some error handling --- lambdas/cognito-user-migration-trigger/index.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index 8cca7ede2..609620b56 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -22,16 +22,18 @@ exports.handler = async (event) => { if (resp.AuthenticationResult == null) { // Username exists, but for whatever reason, the password wasn't sufficient. // Let's simplify and just force a password reset here. - const error = new Error() - error.code = 'PasswordResetRequiredException' - throw error + event.response.finalUserStatus = 'RESET_REQUIRED' + } else { + event.response.finalUserStatus = 'CONFIRMED' } - - event.response.finalUserStatus = 'CONFIRMED' + }, e => { + // Let's go through the standard "forgot password" flow for these errors. + if (e.code !== 'PasswordResetRequiredException') throw e + event.response.finalUserStatus = 'RESET_REQUIRED' }) } else if (event.triggerSource === 'UserMigration_ForgotPassword') { // Non-error response required to enable password-reset code to be sent to user - // `event.response.finalUserStatus` is left alone to trigger reset + event.response.finalUserStatus = 'RESET_REQUIRED' } else { throw new Error('Bad triggerSource ' + event.triggerSource) } @@ -64,8 +66,6 @@ exports.handler = async (event) => { throw new Error('Bad username or password') } - // Let's go through the standard "forgot password" flow. - if (e.code === 'PasswordResetRequiredException') return if (e.code === 'InvalidParameterException') throw new Error('Invalid parameter') if (e.code === 'TooManyRequestsException') throw new Error('Too many requests') console.error(e) From 0b4c0493d39565f6078d62e9bb6489ac537ceee6 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Tue, 23 Jun 2020 16:54:58 -0700 Subject: [PATCH 8/9] Write tests --- .../__tests__/index.js | 2141 +++++++++++++++++ .../cognito-user-migration-trigger/index.js | 2 +- 2 files changed, 2142 insertions(+), 1 deletion(-) create mode 100644 lambdas/cognito-user-migration-trigger/__tests__/index.js diff --git a/lambdas/cognito-user-migration-trigger/__tests__/index.js b/lambdas/cognito-user-migration-trigger/__tests__/index.js new file mode 100644 index 000000000..67c352fd5 --- /dev/null +++ b/lambdas/cognito-user-migration-trigger/__tests__/index.js @@ -0,0 +1,2141 @@ +'use strict' + +const index = require('../index') +const { promiser, bindMock } = require('../../setup-jest') + +describe('cognito-user-migration-trigger', () => { + // Note: many of these are byzantine tests that might seem useless, but they exist purely for completeness. + + const setMock = bindMock() + + const TEST_CLIENT_ID = `Client-${Math.random()}` + const TEST_POOL_ID = `Pool-${Math.random()}` + const TEST_USER_EMAIL = `user-${Math.random()}@example.com` + const TEST_PASS = `password-${Math.random()}` + + function makeEvent (type) { + return { + triggerSource: type, + userName: TEST_USER_EMAIL, + request: { + password: TEST_PASS + }, + callerContext: { + clientId: TEST_CLIENT_ID + }, + userPoolId: TEST_POOL_ID, + response: { + finalUserStatus: null, + userAttributes: null, + messageAction: null, + desiredDeliveryMediums: null, + forceAliasCreation: null + } + } + } + + function fail (code) { + const error = new Error(`Request error (${code})`) + error.code = code + return promiser(null, error) + } + function passUser () { + return promiser({ + UserAttributes: [{ Name: 'email', Value: TEST_USER_EMAIL }] + }) + } + + describe('UserMigration_Authentication', () => { + function passAuth () { + return promiser({ AuthenticationResult: {} }) + } + + function passNoAuth () { + return promiser({ AuthenticationResult: null }) + } + + function passDelayedAuth () { + return promiser(new Promise(resolve => setTimeout(() => resolve({ AuthenticationResult: {} }), 0))) + } + + function passDelayedNoAuth () { + return promiser(new Promise(resolve => setTimeout(() => resolve({ AuthenticationResult: null }), 0))) + } + + function failDelayed (code) { + const error = new Error(`Request error (${code})`) + error.code = code + return promiser(new Promise((resolve, reject) => setTimeout(() => reject(error), 0))) + } + + function passDelayedUser () { + return promiser(new Promise(resolve => setTimeout(() => resolve({ + UserAttributes: [{ Name: 'email', Value: TEST_USER_EMAIL }] + }), 0))) + } + + async function checkFail ({ adminInitiateAuth, adminGetUser, message }) { + const oldConsoleError = console.error + // Ignore logged errors in the tests + if (message === 'Internal error occurred') console.error = () => { } + + try { + setMock(index.cognitoIdentityServiceProvider, 'adminInitiateAuth').mockReturnValue(adminInitiateAuth) + setMock(index.cognitoIdentityServiceProvider, 'adminGetUser').mockReturnValue(adminGetUser) + + const event = makeEvent('UserMigration_Authentication') + await expect(index.handler(event)).rejects.toThrow(message) + + expect(index.cognitoIdentityServiceProvider.adminInitiateAuth).toBeCalledWith( + expect.objectContaining({ + AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', + AuthParameters: { + USERNAME: event.userName, + PASSWORD: event.request.password + }, + ClientId: event.callerContext.clientId, + UserPoolId: event.userPoolId + }) + ) + + expect(index.cognitoIdentityServiceProvider.adminGetUser).toBeCalledWith( + expect.objectContaining({ + UserPoolId: event.userPoolId, + Username: event.userName + }) + ) + } finally { + console.error = oldConsoleError + } + } + + async function checkPass ({ adminInitiateAuth, adminGetUser, status }) { + setMock(index.cognitoIdentityServiceProvider, 'adminInitiateAuth').mockReturnValue(adminInitiateAuth) + setMock(index.cognitoIdentityServiceProvider, 'adminGetUser').mockReturnValue(adminGetUser) + + const event = makeEvent('UserMigration_Authentication') + const result = await index.handler(event) + + expect(result).toBe(event) + + expect(event.response.finalUserStatus).toBe(status) + expect(event.response.userAttributes.email).toBe(TEST_USER_EMAIL) + expect(event.response.userAttributes.email_verified).toBe('true') + expect(event.response.messageAction).toBe('SUPPRESS') + expect(event.response.forceAliasCreation).toBe(true) + + expect(index.cognitoIdentityServiceProvider.adminInitiateAuth).toBeCalledWith( + expect.objectContaining({ + AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', + AuthParameters: { + USERNAME: event.userName, + PASSWORD: event.request.password + }, + ClientId: event.callerContext.clientId, + UserPoolId: event.userPoolId + }) + ) + + expect(index.cognitoIdentityServiceProvider.adminGetUser).toBeCalledWith( + expect.objectContaining({ + UserPoolId: event.userPoolId, + Username: event.userName + }) + ) + } + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws InternalErrorException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed NotAuthorizedException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('NotAuthorizedException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserLambdaValidationException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserLambdaValidationException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserNotConfirmedException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotConfirmedException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed UserNotFoundException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('UserNotFoundException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed MFAMethodNotFoundException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('MFAMethodNotFoundException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed InvalidParameterException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('InvalidParameterException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed TooManyRequestsException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('TooManyRequestsException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed InternalErrorException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('InternalErrorException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws delayed SomeOtherException, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: failDelayed('SomeOtherException'), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns delayed auth result, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: passDelayedAuth(), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns delayed no auth result, adminGetUser throws SomeOtherException', () => + checkFail({ + adminInitiateAuth: passDelayedNoAuth(), + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed NotAuthorizedException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed UserNotFoundException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed InvalidParameterException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed TooManyRequestsException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed InternalErrorException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: passAuth(), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser throws delayed SomeOtherException', () => + checkFail({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: failDelayed('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: passUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: passUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: passUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: passUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: passUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: passUser(), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: passUser(), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: passUser(), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser returns user attributes', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: passUser(), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser returns user attributes', () => + checkPass({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: passUser(), + status: 'RESET_REQUIRED' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser returns user attributes', () => + checkPass({ + adminInitiateAuth: passAuth(), + adminGetUser: passUser(), + status: 'CONFIRMED' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser returns user attributes', () => + checkPass({ + adminInitiateAuth: passNoAuth(), + adminGetUser: passUser(), + status: 'RESET_REQUIRED' + }) + ) + + test('adminInitiateAuth throws NotAuthorizedException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('NotAuthorizedException'), + adminGetUser: passDelayedUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserLambdaValidationException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserLambdaValidationException'), + adminGetUser: passDelayedUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotConfirmedException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserNotConfirmedException'), + adminGetUser: passDelayedUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws UserNotFoundException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('UserNotFoundException'), + adminGetUser: passDelayedUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws MFAMethodNotFoundException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('MFAMethodNotFoundException'), + adminGetUser: passDelayedUser(), + message: 'Bad username or password' + }) + ) + + test('adminInitiateAuth throws InvalidParameterException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('InvalidParameterException'), + adminGetUser: passDelayedUser(), + message: 'Invalid parameter' + }) + ) + + test('adminInitiateAuth throws TooManyRequestsException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('TooManyRequestsException'), + adminGetUser: passDelayedUser(), + message: 'Too many requests' + }) + ) + + test('adminInitiateAuth throws InternalErrorException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('InternalErrorException'), + adminGetUser: passDelayedUser(), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws SomeOtherException, adminGetUser returns delayed user attributes', () => + checkFail({ + adminInitiateAuth: fail('SomeOtherException'), + adminGetUser: passDelayedUser(), + message: 'Internal error occurred' + }) + ) + + test('adminInitiateAuth throws PasswordResetRequiredException, adminGetUser returns delayed user attributes', () => + checkPass({ + adminInitiateAuth: fail('PasswordResetRequiredException'), + adminGetUser: passDelayedUser(), + status: 'RESET_REQUIRED' + }) + ) + + test('adminInitiateAuth returns auth result, adminGetUser returns delayed user attributes', () => + checkPass({ + adminInitiateAuth: passAuth(), + adminGetUser: passDelayedUser(), + status: 'CONFIRMED' + }) + ) + + test('adminInitiateAuth returns no auth result, adminGetUser returns delayed user attributes', () => + checkPass({ + adminInitiateAuth: passNoAuth(), + adminGetUser: passDelayedUser(), + status: 'RESET_REQUIRED' + }) + ) + }) + + describe('UserMigration_ForgotPassword', () => { + async function checkFail ({ adminGetUser, message }) { + const oldConsoleError = console.error + // Ignore logged errors in the tests + if (message === 'Internal error occurred') console.error = () => { } + + try { + setMock(index.cognitoIdentityServiceProvider, 'adminInitiateAuth').mockReturnValue(promiser({ AuthenticationResult: 'dummy' })) + setMock(index.cognitoIdentityServiceProvider, 'adminGetUser').mockReturnValue(adminGetUser) + + const event = makeEvent('UserMigration_ForgotPassword') + await expect(index.handler(event)).rejects.toThrow(message) + + expect(index.cognitoIdentityServiceProvider.adminInitiateAuth).not.toHaveBeenCalled() + + expect(index.cognitoIdentityServiceProvider.adminGetUser).toBeCalledWith( + expect.objectContaining({ + UserPoolId: event.userPoolId, + Username: event.userName + }) + ) + } finally { + console.error = oldConsoleError + } + } + + async function checkPass ({ adminGetUser, status }) { + setMock(index.cognitoIdentityServiceProvider, 'adminInitiateAuth').mockReturnValue(promiser({ AuthenticationResult: 'dummy' })) + setMock(index.cognitoIdentityServiceProvider, 'adminGetUser').mockReturnValue(adminGetUser) + + const event = makeEvent('UserMigration_ForgotPassword') + const result = await index.handler(event) + + expect(result).toBe(event) + + expect(event.response.finalUserStatus).toBe(status) + expect(event.response.userAttributes.email).toBe(TEST_USER_EMAIL) + expect(event.response.userAttributes.email_verified).toBe('true') + expect(event.response.messageAction).toBe('SUPPRESS') + expect(event.response.forceAliasCreation).toBe(true) + + expect(index.cognitoIdentityServiceProvider.adminInitiateAuth).not.toHaveBeenCalled() + + expect(index.cognitoIdentityServiceProvider.adminGetUser).toBeCalledWith( + expect.objectContaining({ + UserPoolId: event.userPoolId, + Username: event.userName + }) + ) + } + + test('adminGetUser throws NotAuthorizedException', () => + checkFail({ + adminGetUser: fail('NotAuthorizedException'), + message: 'Bad username or password' + }) + ) + + test('adminGetUser throws UserNotFoundException', () => + checkFail({ + adminGetUser: fail('UserNotFoundException'), + message: 'Bad username or password' + }) + ) + + test('adminGetUser throws InvalidParameterException', () => + checkFail({ + adminGetUser: fail('InvalidParameterException'), + message: 'Invalid parameter' + }) + ) + + test('adminGetUser throws TooManyRequestsException', () => + checkFail({ + adminGetUser: fail('TooManyRequestsException'), + message: 'Too many requests' + }) + ) + + test('adminGetUser throws InternalErrorException', () => + checkFail({ + adminGetUser: fail('InternalErrorException'), + message: 'Internal error occurred' + }) + ) + + test('adminGetUser throws SomeOtherException', () => + checkFail({ + adminGetUser: fail('SomeOtherException'), + message: 'Internal error occurred' + }) + ) + + test('adminGetUser returns user attributes', () => + checkPass({ + adminGetUser: passUser(), + status: 'RESET_REQUIRED' + }) + ) + }) + + describe('Ohter trigger sources', () => { + test('throws exception based on source', async () => { + const oldConsoleError = console.error + // Ignore logged errors in the tests + console.error = () => { } + + try { + setMock(index.cognitoIdentityServiceProvider, 'adminInitiateAuth').mockReturnValue(promiser({ AuthenticationResult: 'dummy' })) + setMock(index.cognitoIdentityServiceProvider, 'adminGetUser').mockReturnValue(promiser({ UserAttributes: 'dummy' })) + + const event = makeEvent('Invalid_Trigger_Source') + await expect(index.handler(event)).rejects.toThrow('Bad trigger source: Invalid_Trigger_Source') + + expect(index.cognitoIdentityServiceProvider.adminInitiateAuth).not.toHaveBeenCalled() + expect(index.cognitoIdentityServiceProvider.adminGetUser).not.toHaveBeenCalled() + } finally { + console.error = oldConsoleError + } + }) + }) +}) diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index 609620b56..7dbd20817 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -35,7 +35,7 @@ exports.handler = async (event) => { // Non-error response required to enable password-reset code to be sent to user event.response.finalUserStatus = 'RESET_REQUIRED' } else { - throw new Error('Bad triggerSource ' + event.triggerSource) + throw new Error(`Bad trigger source: ${event.triggerSource}`) } try { From f78e6946dc165f21e06a29ed26f75025ab1d0e36 Mon Sep 17 00:00:00 2001 From: amazon-meaisiah Date: Wed, 24 Jun 2020 04:01:47 -0700 Subject: [PATCH 9/9] Fix outdated refs, simplify template --- cloudformation/template.yaml | 186 ++++++------------ .../index.js | 49 ----- .../cfn-cognito-user-pools-domain/index.js | 86 -------- .../index.js | 4 + .../index.js | 16 +- .../cognito-user-migration-trigger/index.js | 63 ++++-- .../migrate-group-membership.js | 47 +++++ scripts/internal/deploy-template.js | 2 +- 8 files changed, 169 insertions(+), 284 deletions(-) delete mode 100644 lambdas/cfn-cognito-user-pools-client-settings/index.js delete mode 100644 lambdas/cfn-cognito-user-pools-domain/index.js create mode 100644 lambdas/common-layer/nodejs/node_modules/dev-portal-common/migrate-group-membership.js diff --git a/cloudformation/template.yaml b/cloudformation/template.yaml index e56fb31da..f6f9c4706 100644 --- a/cloudformation/template.yaml +++ b/cloudformation/template.yaml @@ -181,7 +181,7 @@ Conditions: NotDevelopmentMode: !Not [!Condition DevelopmentMode] InUSEastOne: !Equals [!Ref 'AWS::Region', 'us-east-1'] InviteAccountRegistrationMode: !Equals [!Ref AccountRegistrationMode, 'invite'] - HasLegacyUserPool: !Equals [!Ref CognitoIdentityPoolName, ''] + HasLegacyUserPool: !Not [!Equals [!Ref CognitoIdentityPoolName, '']] Resources: ApiGatewayApi: @@ -882,6 +882,7 @@ Resources: - Effect: Allow Action: - cognito-idp:AdminAddUserToGroup + - cognito-idp:AdminDeleteUserAttributes Resource: !GetAtt CognitoUserPoolV2.Arn CognitoPostAuthenticationTriggerExecutionRole: @@ -919,6 +920,7 @@ Resources: - Effect: Allow Action: - cognito-idp:AdminAddUserToGroup + - cognito-idp:AdminDeleteUserAttributes Resource: !GetAtt CognitoUserPoolV2.Arn CognitoUserMigrationTriggerExecutionRole: @@ -946,7 +948,8 @@ Resources: - Effect: Allow Action: - cognito-idp:AdminInitiateAuth - - cognito-idp:AdminGetUser + - cognito-idp:AdminListGroupsForUser + - cognito-idp:ListUsers Resource: !GetAtt CognitoUserPoolV2.Arn CatalogUpdaterLambdaExecutionRole: @@ -1201,9 +1204,10 @@ Resources: FeedbackTableName: !Ref DevPortalFeedbackTableName FeedbackSnsTopicArn: !If [EnableFeedbackSubmission, !Ref FeedbackSubmittedSNSTopic, ''] - UserPoolId: !Ref CognitoUserPoolV2 - AdminsGroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']] - RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup' + # Break a circular dependency + UserPoolId: !Ref CognitoIdentityPoolNameV2 + AdminsGroupName: !Sub '${AWS::StackName}-AdminsGroupV2' + RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroupV2' DevelopmentMode: !Ref DevelopmentMode # Adds the API as a trigger Events: @@ -1249,7 +1253,9 @@ Resources: Variables: AccountRegistrationMode: !Ref AccountRegistrationMode PreLoginAccountsTableName: !Ref DevPortalPreLoginAccountsTableName - RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup' + UserPoolId: !Ref CognitoUserPoolV2 + AdminsGroupName: !Ref CognitoAdminsGroupV2 + RegisteredGroupName: !Ref CognitoRegisteredGroupV2 Layers: - !Ref LambdaCommonLayer @@ -1267,7 +1273,9 @@ Resources: Variables: CustomersTableName: !Ref DevPortalCustomersTableName PreLoginAccountsTableName: !Ref DevPortalPreLoginAccountsTableName - RegisteredGroupName: !Sub '${AWS::StackName}-RegisteredGroup' + UserPoolId: !Ref CognitoUserPoolV2 + AdminsGroupName: !Ref CognitoAdminsGroupV2 + RegisteredGroupName: !Ref CognitoRegisteredGroupV2 Layers: - !Ref LambdaCommonLayer @@ -1282,6 +1290,11 @@ Resources: Role: !GetAtt CognitoUserMigrationTriggerExecutionRole.Arn Runtime: nodejs12.x Timeout: 3 + Environment: + Variables: + OldUserPool: !Ref CognitoIdentityPoolName + OldAdminsGroup: !Ref CognitoAdminsGroup + OldRegisteredGroup: !Ref CognitoRegisteredGroup # Adjust this and the next pool accordingly any time you make a breaking change to pools. CognitoUserPool: @@ -1333,7 +1346,7 @@ Resources: - '' - - !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:' - !Sub '${AWS::StackName}-CognitoUserMigrationTriggerFn' - - '' + - !Ref AWS::NoValue Policies: PasswordPolicy: MinimumLength: 12 @@ -1342,7 +1355,9 @@ Resources: Schema: - AttributeDataType: String Name: email - Required: false + Required: true + - AttributeDataType: String + Name: custom:groups AdminCreateUserConfig: AllowAdminCreateUserOnly: !If [ InviteAccountRegistrationMode, true, false, @@ -1375,72 +1390,16 @@ Resources: EmailVerificationSubject: 'Developer Portal - Verification Code' EmailVerificationMessage: '

Developer Portal

Your verification code is {####}

' - CognitoUserPoolClient: + CognitoUserPoolClientV2: Type: AWS::Cognito::UserPoolClient - # It's really unintuitive, but changing any of the properties here will cause stack updates to deploy non-functionally. - # The CognitoUserPoolClientSettings custom resource runs after this resource and adds a bunch of fields. - # However, when this is updated and changes, the CUPCS custom resource doesn't re-run, and so a bunch of vital - # settings won't be set, e.g., CallbackURL. Properties: UserPoolId: !Ref CognitoUserPoolV2 ClientName: CognitoIdentityPool GenerateSecret: false RefreshTokenValidity: 30 PreventUserExistenceErrors: ENABLED - - CognitoUserPoolClientSettingsBackingFnRole: - Type: 'AWS::IAM::Role' - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: 'sts:AssumeRole' - Principal: - Service: lambda.amazonaws.com - Policies: - - PolicyName: WriteCloudWatchLogs - PolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: - - 'logs:CreateLogGroup' - - 'logs:CreateLogStream' - - 'logs:PutLogEvents' - Resource: 'arn:aws:logs:*:*:*' - - PolicyName: UpdateUserPoolClient - PolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: 'cognito-idp:UpdateUserPoolClient' - Resource: 'arn:aws:cognito-idp:*:*:userpool/*' - - CognitoUserPoolClientSettingsBackingFn: - Type: AWS::Serverless::Function - Properties: - Runtime: nodejs12.x - MemorySize: 128 - Timeout: 300 - CodeUri: ../lambdas/cfn-cognito-user-pools-client-settings - Handler: index.handler - Role: !GetAtt CognitoUserPoolClientSettingsBackingFnRole.Arn - Layers: - - !Ref LambdaCommonLayer - - CognitoUserPoolClientSettings: - Type: AWS::CloudFormation::CustomResource - Properties: - Timeout: 360 - ServiceToken: !GetAtt CognitoUserPoolClientSettingsBackingFn.Arn - UserPoolId: !Ref CognitoUserPoolV2 - UserPoolClientId: !Ref CognitoUserPoolClient SupportedIdentityProviders: [ "COGNITO" ] # should (eventually) allow people to add values - CallbackURL: !If [ LocalDevelopmentMode, + CallbackURLs: !If [ LocalDevelopmentMode, [ 'http://localhost:3000/index.html?action=login', !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html?action=login' ]], @@ -1450,7 +1409,7 @@ Resources: !Join [ '', [ 'https://', !If [ NoCustomDomainName, !GetAtt DefaultCloudfrontDistribution.DomainName, !Ref CustomDomainName ], '/index.html?action=login' ]] ] ] - LogoutURL: !If [ LocalDevelopmentMode, + LogoutURLs: !If [ LocalDevelopmentMode, [ 'http://localhost:3000/index.html?action=logout', !Join [ '', [ 'https://', !GetAtt DevPortalSiteS3Bucket.RegionalDomainName, '/index.html?action=logout' ]], @@ -1464,63 +1423,9 @@ Resources: AllowedOAuthFlows: [ "implicit" ] AllowedOAuthScopes: [ "openid" ] - CognitoUserPoolDomainBackingFnRole: - Type: 'AWS::IAM::Role' - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: 'sts:AssumeRole' - Principal: - Service: lambda.amazonaws.com - Policies: - - PolicyName: WriteCloudWatchLogs - PolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: - - 'logs:CreateLogGroup' - - 'logs:CreateLogStream' - - 'logs:PutLogEvents' - Resource: 'arn:aws:logs:*:*:*' - - PolicyName: ManageUserPoolDomain - PolicyDocument: - Version: '2012-10-17' - Statement: - - - Effect: Allow - Action: 'cognito-idp:CreateUserPoolDomain' - Resource: 'arn:aws:cognito-idp:*:*:userpool/*' - - - Effect: Allow - Action: 'cognito-idp:DeleteUserPoolDomain' - Resource: 'arn:aws:cognito-idp:*:*:userpool/*' - - - Effect: Allow - Action: 'cognito-idp:DescribeUserPoolDomain' - Resource: '*' - - CognitoUserPoolDomainBackingFn: - Type: AWS::Serverless::Function - Properties: - Runtime: nodejs12.x - MemorySize: 128 - Timeout: 300 - CodeUri: ../lambdas/cfn-cognito-user-pools-domain - Handler: index.handler - Role: !GetAtt CognitoUserPoolDomainBackingFnRole.Arn - Layers: - - !Ref LambdaCommonLayer - - CognitoUserPoolDomain: - Type: AWS::CloudFormation::CustomResource + CognitoUserPoolDomainV2: + Type: AWS::Cognito::UserPoolDomain Properties: - Timeout: 360 - ServiceToken: !GetAtt CognitoUserPoolDomainBackingFn.Arn UserPoolId: !Ref CognitoUserPoolV2 Domain: !Ref CognitoDomainNameOrPrefix @@ -1530,7 +1435,7 @@ Resources: IdentityPoolName: !Ref CognitoIdentityPoolNameV2 AllowUnauthenticatedIdentities: false CognitoIdentityProviders: - - ClientId: !Ref CognitoUserPoolClient + - ClientId: !Ref CognitoUserPoolClientV2 ProviderName: !Join - '' - - cognito-idp. @@ -1663,6 +1568,7 @@ Resources: CognitoAdminsGroup: Type: AWS::Cognito::UserPoolGroup + Condition: HasLegacyUserPool Properties: Description: 'Admin users of the developer portal' GroupName: !Join ['', [!Ref 'AWS::StackName', 'AdminsGroup']] @@ -1670,15 +1576,37 @@ Resources: # since admin group has a precedence of 0, it takes priority Precedence: 0 RoleArn: !GetAtt CognitoAdminRole.Arn - UserPoolId: !Ref CognitoUserPoolV2 + UserPoolId: !Ref CognitoUserPool CognitoRegisteredGroup: Type: AWS::Cognito::UserPoolGroup + Condition: HasLegacyUserPool Properties: Description: 'Registered users in the developer portal' GroupName: !Sub '${AWS::StackName}-RegisteredGroup' Precedence: 1 RoleArn: !GetAtt CognitoRegisteredRole.Arn + UserPoolId: !Ref CognitoUserPool + + CognitoAdminsGroupV2: + Type: AWS::Cognito::UserPoolGroup + Properties: + Description: 'Admin users of the developer portal' + # For easy location until we can pre-make an admin + GroupName: !Sub '${AWS::StackName}-AdminsGroupV2' + # the role we assume is the role associated with the lowest-precedence users group + # since admin group has a precedence of 0, it takes priority + Precedence: 0 + RoleArn: !GetAtt CognitoAdminRole.Arn + UserPoolId: !Ref CognitoUserPoolV2 + + CognitoRegisteredGroupV2: + Type: AWS::Cognito::UserPoolGroup + Properties: + Description: 'Registered users in the developer portal' + GroupName: !Sub '${AWS::StackName}-RegisteredGroupV2' + Precedence: 1 + RoleArn: !GetAtt CognitoRegisteredRole.Arn UserPoolId: !Ref CognitoUserPoolV2 CatalogUpdaterLambdaFunction: @@ -1730,8 +1658,8 @@ Resources: Region: !Ref 'AWS::Region' IdentityPoolId: !Ref CognitoIdentityPool UserPoolId: !Ref CognitoUserPoolV2 - UserPoolClientId: !Ref CognitoUserPoolClient - UserPoolDomain: !GetAtt CognitoUserPoolDomain.FullUrl + UserPoolClientId: !Ref CognitoUserPoolClientV2 + UserPoolDomain: !Sub 'https://${CognitoDomainNameOrPrefix}.auth.${AWS::Region}.amazoncognito.com' # Marketplace support is currently broken # MarketplaceSuffix: !Ref MarketplaceSubscriptionTopicProductCode RebuildToken: !Ref StaticAssetRebuildToken @@ -1939,7 +1867,7 @@ Resources: Variables: CustomersTableName: !Ref DevPortalCustomersTableName UserPoolId: !Ref CognitoUserPoolV2 - AdminsGroupName: !Ref CognitoAdminsGroup + AdminsGroupName: !Ref CognitoAdminsGroupV2 Layers: - !Ref LambdaCommonLayer diff --git a/lambdas/cfn-cognito-user-pools-client-settings/index.js b/lambdas/cfn-cognito-user-pools-client-settings/index.js deleted file mode 100644 index 14a1d075f..000000000 --- a/lambdas/cfn-cognito-user-pools-client-settings/index.js +++ /dev/null @@ -1,49 +0,0 @@ -const AWS = require('aws-sdk') -const notifyCFN = require('dev-portal-common/notify-cfn') - -exports.handler = async (event, context) => { - try { - let responseData - switch (event.RequestType) { - case 'Create': - case 'Update': - var cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider() - - responseData = await cognitoIdentityServiceProvider.updateUserPoolClient({ - UserPoolId: event.ResourceProperties.UserPoolId, - ClientId: event.ResourceProperties.UserPoolClientId, - SupportedIdentityProviders: event.ResourceProperties.SupportedIdentityProviders, - // make sure these are array-wrapped, but not double-wrapped - CallbackURLs: Array.isArray(event.ResourceProperties.CallbackURL) - ? event.ResourceProperties.CallbackURL - : [event.ResourceProperties.CallbackURL], - LogoutURLs: Array.isArray(event.ResourceProperties.LogoutURL) - ? event.ResourceProperties.LogoutURL - : [event.ResourceProperties.LogoutURL], - AllowedOAuthFlowsUserPoolClient: (event.ResourceProperties.AllowedOAuthFlowsUserPoolClient === 'true'), - AllowedOAuthFlows: event.ResourceProperties.AllowedOAuthFlows, - AllowedOAuthScopes: event.ResourceProperties.AllowedOAuthScopes - }).promise() - - break - - case 'Delete': break // these are just extra settings on a userPoolClient, so we don't actually do a delete action - } - - // trim a useless layer of JSON - if (responseData && responseData.UserPoolClient) { responseData = responseData.UserPoolClient } - - // try to use the User Pool id (plus '-Settings') as the ID - let physicalResourceId - - if (responseData && responseData.UserPoolId) { physicalResourceId = responseData.UserPoolId + '-Settings' } - - await notifyCFN.ofSuccess({ event, context, responseData, physicalResourceId }) - - console.info(`CognitoUserPoolClientSettings Success for request type ${event.RequestType}`) - } catch (error) { - await notifyCFN.ofFailure({ error, event, context }) - - console.error(`CognitoUserPoolClientSettings Error for request type ${event.RequestType}:`, error) - } -} diff --git a/lambdas/cfn-cognito-user-pools-domain/index.js b/lambdas/cfn-cognito-user-pools-domain/index.js deleted file mode 100644 index 0fd68fcf0..000000000 --- a/lambdas/cfn-cognito-user-pools-domain/index.js +++ /dev/null @@ -1,86 +0,0 @@ -const AWS = require('aws-sdk') -const notifyCFN = require('dev-portal-common/notify-cfn') - -exports.handler = async (event, context) => { - try { - var cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider() - let responseData - - switch (event.RequestType) { - case 'Create': - await cognitoIdentityServiceProvider.createUserPoolDomain({ - UserPoolId: event.ResourceProperties.UserPoolId, - Domain: event.ResourceProperties.Domain - }).promise() - - responseData = await cognitoIdentityServiceProvider.describeUserPoolDomain({ - Domain: event.ResourceProperties.Domain - }).promise() - - break - - case 'Update': - // The only way to change the -domain- on a domain (as opposed to the cert) - // is to delete and recreate. we're making the assumption that an update is - // for a new domain. When we support custom domains, would be a good idea - // to make this logic a bit smarter. - await deleteUserPoolDomain(cognitoIdentityServiceProvider, event.OldResourceProperties.Domain) - - await cognitoIdentityServiceProvider.createUserPoolDomain({ - UserPoolId: event.ResourceProperties.UserPoolId, - Domain: event.ResourceProperties.Domain - }).promise() - - responseData = await cognitoIdentityServiceProvider.describeUserPoolDomain({ - Domain: event.ResourceProperties.Domain - }).promise() - - break - - case 'Delete': - await deleteUserPoolDomain(cognitoIdentityServiceProvider, event.ResourceProperties.Domain) - - break - } - - // trim a useless layer of json - if (responseData && responseData.DomainDescription) { responseData = responseData.DomainDescription } - - // try to use the User Pool is (plus '-Settings') as the ID - let physicalResourceId - - if (responseData && responseData.UserPoolId) { - physicalResourceId = responseData.UserPoolId + '-Domain-' + responseData.Domain - - // generate the url - if (responseData.CustomDomainConfig && responseData.CustomDomainConfig.CertificateArn) { - // is a custom domain - responseData.FullUrl = `https://${responseData.Domain}` - } else { - // isn't a custom domain - responseData.FullUrl = `https://${responseData.Domain}.auth.${responseData.UserPoolId.split('_')[0]}.amazoncognito.com` - } - } - - await notifyCFN.ofSuccess({ event, context, responseData, physicalResourceId }) - - console.info(`CognitoUserPoolDomain Success for request type ${event.RequestType}`) - } catch (error) { - await notifyCFN.ofFailure({ event, context, error }) - - console.error(`CognitoUserPoolDomain Error for request type ${event.RequestType}:`, error) - } -} - -async function deleteUserPoolDomain (cognitoIdentityServiceProvider, domain) { - var response = await cognitoIdentityServiceProvider.describeUserPoolDomain({ - Domain: domain - }).promise() - - if (response.DomainDescription.Domain) { - await cognitoIdentityServiceProvider.deleteUserPoolDomain({ - UserPoolId: response.DomainDescription.UserPoolId, - Domain: domain - }).promise() - } -} diff --git a/lambdas/cognito-post-authentication-trigger/index.js b/lambdas/cognito-post-authentication-trigger/index.js index bba48877c..39af22f09 100644 --- a/lambdas/cognito-post-authentication-trigger/index.js +++ b/lambdas/cognito-post-authentication-trigger/index.js @@ -6,13 +6,17 @@ const customersController = require('dev-portal-common/customers-controller') const { getEnv } = require('dev-portal-common/get-env') +const { performMigration } = require('dev-portal-common/migrate-group-membership') exports.handler = async event => { const userId = event.request.userAttributes.sub const userPoolId = event.userPoolId + const groupsToEnsure = event.request.userAttributes.groups console.log(`In Post Authentication trigger for userId=[${userId}]`) + if (groupsToEnsure) await performMigration(userId, groupsToEnsure) + try { const { account, source } = await customersController.findAccountByUserId( userId diff --git a/lambdas/cognito-post-confirmation-trigger/index.js b/lambdas/cognito-post-confirmation-trigger/index.js index 5a02aed4c..9345b3fe9 100644 --- a/lambdas/cognito-post-confirmation-trigger/index.js +++ b/lambdas/cognito-post-confirmation-trigger/index.js @@ -6,17 +6,21 @@ const customersController = require('dev-portal-common/customers-controller') const { getEnv } = require('dev-portal-common/get-env') +const { performMigration } = require('dev-portal-common/migrate-group-membership') exports.handler = async event => { const accountRegistrationMode = getEnv('AccountRegistrationMode') const userId = event.request.userAttributes.sub const emailAddress = event.request.userAttributes.email + const groupsToEnsure = event.request.userAttributes.groups console.log( `In Post Confirmation trigger for userId=[${userId}]` + `, in accountRegistrationMode=[${accountRegistrationMode}]` ) + if (groupsToEnsure) await performMigration(userId, groupsToEnsure) + // We only care about sign-up confirmation, not forgot-password confirmation. if (event.triggerSource !== 'PostConfirmation_ConfirmSignUp') { console.info( @@ -41,14 +45,10 @@ exports.handler = async event => { }) ]) } else if (accountRegistrationMode === 'request') { - try { - await customersController.saveRequestPreLoginAccount({ - userId, - emailAddress - }) - } catch (error) { - console.error(error) - } + await customersController.saveRequestPreLoginAccount({ + userId, + emailAddress + }) } else { // Note: Post Confirmation trigger *does not* run for accounts created via // AdminCreateUser (e.g. in Invite mode). diff --git a/lambdas/cognito-user-migration-trigger/index.js b/lambdas/cognito-user-migration-trigger/index.js index 7dbd20817..0ad8ba7bc 100644 --- a/lambdas/cognito-user-migration-trigger/index.js +++ b/lambdas/cognito-user-migration-trigger/index.js @@ -1,23 +1,26 @@ 'use strict' const AWS = require('aws-sdk') +const { CognitoIdentityServiceProvider } = require('aws-sdk') -exports.cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ +const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' }) +exports.cognitoIdentityServiceProvider = CognitoIdentityServiceProvider + exports.handler = async (event) => { let authReq if (event.triggerSource === 'UserMigration_Authentication') { - authReq = exports.cognitoIdentityServiceProvider.adminInitiateAuth({ + authReq = cognitoIdentityServiceProvider.adminInitiateAuth({ AuthFlow: 'ADMIN_USER_PASSWORD_AUTH', AuthParameters: { USERNAME: event.userName, PASSWORD: event.request.password }, ClientId: event.callerContext.clientId, - UserPoolId: event.userPoolId + UserPoolId: process.env.OldUserPool }).promise().then(resp => { if (resp.AuthenticationResult == null) { // Username exists, but for whatever reason, the password wasn't sufficient. @@ -39,17 +42,55 @@ exports.handler = async (event) => { } try { - const [getUserResp] = await Promise.all([ - exports.cognitoIdentityServiceProvider.adminGetUser({ - UserPoolId: event.userPoolId, - Username: event.userName - }).promise(), + const [userAttributes] = await Promise.all([ + (async () => { + const options = { + UserPoolId: process.env.OldUserPool, + AttributesToGet: ['email', 'username'], + Filter: `email = "${event.userName.replace(/"/, '\\"')}"`, + Limit: 1 + } + + do { + const resp = await cognitoIdentityServiceProvider.listUsers(options).promise() + + if (resp.Users.length) { + const user = resp.Users[0] + const groups = [] + + const options = { + UserPoolId: process.env.OldUserPool, + Username: user.Username + } + + do { + const resp = await cognitoIdentityServiceProvider.adminListGroupsForUser(options).promise() + + for (const group of resp.Groups) { + if (group.GroupName === process.env.OldAdminsGroup) groups.push('admin') + else if (group.GroupName === process.env.OldRegisteredGroup) groups.push('registered') + } + + options.NextToken = resp.NextToken + } while (options.NextToken) + + return { + email: user.Attributes.find(e => e.Name === 'email').Value, + username: user.Username, + email_verified: 'true', + 'custom:groups': groups.join(',') + } + } + + options.PaginationToken = resp.PaginationToken + } while (options.PaginationToken) + + throw new Error('Bad username or password') + })(), authReq ]) - const email = getUserResp.UserAttributes.find(e => e.Name === 'email').Value - - event.response.userAttributes = { email, email_verified: 'true' } + event.response.userAttributes = userAttributes event.response.messageAction = 'SUPPRESS' event.response.desiredDeliveryMediums = 'email' event.response.forceAliasCreation = true diff --git a/lambdas/common-layer/nodejs/node_modules/dev-portal-common/migrate-group-membership.js b/lambdas/common-layer/nodejs/node_modules/dev-portal-common/migrate-group-membership.js new file mode 100644 index 000000000..6ceb013af --- /dev/null +++ b/lambdas/common-layer/nodejs/node_modules/dev-portal-common/migrate-group-membership.js @@ -0,0 +1,47 @@ +'use strict' + +// The following environment variables must be set appropriately: +// - UserPoolId +// - AdminGroupName +// - RegisteredGroupName + +const AWS = require('aws-sdk') +const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({ + apiVersion: '2016-04-18' +}) + +exports.cognitoIdentityServiceProvider = cognitoIdentityServiceProvider + +exports.performMigration = async (username, userGroups) => { + console.log('Migrating user groups') + + const promises = [] + const groups = userGroups.split(',') + + if (groups.includes('admin')) { + console.log('Adding user to admin group') + promises.push(cognitoIdentityServiceProvider.adminAddUserToGroup({ + UserPoolId: process.env.UserPoolId, + Username: username, + GroupName: process.env.AdminGroupName + }).promise()) + } + + if (groups.includes('registered')) { + console.log('Adding user to registered group') + promises.push(cognitoIdentityServiceProvider.adminAddUserToGroup({ + UserPoolId: process.env.UserPoolId, + Username: username, + GroupName: process.env.RegisteredGroupName + }).promise()) + } + + console.log('Deleting custom tracking attribute') + promises.push(cognitoIdentityServiceProvider.adminDeleteUserAttributes({ + UserPoolId: process.env.UserPoolId, + Username: username, + UserAttributeNames: ['custom:groups'] + }).promise()) + + await Promise.all(promises) +} diff --git a/scripts/internal/deploy-template.js b/scripts/internal/deploy-template.js index 265c1d334..38768cd57 100644 --- a/scripts/internal/deploy-template.js +++ b/scripts/internal/deploy-template.js @@ -55,7 +55,7 @@ module.exports = async () => { ...(customersTableName ? [`DevPortalCustomersTableName=${customersTableName}`] : []), ...(preLoginAccountsTableName ? [`DevPortalPreLoginAccountsTableName=${preLoginAccountsTableName}`] : []), ...(feedbackTableName ? [`DevPortalFeedbackTableName=${feedbackTableName}`] : []), - ...(cognitoIdentityPoolName ? [`CognitoIdentityPoolName2=${cognitoIdentityPoolName}`] : []), + ...(cognitoIdentityPoolName ? [`CognitoIdentityPoolNameV2=${cognitoIdentityPoolName}`] : []), ...(legacyCognitoIdentityPoolName ? [`CognitoIdentityPoolName=${legacyCognitoIdentityPoolName}`] : []), ...(developmentMode ? [`LocalDevelopmentMode=${developmentMode}`] : []), ...(cognitoDomainName ? [`CognitoDomainNameOrPrefix=${cognitoDomainName}`] : []),