diff --git a/infra-gen2/backends/auth/mfa-optional-sms/.gitignore b/infra-gen2/backends/auth/mfa-optional-sms/.gitignore new file mode 100644 index 0000000000..03d4668c65 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/.gitignore @@ -0,0 +1,5 @@ +# amplify +node_modules +.amplify +amplify_outputs* +amplifyconfiguration* diff --git a/infra-gen2/backends/auth/mfa-optional-sms/amplify/auth/resource.ts b/infra-gen2/backends/auth/mfa-optional-sms/amplify/auth/resource.ts new file mode 100644 index 0000000000..17c2f464e7 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/amplify/auth/resource.ts @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { defineAuth } from "@aws-amplify/backend"; + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + multifactor: { + mode: "OPTIONAL", + sms: true, + }, +}); diff --git a/infra-gen2/backends/auth/mfa-optional-sms/amplify/backend.ts b/infra-gen2/backends/auth/mfa-optional-sms/amplify/backend.ts new file mode 100644 index 0000000000..238fd4ee00 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/amplify/backend.ts @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { defineBackend } from "@aws-amplify/backend"; +import { addAuthUserExtensions } from "infra-common"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); + +const resources = backend.auth.resources; +const { userPool, cfnResources } = resources; +const { stack } = userPool; +const { cfnUserPool } = cfnResources; + +// Adds infra for creating/deleting users via App Sync and fetching confirmation +// and MFA codes from App Sync. +const customOutputs = addAuthUserExtensions({ + name: "mfa-optional-sms", + stack, + userPool, + cfnUserPool, +}); +backend.addOutput(customOutputs); diff --git a/infra-gen2/backends/auth/mfa-optional-sms/amplify/package.json b/infra-gen2/backends/auth/mfa-optional-sms/amplify/package.json new file mode 100644 index 0000000000..aead43de36 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/amplify/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/infra-gen2/backends/auth/mfa-optional-sms/amplify/tsconfig.json b/infra-gen2/backends/auth/mfa-optional-sms/amplify/tsconfig.json new file mode 100644 index 0000000000..4eb4ab26ca --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/amplify/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "$amplify/*": [ + "../.amplify/generated/*" + ] + } + } +} \ No newline at end of file diff --git a/infra-gen2/backends/auth/mfa-optional-sms/package.json b/infra-gen2/backends/auth/mfa-optional-sms/package.json new file mode 100644 index 0000000000..a80daed7e3 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-optional-sms/package.json @@ -0,0 +1,5 @@ +{ + "name": "mfa-optional-sms", + "version": "1.0.0", + "main": "index.js" +} diff --git a/infra-gen2/backends/auth/mfa-required-sms/.gitignore b/infra-gen2/backends/auth/mfa-required-sms/.gitignore new file mode 100644 index 0000000000..03d4668c65 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/.gitignore @@ -0,0 +1,5 @@ +# amplify +node_modules +.amplify +amplify_outputs* +amplifyconfiguration* diff --git a/infra-gen2/backends/auth/mfa-required-sms/amplify/auth/resource.ts b/infra-gen2/backends/auth/mfa-required-sms/amplify/auth/resource.ts new file mode 100644 index 0000000000..fcbc3dcd45 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/amplify/auth/resource.ts @@ -0,0 +1,14 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { defineAuth } from "@aws-amplify/backend"; + +export const auth = defineAuth({ + loginWith: { + email: true, + }, + multifactor: { + mode: "REQUIRED", + sms: true, + }, +}); diff --git a/infra-gen2/backends/auth/mfa-required-sms/amplify/backend.ts b/infra-gen2/backends/auth/mfa-required-sms/amplify/backend.ts new file mode 100644 index 0000000000..7fcaccc423 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/amplify/backend.ts @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { defineBackend } from "@aws-amplify/backend"; +import { addAuthUserExtensions } from "infra-common"; +import { auth } from "./auth/resource"; + +const backend = defineBackend({ + auth, +}); + +const resources = backend.auth.resources; +const { userPool, cfnResources } = resources; +const { stack } = userPool; +const { cfnUserPool } = cfnResources; + +// Adds infra for creating/deleting users via App Sync and fetching confirmation +// and MFA codes from App Sync. +const customOutputs = addAuthUserExtensions({ + name: "mfa-required-sms", + stack, + userPool, + cfnUserPool, +}); +backend.addOutput(customOutputs); diff --git a/infra-gen2/backends/auth/mfa-required-sms/amplify/package.json b/infra-gen2/backends/auth/mfa-required-sms/amplify/package.json new file mode 100644 index 0000000000..aead43de36 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/amplify/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file diff --git a/infra-gen2/backends/auth/mfa-required-sms/amplify/tsconfig.json b/infra-gen2/backends/auth/mfa-required-sms/amplify/tsconfig.json new file mode 100644 index 0000000000..4eb4ab26ca --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/amplify/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "paths": { + "$amplify/*": [ + "../.amplify/generated/*" + ] + } + } +} \ No newline at end of file diff --git a/infra-gen2/backends/auth/mfa-required-sms/package.json b/infra-gen2/backends/auth/mfa-required-sms/package.json new file mode 100644 index 0000000000..7449d27736 --- /dev/null +++ b/infra-gen2/backends/auth/mfa-required-sms/package.json @@ -0,0 +1,5 @@ +{ + "name": "mfa-required-sms", + "version": "1.0.0", + "main": "index.js" +} diff --git a/infra-gen2/infra-common/src/auth-user-extensions/auth-user-extensions.ts b/infra-gen2/infra-common/src/auth-user-extensions/auth-user-extensions.ts index 6b532d102d..ed20e9f7a8 100644 --- a/infra-gen2/infra-common/src/auth-user-extensions/auth-user-extensions.ts +++ b/infra-gen2/infra-common/src/auth-user-extensions/auth-user-extensions.ts @@ -4,6 +4,7 @@ import { CfnUserPool, IUserPool } from "aws-cdk-lib/aws-cognito"; import { addCreateUserLambda } from "./create-user-lambda"; import { addCustomSenderLambda } from "./custom-sender-lambda"; import { addDeleteUserLambda } from "./delete-user-lambda"; +import { addEnableSmsMfaLambda } from "./enable-sms-mfa-lambda"; import { addUserGraphql } from "./user-graphql"; type AmplifyOutputs = Parameters[0]; @@ -23,6 +24,7 @@ export const addAuthUserExtensions = ({ addCustomSenderLambda({ name, stack, cfnUserPool, graphQL }); addCreateUserLambda({ name, stack, userPool, graphQL }); addDeleteUserLambda({ name, stack, userPool, graphQL }); + addEnableSmsMfaLambda({ name, stack, userPool, graphQL }); return { data: { aws_region: stack.region, diff --git a/infra-gen2/infra-common/src/auth-user-extensions/custom-sender-lambda.ts b/infra-gen2/infra-common/src/auth-user-extensions/custom-sender-lambda.ts index 414f981ce1..51835629af 100644 --- a/infra-gen2/infra-common/src/auth-user-extensions/custom-sender-lambda.ts +++ b/infra-gen2/infra-common/src/auth-user-extensions/custom-sender-lambda.ts @@ -44,9 +44,6 @@ export function addCustomSenderLambda({ "custom-email-sender.js" ), runtime: Runtime.NODEJS_18_X, - bundling: { - nodeModules: ["@aws-crypto/client-node"], - }, environment: { GRAPHQL_API_ENDPOINT: graphQL.graphqlUrl, GRAPHQL_API_KEY: graphQL.apiKey!, @@ -68,9 +65,6 @@ export function addCustomSenderLambda({ "custom-sms-sender.js" ), runtime: Runtime.NODEJS_18_X, - bundling: { - nodeModules: ["@aws-crypto/client-node"], - }, environment: { GRAPHQL_API_ENDPOINT: graphQL.graphqlUrl, GRAPHQL_API_KEY: graphQL.apiKey!, diff --git a/infra-gen2/infra-common/src/auth-user-extensions/enable-sms-mfa-lambda.ts b/infra-gen2/infra-common/src/auth-user-extensions/enable-sms-mfa-lambda.ts new file mode 100644 index 0000000000..b1948516bf --- /dev/null +++ b/infra-gen2/infra-common/src/auth-user-extensions/enable-sms-mfa-lambda.ts @@ -0,0 +1,49 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Stack } from "aws-cdk-lib"; +import { GraphqlApi, MappingTemplate } from "aws-cdk-lib/aws-appsync"; +import { IUserPool } from "aws-cdk-lib/aws-cognito"; +import { Runtime } from "aws-cdk-lib/aws-lambda"; +import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; +import path from "path"; + +export function addEnableSmsMfaLambda({ + name, + stack, + graphQL, + userPool, +}: { + name: string; + stack: Stack; + graphQL: GraphqlApi; + userPool: IUserPool; +}) { + const enableSmsMfaLambda = new NodejsFunction(stack, `${name}-enableSmsMfa`, { + runtime: Runtime.NODEJS_18_X, + entry: path.resolve( + __dirname, + "..", + "lambda-triggers", + "enable-sms-mfa.js" + ), + environment: { + USER_POOL_ID: userPool.userPoolId, + }, + }); + + userPool.grant(enableSmsMfaLambda, "cognito-idp:AdminSetUserMFAPreference"); + + // Mutation.enableSmsMfa + const enableSmsMfaSource = graphQL.addLambdaDataSource( + "GraphQLApiEnableSmsMfaLambda", + enableSmsMfaLambda + ); + + enableSmsMfaSource.createResolver("MutationEnableSmsMfaResolver", { + typeName: "Mutation", + fieldName: "enableSmsMfa", + requestMappingTemplate: MappingTemplate.lambdaRequest(), + responseMappingTemplate: MappingTemplate.lambdaResult(), + }); +} diff --git a/infra-gen2/infra-common/src/lambda-triggers/enable-sms-mfa.ts b/infra-gen2/infra-common/src/lambda-triggers/enable-sms-mfa.ts new file mode 100644 index 0000000000..c5a3f233f2 --- /dev/null +++ b/infra-gen2/infra-common/src/lambda-triggers/enable-sms-mfa.ts @@ -0,0 +1,54 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import * as cognito from "@aws-sdk/client-cognito-identity-provider"; +import type * as lambda from "aws-lambda"; + +interface EnableSmsMfaRequest { + username: string; +} + +interface EnableSmsMfaResponse { + success: boolean; + error?: string; +} + +const USER_POOL_ID = process.env.USER_POOL_ID; +const CLIENT = new cognito.CognitoIdentityProviderClient({ + region: process.env.REGION, +}); + +export const handler: lambda.AppSyncResolverHandler< + EnableSmsMfaRequest, + EnableSmsMfaResponse +> = async ( + event: lambda.AppSyncResolverEvent +): Promise => { + console.log(`Got event: ${JSON.stringify(event, null, 2)}`); + + const { username } = event.arguments; + console.log(`Enabling SMS MFA for user ${username}...`); + try { + const mfaParams: cognito.AdminSetUserMFAPreferenceCommandInput = { + UserPoolId: USER_POOL_ID, + Username: username, + SMSMfaSettings: { + Enabled: true, + PreferredMfa: true, + }, + }; + const resp = await CLIENT.send( + new cognito.AdminSetUserMFAPreferenceCommand(mfaParams), + ); + console.log(`Successfully enabled MFA for ${username}`, resp); + return { + success: true, + }; + } catch (err: any) { + console.log(`Could not enable MFA for ${username}`, err); + return { + success: false, + error: err.toString(), + }; + } + }; diff --git a/infra-gen2/package-lock.json b/infra-gen2/package-lock.json index e49338ae82..c284bca6e1 100644 --- a/infra-gen2/package-lock.json +++ b/infra-gen2/package-lock.json @@ -35,6 +35,12 @@ "backends/auth/email-sign-in": { "version": "1.0.0" }, + "backends/auth/mfa-optional-sms": { + "version": "1.0.0" + }, + "backends/auth/mfa-required-sms": { + "version": "1.0.0" + }, "backends/auth/phone-sign-in": { "version": "1.0.0" }, @@ -19803,6 +19809,14 @@ "node": ">= 8" } }, + "node_modules/mfa-optional-sms": { + "resolved": "backends/auth/mfa-optional-sms", + "link": true + }, + "node_modules/mfa-required-sms": { + "resolved": "backends/auth/mfa-required-sms", + "link": true + }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", diff --git a/infra-gen2/tool/deploy_gen2.dart b/infra-gen2/tool/deploy_gen2.dart index da44078596..642d87ce8a 100644 --- a/infra-gen2/tool/deploy_gen2.dart +++ b/infra-gen2/tool/deploy_gen2.dart @@ -53,6 +53,16 @@ const List infraConfig = [ identifier: 'phone-sign-in', pathToSource: 'infra-gen2/backends/auth/phone-sign-in', ), + AmplifyBackend( + name: 'mfa-optional-sms', + identifier: 'mfa-opt-sms', + pathToSource: 'infra-gen2/backends/auth/mfa-optional-sms', + ), + AmplifyBackend( + name: 'mfa-required-sms', + identifier: 'mfa-req-sms', + pathToSource: 'infra-gen2/backends/auth/mfa-required-sms', + ), ], ), AmplifyBackendGroup( diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_test.dart index 0fe283c276..6d56410548 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_test.dart @@ -13,23 +13,27 @@ void main() { testRunner.setupTests(); group('MFA (SMS)', () { - final smsEnvironments = mfaEnvironments.where((env) => env.sms); - for (final env in smsEnvironments) { - testRunner.withEnvironment(env, () { + final smsEnvironments = mfaEnvironments.where( + (env) => env.mfaInfo!.smsEnabled, + ); + for (final environment in smsEnvironments) { + testRunner.withEnvironment(environment, (env) { asyncTest( 'can sign in with SMS MFA enabled by administrator', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); - final otpResult = - await getOtpCode(UserAttribute.username(username)); + final otpResult = await getOtpCode( + env.getLoginAttribute(username), + ); await adminCreateUser( username, password, autoConfirm: true, verifyAttributes: true, + attributes: env.getDefaultAttributes(username), enableMfa: true, ); @@ -56,9 +60,9 @@ void main() { }); } - testRunner.withEnvironment(MfaEnvironment.mfaRequiredSms, () { + testRunner.withEnvironment(mfaRequiredSms, (env) { asyncTest('must configure MFA when required', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); await adminCreateUser( @@ -66,9 +70,12 @@ void main() { password, autoConfirm: true, verifyAttributes: true, + attributes: env.getDefaultAttributes(username), ); - final otpResult = await getOtpCode(UserAttribute.username(username)); + final otpResult = await getOtpCode( + env.getLoginAttribute(username), + ); final signInRes = await Amplify.Auth.signIn( username: username, @@ -100,9 +107,9 @@ void main() { }); }); - testRunner.withEnvironment(MfaEnvironment.mfaOptionalSms, () { + testRunner.withEnvironment(mfaOptionalSms, (env) { asyncTest('can skip configuring MFA when not required', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); await adminCreateUser( @@ -110,6 +117,7 @@ void main() { password, autoConfirm: true, verifyAttributes: true, + attributes: env.getDefaultAttributes(username), ); final signInRes = await Amplify.Auth.signIn( @@ -129,14 +137,15 @@ void main() { asyncTest( 'fetchMfaPreference returns SMS when enabled outside library', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); - await adminCreateUser( + final cognitoUsername = await adminCreateUser( username, password, autoConfirm: true, verifyAttributes: true, + attributes: env.getDefaultAttributes(username), ); final signInRes = await Amplify.Auth.signIn( @@ -149,7 +158,7 @@ void main() { 'the first sign-in', ).equals(AuthSignInStep.done); - await adminEnableSmsMfa(username); + await adminEnableSmsMfa(cognitoUsername); check(await cognitoPlugin.fetchMfaPreference()).equals( const UserMfaPreference( diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_optional_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_optional_test.dart index 16b84c7a95..ca25f39e31 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_optional_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_optional_test.dart @@ -13,9 +13,9 @@ void main() { testRunner.setupTests(); group('MFA (SMS + TOTP)', () { - testRunner.withEnvironment(MfaEnvironment.mfaOptionalSmsTotp, () { + testRunner.withEnvironment(mfaOptionalSmsTotp, (env) { asyncTest('can set up TOTP MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); // Create user with no phone number. @@ -87,7 +87,7 @@ void main() { }); asyncTest('can select TOTP MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); final phoneNumber = generatePhoneNumber(); @@ -289,7 +289,7 @@ void main() { }); asyncTest('can select SMS MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); final phoneNumber = generatePhoneNumber(); diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_required_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_required_test.dart index da1d66eeb2..32862bf0bb 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_required_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_sms_totp_required_test.dart @@ -14,9 +14,9 @@ void main() { testRunner.setupTests(); group('MFA (SMS + TOTP)', () { - testRunner.withEnvironment(MfaEnvironment.mfaRequiredSmsTotp, () { + testRunner.withEnvironment(mfaRequiredSmsTotp, (env) { asyncTest('can set up TOTP MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); // Create a user with no phone number. @@ -75,7 +75,7 @@ void main() { }); asyncTest('can select TOTP MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); final phoneNumber = generatePhoneNumber(); @@ -290,7 +290,7 @@ void main() { }); asyncTest('can select SMS MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); final phoneNumber = generatePhoneNumber(); diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_optional_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_optional_test.dart index 64dff5af8d..42b1822372 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_optional_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_optional_test.dart @@ -84,7 +84,7 @@ void main() { ); } - testRunner.withEnvironment(MfaEnvironment.mfaOptionalTotp, () { + testRunner.withEnvironment(mfaOptionalTotp, (env) { group('can sign in with TOTP MFA', () { asyncTest( 'w/ no device name', @@ -96,7 +96,7 @@ void main() { ); asyncTest('verifyTotpSetup allows retries', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); await adminCreateUser( diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_required_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_required_test.dart index 85594cb7b9..84370b665c 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_required_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/mfa_totp_required_test.dart @@ -14,9 +14,9 @@ void main() { testRunner.setupTests(); group('MFA (TOTP)', () { - testRunner.withEnvironment(MfaEnvironment.mfaRequiredTotp, () { + testRunner.withEnvironment(mfaRequiredTotp, (env) { asyncTest('can sign in with TOTP MFA', (_) async { - final username = generateUsername(); + final username = env.generateUsername(); final password = generatePassword(); await adminCreateUser( diff --git a/packages/auth/amplify_auth_cognito/example/integration_test/reset_password_test.dart b/packages/auth/amplify_auth_cognito/example/integration_test/reset_password_test.dart index 69c2fcecc0..47555320d0 100644 --- a/packages/auth/amplify_auth_cognito/example/integration_test/reset_password_test.dart +++ b/packages/auth/amplify_auth_cognito/example/integration_test/reset_password_test.dart @@ -33,12 +33,7 @@ void main() { autoConfirm: true, verifyAttributes: true, autoFillAttributes: environment.loginMethod.isUsername, - // email/phone attributes must be provided for auto verification. - attributes: switch (environment.loginMethod) { - LoginMethod.email => {AuthUserAttributeKey.email: username}, - LoginMethod.phone => {AuthUserAttributeKey.phoneNumber: username}, - _ => {}, - }, + attributes: environment.getDefaultAttributes(username), ); await Amplify.Auth.signIn( diff --git a/packages/test/amplify_auth_integration_test/lib/amplify_auth_integration_test.dart b/packages/test/amplify_auth_integration_test/lib/amplify_auth_integration_test.dart index 0023b6acaa..0e94ac7fd3 100644 --- a/packages/test/amplify_auth_integration_test/lib/amplify_auth_integration_test.dart +++ b/packages/test/amplify_auth_integration_test/lib/amplify_auth_integration_test.dart @@ -6,7 +6,7 @@ library amplify_auth_integration_test; export 'src/async_test.dart'; -export 'src/mfa_environments.dart'; +export 'src/environments.dart'; export 'src/test_auth_plugin.dart'; export 'src/test_runner.dart'; export 'src/totp_utils.dart'; diff --git a/packages/test/amplify_auth_integration_test/lib/src/environments.dart b/packages/test/amplify_auth_integration_test/lib/src/environments.dart new file mode 100644 index 0000000000..f6b9ebee6f --- /dev/null +++ b/packages/test/amplify_auth_integration_test/lib/src/environments.dart @@ -0,0 +1,73 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_auth_integration_test/amplify_auth_integration_test.dart'; + +/// A subset of environments with a user pool, used to test common user pool +/// functionality such as sign in, sign up, and confirm sign up. +const List userPoolEnvironments = [ + EnvironmentInfo.withGen1Defaults(name: 'main'), + EnvironmentInfo.withGen1Defaults(name: 'user-pool-only'), + EnvironmentInfo.withGen1Defaults(name: 'with-client-secret'), + EnvironmentInfo.withGen2Defaults(name: 'email-sign-in'), + EnvironmentInfo.withGen2Defaults( + name: 'phone-sign-in', + loginMethod: LoginMethod.phone, + confirmationDeliveryMedium: DeliveryMedium.sms, + resetPasswordDeliveryMedium: DeliveryMedium.sms, + ), +]; + +/// An environment with optional MFA via SMS only. +const mfaOptionalSms = EnvironmentInfo.withGen2Defaults( + name: 'mfa-optional-sms', + mfaInfo: MfaInfo(smsEnabled: true, required: false), +); + +/// An environment with required MFA via SMS only. +const mfaRequiredSms = EnvironmentInfo.withGen2Defaults( + name: 'mfa-required-sms', + mfaInfo: MfaInfo(smsEnabled: true, required: true), +); + +/// An environment with optional MFA via TOTP only. +const mfaOptionalTotp = EnvironmentInfo.withGen1Defaults( + name: 'mfa-optional-totp', + mfaInfo: MfaInfo(totpEnabled: true, required: false), +); + +/// An environment with required MFA via TOTP only. +const mfaRequiredTotp = EnvironmentInfo.withGen1Defaults( + name: 'mfa-required-totp', + mfaInfo: MfaInfo(totpEnabled: true, required: true), +); + +/// An environment with required MFA via SMS & TOTP. +const mfaOptionalSmsTotp = EnvironmentInfo.withGen1Defaults( + name: 'mfa-optional-sms-totp', + mfaInfo: MfaInfo(smsEnabled: true, totpEnabled: true, required: false), +); + +/// An environment with required MFA via SMS & TOTP. +const mfaRequiredSmsTotp = EnvironmentInfo.withGen1Defaults( + name: 'mfa-required-sms-totp', + mfaInfo: MfaInfo(smsEnabled: true, totpEnabled: true, required: true), +); + +/// Environments that support MFA +const List mfaEnvironments = [ + mfaOptionalSms, + mfaRequiredSms, + mfaOptionalTotp, + mfaRequiredTotp, + mfaOptionalSmsTotp, + mfaRequiredSmsTotp, +]; + +/// Environments with a user pool and opt-in device tracking. +const List deviceOptInEnvironments = [ + 'device-tracking-opt-in', + 'user-pool-only', + 'with-client-secret', +]; diff --git a/packages/test/amplify_auth_integration_test/lib/src/mfa_environments.dart b/packages/test/amplify_auth_integration_test/lib/src/mfa_environments.dart deleted file mode 100644 index d05047a434..0000000000 --- a/packages/test/amplify_auth_integration_test/lib/src/mfa_environments.dart +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import 'package:amplify_auth_integration_test/src/test_runner.dart'; - -/// E2E environments with MFA enabled. -/// -/// Can be filtered to get environments with a specific configuration. -const mfaEnvironments = MfaEnvironment.values; - -/// {@template amplify_auth_integration_test.mfa_environment} -/// A description of an E2E environment with MFA enabled. -/// {@endtemplate} -enum MfaEnvironment implements TestEnvironment { - /// MFA optional, SMS-only - mfaOptionalSms( - environmentName: 'mfa-optional-sms', - required: false, - sms: true, - totp: false, - ), - - /// MFA required, SMS-only - mfaRequiredSms( - environmentName: 'mfa-required-sms', - required: true, - sms: true, - totp: false, - ), - - /// MFA optional, TOTP-only - mfaOptionalTotp( - environmentName: 'mfa-optional-totp', - required: false, - sms: false, - totp: true, - ), - - /// MFA required, TOTP-only - mfaRequiredTotp( - environmentName: 'mfa-required-totp', - required: true, - sms: false, - totp: true, - ), - - /// MFA optional, SMS + TOTP - mfaOptionalSmsTotp( - environmentName: 'mfa-optional-sms-totp', - required: false, - sms: true, - totp: true, - ), - - /// MFA required, SMS + TOTP - mfaRequiredSmsTotp( - environmentName: 'mfa-required-sms-totp', - required: true, - sms: true, - totp: true, - ); - - /// {@macro amplify_auth_integration_test.mfa_environment} - const MfaEnvironment({ - required this.environmentName, - required this.required, - required this.sms, - required this.totp, - }); - - /// The name of the environment. - @override - final String environmentName; - - /// Whether MFA is required (`true`) or optional (`false`). - final bool required; - - /// Whether SMS MFA is available. - final bool sms; - - /// Whether TOTP MFA is available. - final bool totp; -} diff --git a/packages/test/amplify_auth_integration_test/lib/src/test_runner.dart b/packages/test/amplify_auth_integration_test/lib/src/test_runner.dart index 864af1d62f..1401bc4fd6 100644 --- a/packages/test/amplify_auth_integration_test/lib/src/test_runner.dart +++ b/packages/test/amplify_auth_integration_test/lib/src/test_runner.dart @@ -57,13 +57,13 @@ class EnvironmentInfo { required this.preventUserExistenceErrors, required this.confirmationDeliveryMedium, required this.resetPasswordDeliveryMedium, - required this.mfaEnabled, + required this.mfaInfo, }); /// The default env info for the gen 1 CLI. const EnvironmentInfo.withGen1Defaults({ required this.name, - this.mfaEnabled = true, + this.mfaInfo = const MfaInfo(smsEnabled: true), this.configVersion = AmplifyConfigVersion.config, this.loginMethod = LoginMethod.username, this.preventUserExistenceErrors = false, @@ -74,7 +74,7 @@ class EnvironmentInfo { /// The default env info for gen 2. const EnvironmentInfo.withGen2Defaults({ required this.name, - this.mfaEnabled = false, + this.mfaInfo, this.configVersion = AmplifyConfigVersion.outputs, this.loginMethod = LoginMethod.email, this.preventUserExistenceErrors = true, @@ -99,6 +99,18 @@ class EnvironmentInfo { LoginMethod.phone => amp_test.generateUSPhoneNumber().toE164(), }; + /// Returns the attributes that Cognito will create automatically based on the + /// sign up method. + /// + /// For example, if sign in alias is Email, the user's username automatically + /// is set to their email. + Map getDefaultAttributes(String username) => + switch (loginMethod) { + LoginMethod.email => {AuthUserAttributeKey.email: username}, + LoginMethod.phone => {AuthUserAttributeKey.phoneNumber: username}, + LoginMethod.username => {} + }; + /// The name of the environment in the config/outputs file. final String name; @@ -121,29 +133,29 @@ class EnvironmentInfo { final DeliveryMedium resetPasswordDeliveryMedium; /// Whether or no MFA is enabled for this environment. - final bool mfaEnabled; + bool get mfaEnabled => mfaInfo != null; + + /// Multi-factor auth configuration information for the environment. + final MfaInfo? mfaInfo; } -/// Environments with a user pool and username-based sign in. -const List userPoolEnvironments = [ - EnvironmentInfo.withGen1Defaults(name: 'main'), - EnvironmentInfo.withGen1Defaults(name: 'user-pool-only'), - EnvironmentInfo.withGen1Defaults(name: 'with-client-secret'), - EnvironmentInfo.withGen2Defaults(name: 'email-sign-in'), - EnvironmentInfo.withGen2Defaults( - name: 'phone-sign-in', - loginMethod: LoginMethod.phone, - confirmationDeliveryMedium: DeliveryMedium.sms, - resetPasswordDeliveryMedium: DeliveryMedium.sms, - ), -]; - -/// Environments with a user pool and opt-in device tracking. -const List deviceOptInEnvironments = [ - 'device-tracking-opt-in', - 'user-pool-only', - 'with-client-secret', -]; +/// Multi-factor auth configuration information for the environment. +class MfaInfo { + const MfaInfo({ + this.required = false, + this.smsEnabled = false, + this.totpEnabled = false, + }); + + /// Whether MFA is required (`true`) or optional (`false`). + final bool required; + + /// Whether SMS MFA is available. + final bool smsEnabled; + + /// Whether TOTP MFA is available. + final bool totpEnabled; +} /// A test environment descriptor. abstract interface class TestEnvironment { @@ -263,13 +275,19 @@ class AuthTestRunner { } /// Runs [body] in a [group] which configures [environment]. - void withEnvironment(TestEnvironment environment, void Function() body) { - group(environment.environmentName, () { + void withEnvironment( + EnvironmentInfo environment, + void Function(EnvironmentInfo env) body, + ) { + group(environment.name, () { setUp(() async { - await configure(environmentName: environment.environmentName); + await configure( + environmentName: environment.name, + useAmplifyOutputs: environment.useAmplifyOutputs, + ); }); - body(); + body(environment); }); } }