diff --git a/.doc_gen/metadata/cognito-identity-provider_metadata.yaml b/.doc_gen/metadata/cognito-identity-provider_metadata.yaml index c53fb773998..2d04ef9b80f 100644 --- a/.doc_gen/metadata/cognito-identity-provider_metadata.yaml +++ b/.doc_gen/metadata/cognito-identity-provider_metadata.yaml @@ -163,6 +163,15 @@ cognito-identity-provider_SignUp: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.signup + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.SignUp services: cognito-identity-provider: {SignUp} cognito-identity-provider_AdminGetUser: @@ -223,6 +232,15 @@ cognito-identity-provider_AdminGetUser: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.admin_get_user + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.AdminGetUser services: cognito-identity-provider: {AdminGetUser} cognito-identity-provider_ResendConfirmationCode: @@ -283,6 +301,15 @@ cognito-identity-provider_ResendConfirmationCode: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.resend_confirmation + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.ResendConfirmationCode services: cognito-identity-provider: {ResendConfirmationCode} cognito-identity-provider_ConfirmSignUp: @@ -343,6 +370,15 @@ cognito-identity-provider_ConfirmSignUp: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.confirm_signup + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.ConfirmSignUp services: cognito-identity-provider: {ConfirmSignUp} cognito-identity-provider_ListUsers: @@ -453,6 +489,15 @@ cognito-identity-provider_AdminInitiateAuth: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.admin_initiate_auth + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.AdminInitiateAuth services: cognito-identity-provider: {AdminInitiateAuth} cognito-identity-provider_AssociateSoftwareToken: @@ -513,6 +558,15 @@ cognito-identity-provider_AssociateSoftwareToken: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.associate_software_token + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.AssociateSoftwareToken services: cognito-identity-provider: {AssociateSoftwareToken} cognito-identity-provider_VerifySoftwareToken: @@ -573,6 +627,15 @@ cognito-identity-provider_VerifySoftwareToken: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.verify_software_token + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.VerifySoftwareToken services: cognito-identity-provider: {VerifySoftwareToken} cognito-identity-provider_AdminRespondToAuthChallenge: @@ -633,6 +696,15 @@ cognito-identity-provider_AdminRespondToAuthChallenge: - cpp.example_code.cognito.client_configuration - cpp.example_code.cognito.cognito_client - cpp.example_code.cognito.admin_respond_to_auth_challenge + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: + snippet_tags: + - swift.cognito-identity-provider.import + - swift.cognito-identity-provider.AdminRespondToAuthChallenge services: cognito-identity-provider: {AdminRespondToAuthChallenge} cognito-identity-provider_ConfirmDevice: @@ -947,6 +1019,17 @@ cognito-identity-provider_Scenario_SignUpUserWithMfa: snippet_tags: - cpp.example_code.cognito.client_configuration - cpp.example_code.getting_started_with_user_pools + Swift: + versions: + - sdk_version: 1 + github: swift/example_code/cognito-identity-provider + excerpts: + - description: The Package.swift file. + snippet_tags: + - swift.cognito-identity-provider.scenario.package + - description: The Swift code file. + snippet_tags: + - swift.cognito-identity-provider.scenario services: cognito-identity-provider: {ConfirmSignUp, ConfirmDevice, AssociateSoftwareToken, AdminGetUser, ListUsers, ResendConfirmationCode, RespondToAuthChallenge, AdminRespondToAuthChallenge, SignUp, InitiateAuth, AdminInitiateAuth, VerifySoftwareToken} diff --git a/swift/example_code/cognito-identity-provider/README.md b/swift/example_code/cognito-identity-provider/README.md new file mode 100644 index 00000000000..86fbbdabe62 --- /dev/null +++ b/swift/example_code/cognito-identity-provider/README.md @@ -0,0 +1,118 @@ +# Amazon Cognito Identity Provider code examples for the SDK for Swift + +## Overview + +Shows how to use the AWS SDK for Swift to work with Amazon Cognito Identity Provider. + + + + +_Amazon Cognito Identity Provider handles user authentication and authorization for your web and mobile apps._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `swift` folder. + + + + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [AdminGetUser](scenario/Sources/entry.swift#L98) +- [AdminInitiateAuth](scenario/Sources/entry.swift#L247) +- [AdminRespondToAuthChallenge](scenario/Sources/entry.swift#L377) +- [AssociateSoftwareToken](scenario/Sources/entry.swift#L298) +- [ConfirmSignUp](scenario/Sources/entry.swift#L215) +- [ResendConfirmationCode](scenario/Sources/entry.swift#L180) +- [SignUp](scenario/Sources/entry.swift#L131) +- [VerifySoftwareToken](scenario/Sources/entry.swift#L334) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [Sign up a user with a user pool that requires MFA](scenario/Package.swift) + + + + + +## Run the examples + +### Instructions + +To build any of these examples from a terminal window, navigate into its +directory, then use the following command: + +``` +$ swift build +``` + +To build one of these examples in Xcode, navigate to the example's directory +(such as the `ListUsers` directory, to build that example). Then type `xed.` +to open the example directory in Xcode. You can then use standard Xcode build +and run commands. + + + + + + +#### Sign up a user with a user pool that requires MFA + +This example shows you how to do the following: + +- Sign up and confirm a user with a username, password, and email address. +- Set up multi-factor authentication by associating an MFA application with the user. +- Sign in by using a password and an MFA code. + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `swift` folder. + + + + + + +## Additional resources + +- [Amazon Cognito Identity Provider Developer Guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) +- [Amazon Cognito Identity Provider API Reference](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/Welcome.html) +- [SDK for Swift Amazon Cognito Identity Provider reference](https://sdk.amazonaws.com/swift/api/awscognitoidentityprovider/latest/documentation/awscognitoidentityprovider) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/swift/example_code/cognito-identity-provider/scenario/Package.swift b/swift/example_code/cognito-identity-provider/scenario/Package.swift new file mode 100644 index 00000000000..ef3fa21c2a8 --- /dev/null +++ b/swift/example_code/cognito-identity-provider/scenario/Package.swift @@ -0,0 +1,47 @@ +// swift-tools-version: 5.9 +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// (swift-tools-version has two lines here because it needs to be the first +// line in the file, but it should also appear in the snippet below) +// +// snippet-start:[swift.cognito-identity-provider.scenario.package] +// swift-tools-version: 5.9 +// +// The swift-tools-version declares the minimum version of Swift required to +// build this package. + +import PackageDescription + +let package = Package( + name: "cognito-scenario", + // Let Xcode know the minimum Apple platforms supported. + platforms: [ + .macOS(.v13), + .iOS(.v15) + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package( + url: "https://github.com/awslabs/aws-sdk-swift", + from: "1.0.0"), + .package( + url: "https://github.com/apple/swift-argument-parser.git", + branch: "main" + ) + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products + // from dependencies. + .executableTarget( + name: "cognito-scenario", + dependencies: [ + .product(name: "AWSCognitoIdentityProvider", package: "aws-sdk-swift"), + .product(name: "ArgumentParser", package: "swift-argument-parser") + ], + path: "Sources") + + ] +) +// snippet-end:[swift.cognito-identity-provider.scenario.package] diff --git a/swift/example_code/cognito-identity-provider/scenario/Sources/entry.swift b/swift/example_code/cognito-identity-provider/scenario/Sources/entry.swift new file mode 100644 index 00000000000..a38f5511f99 --- /dev/null +++ b/swift/example_code/cognito-identity-provider/scenario/Sources/entry.swift @@ -0,0 +1,568 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// snippet-start:[swift.cognito-identity-provider.scenario] +// An example demonstrating various features of Amazon Cognito. Before running +// this Swift code example, set up your development environment, including +// your credentials. +// +// For more information, see the following documentation: +// https://docs.aws.amazon.com/sdk-for-kotlin/latest/developer-guide/setup.html +// +// TIP: To set up the required user pool, run the AWS Cloud Development Kit +// (AWS CDK) script provided in this GitHub repo at +// resources/cdk/cognito_scenario_user_pool_with_mfa. +// +// This example performs the following functions: +// +// 1. Invokes the signUp method to sign up a user. +// 2. Invokes the adminGetUser method to get the user's confirmation status. +// 3. Invokes the ResendConfirmationCode method if the user requested another +// code. +// 4. Invokes the confirmSignUp method. +// 5. Invokes the initiateAuth to sign in. This results in being prompted to +// set up TOTP (time-based one-time password). (The response is +// “ChallengeName”: “MFA_SETUP”). +// 6. Invokes the AssociateSoftwareToken method to generate a TOTP MFA private +// key. This can be used with Google Authenticator. +// 7. Invokes the VerifySoftwareToken method to verify the TOTP and register +// for MFA. +// 8. Invokes the AdminInitiateAuth to sign in again. This results in being +// prompted to submit a TOTP (Response: “ChallengeName”: +// “SOFTWARE_TOKEN_MFA”). +// 9. Invokes the AdminRespondToAuthChallenge to get back a token. + +import ArgumentParser +import AWSClientRuntime +import Foundation + +// snippet-start:[swift.cognito-identity-provider.import] +import AWSCognitoIdentityProvider +// snippet-end:[swift.cognito-identity-provider.import] + +struct ExampleCommand: ParsableCommand { + @Argument(help: "The application clientId.") + var clientId: String + @Argument(help: "The user pool ID to use.") + var poolId: String + @Option(help: "Name of the Amazon Region to use") + var region = "us-east-1" + + static var configuration = CommandConfiguration( + commandName: "cognito-scenario", + abstract: """ + Demonstrates various features of Amazon Cognito. + """, + discussion: """ + """ + ) + + /// Prompt for an input string of at least a minimum length. + /// + /// - Parameters: + /// - prompt: The prompt string to display. + /// - minLength: The minimum number of characters to allow in the + /// response. Default value is 0. + /// + /// - Returns: The entered string. + func stringRequest(_ prompt: String, minLength: Int = 1) -> String { + while true { + print(prompt, terminator: "") + let str = readLine() + + guard let str else { + continue + } + if str.count >= minLength { + return str + } else { + print("*** Response must be at least \(minLength) character(s) long.") + } + } + } + + /// Ask a yes/no question. + /// + /// - Parameter prompt: A prompt string to print. + /// + /// - Returns: `true` if the user answered "Y", otherwise `false`. + func yesNoRequest(_ prompt: String) -> Bool { + while true { + let answer = stringRequest(prompt).lowercased() + if answer == "y" || answer == "n" { + return answer == "y" + } + } + } + + // snippet-start:[swift.cognito-identity-provider.AdminGetUser] + /// Get information about a specific user in a user pool. + /// + /// - Parameters: + /// - cipClient: The Amazon Cognito Identity Provider client to use. + /// - userName: The user to retrieve information about. + /// - userPoolId: The user pool to search for the specified user. + /// + /// - Returns: `true` if the user's information was successfully + /// retrieved. Otherwise returns `false`. + func adminGetUser(cipClient: CognitoIdentityProviderClient, userName: String, + userPoolId: String) async -> Bool { + do { + let output = try await cipClient.adminGetUser( + input: AdminGetUserInput( + userPoolId: userPoolId, + username: userName + ) + ) + + guard let userStatus = output.userStatus else { + print("*** Unable to get the user's status.") + return false + } + + print("User status: \(userStatus)") + return true + } catch { + return false + } + } + // snippet-end:[swift.cognito-identity-provider.AdminGetUser] + + // snippet-start:[swift.cognito-identity-provider.SignUp] + /// Create a new user in a user pool. + /// + /// - Parameters: + /// - cipClient: The `CognitoIdentityProviderClient` to use. + /// - clientId: The ID of the app client to create a user for. + /// - userName: The username for the new user. + /// - password: The new user's password. + /// - email: The new user's email address. + /// + /// - Returns: `true` if successful; otherwise `false`. + func signUp(cipClient: CognitoIdentityProviderClient, clientId: String, userName: String, password: String, email: String) async -> Bool { + let emailAttr = CognitoIdentityProviderClientTypes.AttributeType( + name: "email", + value: email + ) + + let userAttrsList = [emailAttr] + + do { + _ = try await cipClient.signUp( + input: SignUpInput( + clientId: clientId, + password: password, + userAttributes: userAttrsList, + username: userName + ) + + ) + + print("=====> User \(userName) signed up.") + } catch _ as AWSCognitoIdentityProvider.UsernameExistsException { + print("*** The username \(userName) already exists. Please use a different one.") + return false + } catch let error as AWSCognitoIdentityProvider.InvalidPasswordException { + print("*** Error: The specified password is invalid. Reason: \(error.properties.message ?? "").") + return false + } catch _ as AWSCognitoIdentityProvider.ResourceNotFoundException { + print("*** Error: The specified client ID (\(clientId)) doesn't exist.") + return false + } catch { + print("*** Unexpected error: \(error)") + return false + } + + return true + } + // snippet-end:[swift.cognito-identity-provider.SignUp] + + // snippet-start:[swift.cognito-identity-provider.ResendConfirmationCode] + /// Requests a new confirmation code be sent to the given user's contact + /// method. + /// + /// - Parameters: + /// - cipClient: The `CognitoIdentityProviderClient` to use. + /// - clientId: The application client ID. + /// - userName: The user to resend a code for. + /// + /// - Returns: `true` if a new code was sent successfully, otherwise + /// `false`. + func resendConfirmationCode(cipClient: CognitoIdentityProviderClient, clientId: String, + userName: String) async -> Bool { + do { + let output = try await cipClient.resendConfirmationCode( + input: ResendConfirmationCodeInput( + clientId: clientId, + username: userName + ) + ) + + guard let deliveryMedium = output.codeDeliveryDetails?.deliveryMedium else { + print("*** Unable to get the delivery method for the resent code.") + return false + } + + print("=====> A new code has been sent by \(deliveryMedium)") + return true + } catch { + print("*** Unable to resend the confirmation code to user \(userName).") + return false + } + } + // snippet-end:[swift.cognito-identity-provider.ResendConfirmationCode] + + // snippet-start:[swift.cognito-identity-provider.ConfirmSignUp] + /// Submit a confirmation code for the specified user. This is the code as + /// entered by the user after they've received it by email or text + /// message. + /// + /// - Parameters: + /// - cipClient: The `CognitoIdentityProviderClient` to use. + /// - clientId: The app client ID the user is signing up for. + /// - userName: The username of the user whose code is being sent. + /// - code: The user's confirmation code. + /// + /// - Returns: `true` if the code was successfully confirmed; otherwise `false`. + func confirmSignUp(cipClient: CognitoIdentityProviderClient, clientId: String, + userName: String, code: String) async -> Bool { + do { + _ = try await cipClient.confirmSignUp( + input: ConfirmSignUpInput( + clientId: clientId, + confirmationCode: code, + username: userName + ) + ) + + print("=====> \(userName) has been confirmed.") + return true + } catch { + print("=====> \(userName)'s code was entered incorrectly.") + return false + } + } + // snippet-end:[swift.cognito-identity-provider.ConfirmSignUp] + + // snippet-start:[swift.cognito-identity-provider.AdminInitiateAuth] + /// Begin an authentication session. + /// + /// - Parameters: + /// - cipClient: The `CongitoIdentityProviderClient` to use. + /// - clientId: The app client ID to use. + /// - userName: The username to check. + /// - password: The user's password. + /// - userPoolId: The user pool to use. + /// + /// - Returns: The session token associated with this authentication + /// session. + func initiateAuth(cipClient: CognitoIdentityProviderClient, clientId: String, + userName: String, password: String, + userPoolId: String) async -> String? { + var authParams: [String: String] = [:] + + authParams["USERNAME"] = userName + authParams["PASSWORD"] = password + + do { + let output = try await cipClient.adminInitiateAuth( + input: AdminInitiateAuthInput( + authFlow: CognitoIdentityProviderClientTypes.AuthFlowType.adminUserPasswordAuth, + authParameters: authParams, + clientId: clientId, + userPoolId: userPoolId + ) + ) + + guard let challengeName = output.challengeName else { + print("*** Invalid response from the auth service.") + return nil + } + + print("=====> Response challenge is \(challengeName)") + + return output.session + } catch _ as UserNotFoundException { + print("*** The specified username, \(userName), doesn't exist.") + return nil + } catch _ as UserNotConfirmedException { + print("*** The user \(userName) has not been confirmed.") + return nil + } catch { + print("*** An unexpected error occurred.") + return nil + } + } + // snippet-end:[swift.cognito-identity-provider.AdminInitiateAuth] + + // snippet-start:[swift.cognito-identity-provider.AssociateSoftwareToken] + /// Request and display an MFA secret token that the user should enter + /// into their authenticator to set it up for the user account. + /// + /// - Parameters: + /// - cipClient: The `CognitoIdentityProviderClient` to use. + /// - authSession: The authentication session to request an MFA secret + /// for. + /// + /// - Returns: A string containing the MFA secret token that should be + /// entered into the authenticator software. + func getSecretForAppMFA(cipClient: CognitoIdentityProviderClient, authSession: String?) async -> String? { + do { + let output = try await cipClient.associateSoftwareToken( + input: AssociateSoftwareTokenInput( + session: authSession + ) + ) + + guard let secretCode = output.secretCode else { + print("*** Unable to get the secret code") + return nil + } + + print("=====> Enter this token into Google Authenticator: \(secretCode)") + return output.session + } catch _ as SoftwareTokenMFANotFoundException { + print("*** The specified user pool isn't configured for MFA.") + return nil + } catch { + print("*** An unexpected error occurred getting the secret for the app's MFA.") + return nil + } + } + // snippet-end:[swift.cognito-identity-provider.AssociateSoftwareToken] + + // snippet-start:[swift.cognito-identity-provider.VerifySoftwareToken] + /// Confirm that the user's TOTP authenticator is configured correctly by + /// sending a code to it to check that it matches successfully. + /// + /// - Parameters: + /// - cipClient: The `CongnitoIdentityProviderClient` to use. + /// - session: An authentication session previously returned by an + /// `associateSoftwareToken()` call. + /// - mfaCode: The 6-digit code currently displayed by the user's + /// authenticator, as provided by the user. + func verifyTOTP(cipClient: CognitoIdentityProviderClient, session: String?, mfaCode: String?) async { + do { + let output = try await cipClient.verifySoftwareToken( + input: VerifySoftwareTokenInput( + session: session, + userCode: mfaCode + ) + ) + + guard let tokenStatus = output.status else { + print("*** Unable to get the token's status.") + return + } + print("=====> The token's status is: \(tokenStatus)") + } catch _ as SoftwareTokenMFANotFoundException { + print("*** The specified user pool isn't configured for MFA.") + return + } catch _ as CodeMismatchException { + print("*** The specified MFA code doesn't match the expected value.") + return + } catch _ as UserNotFoundException { + print("*** The specified username doesn't exist.") + return + } catch _ as UserNotConfirmedException { + print("*** The user has not been confirmed.") + return + } catch { + print("*** Error verifying the MFA token!") + return + } + } + // snippet-end:[swift.cognito-identity-provider.VerifySoftwareToken] + + // snippet-start:[swift.cognito-identity-provider.AdminRespondToAuthChallenge] + /// Respond to the authentication challenge received from Cognito after + /// initiating an authentication session. This involves sending a current + /// MFA code to the service. + /// + /// - Parameters: + /// - cipClient: The `CognitoIdentityProviderClient` to use. + /// - userName: The user's username. + /// - clientId: The app client ID. + /// - userPoolId: The user pool to sign into. + /// - mfaCode: The 6-digit MFA code currently displayed by the user's + /// authenticator. + /// - session: The authentication session to continue processing. + func adminRespondToAuthChallenge(cipClient: CognitoIdentityProviderClient, userName: String, + clientId: String, userPoolId: String, mfaCode: String, + session: String) async { + print("=====> SOFTWARE_TOKEN_MFA challenge is generated...") + + var challengeResponsesOb: [String: String] = [:] + challengeResponsesOb["USERNAME"] = userName + challengeResponsesOb["SOFTWARE_TOKEN_MFA_CODE"] = mfaCode + + do { + let output = try await cipClient.adminRespondToAuthChallenge( + input: AdminRespondToAuthChallengeInput( + challengeName: CognitoIdentityProviderClientTypes.ChallengeNameType.softwareTokenMfa, + challengeResponses: challengeResponsesOb, + clientId: clientId, + session: session, + userPoolId: userPoolId + ) + ) + + guard let authenticationResult = output.authenticationResult else { + print("*** Unable to get authentication result.") + return + } + + print("=====> Authentication result (JWTs are redacted):") + print(authenticationResult) + } catch _ as SoftwareTokenMFANotFoundException { + print("*** The specified user pool isn't configured for MFA.") + return + } catch _ as CodeMismatchException { + print("*** The specified MFA code doesn't match the expected value.") + return + } catch _ as UserNotFoundException { + print("*** The specified username, \(userName), doesn't exist.") + return + } catch _ as UserNotConfirmedException { + print("*** The user \(userName) has not been confirmed.") + return + } catch let error as NotAuthorizedException { + print("*** Unauthorized access. Reason: \(error.properties.message ?? "")") + } catch { + print("*** Error responding to the MFA challenge.") + return + } + } + // snippet-end:[swift.cognito-identity-provider.AdminRespondToAuthChallenge] + + /// Called by ``main()`` to run the bulk of the example. + func runAsync() async throws { + let config = try await CognitoIdentityProviderClient.CognitoIdentityProviderClientConfiguration(region: region) + let cipClient = CognitoIdentityProviderClient(config: config) + + print(""" + This example collects information about a user, then creates that user in the + specified user pool. Then, it enables Multi-Factor Authentication (MFA) for that + user by associating an authenticator application (such as Google Authenticator + or a password manager that supports TOTP). Then, the user uses a code from their + authenticator application to sign in. + + """) + + let userName = stringRequest("Please enter a new username: ") + let password = stringRequest("Enter a password: ") + let email = stringRequest("Enter your email address: ", minLength: 5) + + // Submit the sign-up request to AWS. + + print("==> Signing up user \(userName)...") + if await signUp(cipClient: cipClient, clientId: clientId, + userName: userName, password: password, + email: email) == false { + return + } + + // Check the user's status. This time, it should come back "unconfirmed". + + print("==> Getting the status of user \(userName) from the user pool (should be 'unconfirmed')...") + if await adminGetUser(cipClient: cipClient, userName: userName, userPoolId: poolId) == false { + return + } + + // Ask the user if they want a replacement code sent, such as if the + // code hasn't arrived yet. If the user responds with a "yes," send a + // new code. + + if yesNoRequest("==> A confirmation code was sent to \(userName). Would you like to send a new code (Y/N)? ") { + print("==> Sending a new confirmation code...") + if await resendConfirmationCode(cipClient: cipClient, clientId: clientId, userName: userName) == false { + return + } + } + + // Ask the user to enter the confirmation code, then send it to Amazon + // Cognito to verify it. + + let code = stringRequest("==> Enter the confirmation code sent to \(userName): ") + if await confirmSignUp(cipClient: cipClient, clientId: clientId, userName: userName, code: code) == false { + // The code didn't match. Your application may wish to offer to + // re-send the confirmation code here and try again. + return + } + + // Check the user's status again. This time it should come back + // "confirmed". + + print("==> Rechecking status of user \(userName) in the user pool (should be 'confirmed')...") + if await adminGetUser(cipClient: cipClient, userName: userName, userPoolId: poolId) == false { + return + } + // Check the challenge mode. Here, it should be "mfaSetup", indicating + // that the user needs to add MFA before using it. This returns a + // session that can be used to register MFA, or nil if an error occurs. + + let authSession = await initiateAuth(cipClient: cipClient, clientId: clientId, + userName: userName, password: password, + userPoolId: poolId) + if authSession == nil { + return + } + + // Ask Cognito for an MFA secret token that the user should enter into + // their authenticator software (such as Google Authenticator) or + // password manager to configure it for this user account. This + // returns a new session that should be used for the new stage of the + // authentication process. + + let newSession = await getSecretForAppMFA(cipClient: cipClient, authSession: authSession) + if newSession == nil { + return + } + + // Ask the user to enter the current 6-digit code displayed by their + // authenticator. Then verify that it matches the value expected for + // the session. + + let mfaCode1 = stringRequest("==> Enter the 6-digit code displayed in your authenticator: ", + minLength: 6) + await verifyTOTP(cipClient: cipClient, session: newSession, mfaCode: mfaCode1) + + // Ask the user to authenticate now that the authenticator has been + // configured. This creates a new session using the user's username + // and password as already entered. + + print("\nNow starting the sign-in process for user \(userName)...\n") + + let session2 = await initiateAuth(cipClient: cipClient, clientId: clientId, + userName: userName, password: password, userPoolId: poolId) + guard let session2 else { + return + } + + // Now that we have a new auth session, `session2`, ask the user for a + // new 6-digit code from their authenticator, and send it to the auth + // session. + + let mfaCode2 = stringRequest("==> Wait for your authenticator to show a new 6-digit code, then enter it: ", + minLength: 6) + await adminRespondToAuthChallenge(cipClient: cipClient, userName: userName, + clientId: clientId, userPoolId: poolId, + mfaCode: mfaCode2, session: session2) + } +} + +/// The program's asynchronous entry point. +@main +struct Main { + static func main() async { + let args = Array(CommandLine.arguments.dropFirst()) + + do { + let command = try ExampleCommand.parse(args) + try await command.runAsync() + } catch { + ExampleCommand.exit(withError: error) + } + } +} +// snippet-end:[swift.cognito-identity-provider.scenario] diff --git a/swift/example_code/cognito-identity-provider/scenario/test.sh b/swift/example_code/cognito-identity-provider/scenario/test.sh new file mode 100755 index 00000000000..e69de29bb2d