From 9b71ce7bb8be03e054a84011209c21763a79d152 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Mon, 10 Jul 2023 14:58:54 -0500 Subject: [PATCH 01/28] initial state machine changes --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 6 +- .../cognito/actions/SignInCognitoActions.kt | 23 +++- .../AWSCognitoAuthConfirmSignInOptions.kt | 17 ++- .../codegen/actions/SetupTOTPActions.kt | 24 ++++ .../codegen/actions/SignInActions.kt | 1 + .../codegen/data/SignInTOTPSetupData.kt | 21 +++ .../codegen/events/SetupTOTPEvent.kt | 35 +++++ .../codegen/events/SignInEvent.kt | 14 +- .../codegen/states/SetupTOTPState.kt | 128 ++++++++++++++++++ .../codegen/states/SignInState.kt | 83 ++++++++++-- .../com/amplifyframework/TOTPSetupDetails.kt | 29 ++++ .../java/com/amplifyframework/auth/MFAType.kt | 20 +++ .../auth/result/step/AuthNextSignInStep.java | 42 +++++- .../auth/result/step/AuthSignInStep.java | 23 +++- 14 files changed, 436 insertions(+), 30 deletions(-) create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt create mode 100644 core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt create mode 100644 core/src/main/java/com/amplifyframework/auth/MFAType.kt diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 24a2abafec..4c632b9417 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -567,7 +567,7 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.cancel(token) val authSignInResult = AuthSignInResult( true, - AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null) + AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null, null, null) ) onSuccess.accept(authSignInResult) sendHubEvent(AuthChannelEventName.SIGNED_IN.toString()) @@ -647,7 +647,7 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.cancel(token) val authSignInResult = AuthSignInResult( true, - AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null) + AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null, null, null) ) onSuccess.accept(authSignInResult) sendHubEvent(AuthChannelEventName.SIGNED_IN.toString()) @@ -853,7 +853,7 @@ internal class RealAWSCognitoAuthPlugin( val authSignInResult = AuthSignInResult( true, - AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null) + AuthNextSignInStep(AuthSignInStep.DONE, mapOf(), null, null, null) ) onSuccess.accept(authSignInResult) sendHubEvent(AuthChannelEventName.SIGNED_IN.toString()) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index bb92d7db1e..8891196746 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -32,6 +32,7 @@ import com.amplifyframework.statemachine.codegen.events.CustomSignInEvent import com.amplifyframework.statemachine.codegen.events.DeviceSRPSignInEvent import com.amplifyframework.statemachine.codegen.events.HostedUIEvent import com.amplifyframework.statemachine.codegen.events.SRPEvent +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent import com.amplifyframework.statemachine.codegen.events.SignInChallengeEvent import com.amplifyframework.statemachine.codegen.events.SignInEvent @@ -48,7 +49,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartCustomAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = CustomSignInEvent( - CustomSignInEvent.EventType.InitiateCustomSignIn(event.username, event.metadata) + CustomSignInEvent.EventType.InitiateCustomSignIn(event.username, event.metadata), ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -58,7 +59,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartMigrationAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = SignInEvent( - SignInEvent.EventType.InitiateMigrateAuth(event.username, event.password, event.metadata) + SignInEvent.EventType.InitiateMigrateAuth(event.username, event.password, event.metadata), ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -76,7 +77,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartDeviceSRPAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = DeviceSRPSignInEvent( - DeviceSRPSignInEvent.EventType.RespondDeviceSRPChallenge(event.username, event.metadata) + DeviceSRPSignInEvent.EventType.RespondDeviceSRPChallenge(event.username, event.metadata), ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -108,20 +109,20 @@ internal object SignInCognitoActions : SignInActions { this.passwordVerifier = deviceVerifierMap["verifier"] this.salt = deviceVerifierMap["salt"] } - } + }, ) ?: throw ServiceException("Sign in failed", AmplifyException.TODO_RECOVERY_SUGGESTION) val updatedDeviceMetadata = deviceMetadata.copy(deviceSecret = deviceVerifierMap["secret"]) credentialStoreClient.storeCredentials( CredentialType.Device(event.signedInData.username), - AmplifyCredential.DeviceData(updatedDeviceMetadata) + AmplifyCredential.DeviceData(updatedDeviceMetadata), ) AuthenticationEvent( AuthenticationEvent.EventType.SignInCompleted( event.signedInData, - DeviceMetadata.Metadata(deviceKey, deviceGroupKey) - ) + DeviceMetadata.Metadata(deviceKey, deviceGroupKey), + ), ) } catch (e: Exception) { SignInEvent(SignInEvent.EventType.ThrowError(e)) @@ -137,4 +138,12 @@ internal object SignInCognitoActions : SignInActions { logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) } + + override fun initiateTOTOSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = + Action("initiateTOTOSetup") { id, dispatcher -> + logger.verbose("$id Starting execution") + val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP(event.signInTOTPSetupData)) + logger.verbose("$id Sending event ${evt.type}") + dispatcher.send(evt) + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthConfirmSignInOptions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthConfirmSignInOptions.kt index 88bcc66fc1..08cf7fc78d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthConfirmSignInOptions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthConfirmSignInOptions.kt @@ -31,7 +31,12 @@ data class AWSCognitoAuthConfirmSignInOptions internal constructor( * Get additional user attributes which should be associated with this user on confirmSignIn. * @return additional user attributes which should be associated with this user on confirmSignIn */ - val userAttributes: List + val userAttributes: List, + /** + * Get the friendly device name used to setup TOTP. + * @return friendly device name + */ + val friendlyDeviceName: String? ) : AuthConfirmSignInOptions() { companion object { @@ -53,6 +58,7 @@ data class AWSCognitoAuthConfirmSignInOptions internal constructor( class CognitoBuilder : Builder() { private var metadata: Map = mapOf() private var userAttributes: List = listOf() + private var friendlyDeviceName: String? = null /** * Returns the type of builder this is to support proper flow with it being an extended class. @@ -77,10 +83,17 @@ data class AWSCognitoAuthConfirmSignInOptions internal constructor( */ fun userAttributes(userAttributes: List) = apply { this.userAttributes = userAttributes } + /** + * Set the friendlyDeviceName field for the object being built. + * @param friendlyDeviceName friendly name of the device used to setup totp. + * @return the instance of the builder. + */ + fun friendlyDeviceName(friendlyDeviceName: String) = apply { this.friendlyDeviceName = friendlyDeviceName } + /** * Construct and return the object with the values set in the builder. * @return a new instance of AWSCognitoAuthConfirmSignInOptions with the values specified in the builder. */ - override fun build() = AWSCognitoAuthConfirmSignInOptions(metadata, userAttributes) + override fun build() = AWSCognitoAuthConfirmSignInOptions(metadata, userAttributes, friendlyDeviceName) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt new file mode 100644 index 0000000000..c7b7703917 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.statemachine.codegen.actions + +import com.amplifyframework.statemachine.Action +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent + +internal interface SetupTOTPActions { + fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action + fun verifyChallengeAnswer(eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer): Action + fun respondToAuthChallenge(eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge): Action +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt index bdb56e9184..afb4af1363 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt @@ -27,4 +27,5 @@ internal interface SignInActions { fun initResolveChallenge(event: SignInEvent.EventType.ReceivedChallenge): Action fun confirmDevice(event: SignInEvent.EventType.ConfirmDevice): Action fun startHostedUIAuthAction(event: SignInEvent.EventType.InitiateHostedUISignIn): Action + fun initiateTOTOSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt new file mode 100644 index 0000000000..7d104003f5 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.statemachine.codegen.data + +internal data class SignInTOTPSetupData( + val secretCode: String, + val session: String, + val username: String +) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt new file mode 100644 index 0000000000..effd03d996 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.statemachine.codegen.events + +import com.amplifyframework.TOTPSetupDetails +import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData +import java.util.Date + +internal class SetupTOTPEvent(val eventType: EventType, override val time: Date? = null) : + StateMachineEvent { + + sealed class EventType { + data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData) : EventType() + data class WaitForAnswer(val totpSetupDetails: TOTPSetupDetails) : EventType() + data class ThrowAuthError(val exception: Exception) : EventType() + data class VerifyChallengeAnswer(val answer: String) : EventType() + data class RespondToAuthChallenge(val id: String = "") : EventType() + data class Verified(val id: String = "") : EventType() + } + + override val type: String = eventType.javaClass.simpleName +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt index 9a2b2d9cdc..2d7fa2b8e9 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SignInEvent.kt @@ -19,6 +19,7 @@ import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.DeviceMetadata import com.amplifyframework.statemachine.codegen.data.SignInData +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import com.amplifyframework.statemachine.codegen.data.SignedInData import java.util.Date @@ -27,40 +28,41 @@ internal class SignInEvent(val eventType: EventType, override val time: Date? = data class InitiateSignInWithSRP( val username: String, val password: String, - val metadata: Map + val metadata: Map, ) : EventType() data class InitiateSignInWithCustom( val username: String, - val metadata: Map + val metadata: Map, ) : EventType() data class InitiateCustomSignInWithSRP( val username: String, val password: String, - val metadata: Map + val metadata: Map, ) : EventType() data class InitiateMigrateAuth( val username: String, val password: String, - val metadata: Map + val metadata: Map, ) : EventType() data class InitiateHostedUISignIn(val hostedUISignInData: SignInData.HostedUISignInData) : EventType() data class SignedIn(val id: String = "") : EventType() data class InitiateSignInWithDeviceSRP( val username: String, - val metadata: Map + val metadata: Map, ) : EventType() data class ConfirmDevice( val deviceMetadata: DeviceMetadata.Metadata, - val signedInData: SignedInData + val signedInData: SignedInData, ) : EventType() data class FinalizeSignIn(val id: String = "") : EventType() data class ReceivedChallenge(val challenge: AuthChallenge) : EventType() data class ThrowError(val exception: Exception) : EventType() + data class InitiateTOTPSetup(val signInTOTPSetupData: SignInTOTPSetupData) : EventType() } override val type: String = eventType.javaClass.simpleName diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt new file mode 100644 index 0000000000..9924347e02 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.statemachine.codegen.states + +import com.amplifyframework.TOTPSetupDetails +import com.amplifyframework.statemachine.State +import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.StateMachineResolver +import com.amplifyframework.statemachine.StateResolution +import com.amplifyframework.statemachine.codegen.actions.SetupTOTPActions +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent + +internal sealed class SetupTOTPState : State { + data class NotStarted(val id: String = "") : SetupTOTPState() + data class SetupTOTP(val signInTOTPSetupData: SignInTOTPSetupData) : SetupTOTPState() + data class WaitingForAnswer(val setupTOTPSetupDetails: TOTPSetupDetails) : SetupTOTPState() + data class Verifying(val code: String) : SetupTOTPState() + data class RespondingToAuthChallenge(val id: String = "") : SetupTOTPState() + data class Success(val id: String = "") : SetupTOTPState() + data class Error(val exception: Exception) : SetupTOTPState() + + class Resolver(private val setupTOTPActions: SetupTOTPActions) : StateMachineResolver { + override val defaultState = NotStarted() + + override fun resolve(oldState: SetupTOTPState, event: StateMachineEvent): StateResolution { + val defaultResolution = StateResolution(oldState) + val challengeEvent = (event as? SetupTOTPEvent)?.eventType + return when (oldState) { + is NotStarted -> when (challengeEvent) { + is SetupTOTPEvent.EventType.SetupTOTP -> { + StateResolution( + SetupTOTP(challengeEvent.totpSetupDetails), + listOf(setupTOTPActions.initiateTOTPSetup(challengeEvent)), + ) + } + + is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( + Error(challengeEvent.exception), + ) + + else -> defaultResolution + } + + is SetupTOTP -> when (challengeEvent) { + is SetupTOTPEvent.EventType.WaitForAnswer -> { + StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails)) + } + + is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( + Error(challengeEvent.exception), + ) + + else -> defaultResolution + } + + is WaitingForAnswer -> when (challengeEvent) { + is SetupTOTPEvent.EventType.VerifyChallengeAnswer -> { + StateResolution( + Verifying(challengeEvent.answer), + listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), + ) + } + + is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( + Error(challengeEvent.exception), + ) + + else -> defaultResolution + } + + is Verifying -> when (challengeEvent) { + is SetupTOTPEvent.EventType.RespondToAuthChallenge -> { + StateResolution( + RespondingToAuthChallenge(), + listOf(setupTOTPActions.respondToAuthChallenge(challengeEvent)), + ) + } + + is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( + Error(challengeEvent.exception), + ) + + else -> defaultResolution + } + + is RespondingToAuthChallenge -> when (challengeEvent) { + is SetupTOTPEvent.EventType.Verified -> { + StateResolution( + Success(), + ) + } + + is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( + Error(challengeEvent.exception), + ) + + else -> defaultResolution + } + + is Error -> when (challengeEvent) { + is SetupTOTPEvent.EventType.VerifyChallengeAnswer -> { + StateResolution( + Verifying(challengeEvent.answer), + listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), + ) + } + + else -> defaultResolution + } + + else -> defaultResolution + } + } + } +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index 5fecde353d..e5a4ddfb40 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -31,6 +31,7 @@ internal sealed class SignInState : State { data class SigningInViaMigrateAuth(override var migrateSignInState: MigrateSignInState?) : SignInState() data class ResolvingDeviceSRP(override var deviceSRPSignInState: DeviceSRPSignInState?) : SignInState() data class ResolvingChallenge(override var challengeState: SignInChallengeState?) : SignInState() + data class ResolvingTOTPSetup(override var setupTOTPState: SetupTOTPState?) : SignInState() data class ConfirmingDevice(val id: String = "") : SignInState() data class Done(val id: String = "") : SignInState() data class Error(val exception: Exception) : SignInState() @@ -42,6 +43,7 @@ internal sealed class SignInState : State { open var migrateSignInState: MigrateSignInState? = MigrateSignInState.NotStarted() open var hostedUISignInState: HostedUISignInState? = HostedUISignInState.NotStarted() open var deviceSRPSignInState: DeviceSRPSignInState? = DeviceSRPSignInState.NotStarted() + open var setupTOTPState: SetupTOTPState? = SetupTOTPState.NotStarted() class Resolver( private val srpSignInResolver: StateMachineResolver, @@ -50,6 +52,7 @@ internal sealed class SignInState : State { private val challengeResolver: StateMachineResolver, private val hostedUISignInResolver: StateMachineResolver, private val deviceSRPSignInResolver: StateMachineResolver, + private val setupTOTPResolver: StateMachineResolver, private val signInActions: SignInActions, ) : StateMachineResolver { @@ -94,12 +97,16 @@ internal sealed class SignInState : State { actions += it.actions } + oldState.setupTOTPState?.let { setupTOTPResolver.resolve(it, event) }?.let { + builder.setupTOTPState = it.newState + actions += it.actions + } return StateResolution(builder.build(), actions) } private fun resolveSignInEvent( oldState: SignInState, - event: StateMachineEvent + event: StateMachineEvent, ): StateResolution { val signInEvent = asSignInEvent(event) val defaultResolution = StateResolution(oldState) @@ -107,75 +114,133 @@ internal sealed class SignInState : State { is NotStarted -> when (signInEvent) { is SignInEvent.EventType.InitiateSignInWithSRP -> StateResolution( SigningInWithSRP(oldState.srpSignInState), - listOf(signInActions.startSRPAuthAction(signInEvent)) + listOf(signInActions.startSRPAuthAction(signInEvent)), ) + is SignInEvent.EventType.InitiateSignInWithCustom -> StateResolution( SigningInWithCustom(oldState.customSignInState), - listOf(signInActions.startCustomAuthAction(signInEvent)) + listOf(signInActions.startCustomAuthAction(signInEvent)), ) + is SignInEvent.EventType.InitiateHostedUISignIn -> StateResolution( SigningInWithHostedUI(HostedUISignInState.NotStarted()), - listOf(signInActions.startHostedUIAuthAction(signInEvent)) + listOf(signInActions.startHostedUIAuthAction(signInEvent)), ) + is SignInEvent.EventType.InitiateMigrateAuth -> StateResolution( SigningInViaMigrateAuth(MigrateSignInState.NotStarted()), - listOf(signInActions.startMigrationAuthAction(signInEvent)) + listOf(signInActions.startMigrationAuthAction(signInEvent)), ) + is SignInEvent.EventType.InitiateCustomSignInWithSRP -> StateResolution( SigningInWithSRPCustom(oldState.srpSignInState), - listOf(signInActions.startCustomAuthWithSRPAction(signInEvent)) + listOf(signInActions.startCustomAuthWithSRPAction(signInEvent)), ) + else -> defaultResolution } + is SigningInWithSRP, is SigningInWithCustom, is SigningInViaMigrateAuth, - is SigningInWithSRPCustom -> when (signInEvent) { + is SigningInWithSRPCustom, + -> when (signInEvent) { is SignInEvent.EventType.ReceivedChallenge -> { val action = signInActions.initResolveChallenge(signInEvent) StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action)) } + is SignInEvent.EventType.InitiateSignInWithDeviceSRP -> StateResolution( ResolvingDeviceSRP(DeviceSRPSignInState.NotStarted()), - listOf(signInActions.startDeviceSRPAuthAction(signInEvent)) + listOf(signInActions.startDeviceSRPAuthAction(signInEvent)), ) + is SignInEvent.EventType.ConfirmDevice -> { val action = signInActions.confirmDevice(signInEvent) StateResolution(ConfirmingDevice(), listOf(action)) } + + is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( + ResolvingTOTPSetup(SetupTOTPState.NotStarted()), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + ) + is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) else -> defaultResolution } + is ResolvingChallenge -> when (signInEvent) { is SignInEvent.EventType.ConfirmDevice -> { val action = signInActions.confirmDevice(signInEvent) StateResolution(ConfirmingDevice(), listOf(action)) } + is SignInEvent.EventType.ReceivedChallenge -> { val action = signInActions.initResolveChallenge(signInEvent) StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action)) } + + is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( + ResolvingTOTPSetup(SetupTOTPState.NotStarted()), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + ) + is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) else -> defaultResolution } + + is ResolvingTOTPSetup -> when (signInEvent) { + is SignInEvent.EventType.ReceivedChallenge -> { + val action = signInActions.initResolveChallenge(signInEvent) + StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action)) + } + + is SignInEvent.EventType.ConfirmDevice -> { + val action = signInActions.confirmDevice(signInEvent) + StateResolution(ConfirmingDevice(), listOf(action)) + } + + is SignInEvent.EventType.InitiateSignInWithDeviceSRP -> StateResolution( + ResolvingDeviceSRP(DeviceSRPSignInState.NotStarted()), + listOf(signInActions.startDeviceSRPAuthAction(signInEvent)), + ) + + is SignInEvent.EventType.FinalizeSignIn -> { + StateResolution(SignedIn()) + } + + else -> defaultResolution + } + is ResolvingDeviceSRP -> when (signInEvent) { is SignInEvent.EventType.ReceivedChallenge -> { val action = signInActions.initResolveChallenge(signInEvent) StateResolution(ResolvingChallenge(oldState.challengeState), listOf(action)) } + + is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( + ResolvingTOTPSetup(SetupTOTPState.NotStarted()), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + ) + is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) + else -> defaultResolution } + is ConfirmingDevice -> when (signInEvent) { is SignInEvent.EventType.FinalizeSignIn -> { StateResolution(SignedIn()) } + is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) else -> defaultResolution } + is SigningInWithHostedUI -> when (signInEvent) { is SignInEvent.EventType.SignedIn -> StateResolution(Done()) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) else -> defaultResolution } + else -> defaultResolution } } @@ -189,6 +254,7 @@ internal sealed class SignInState : State { var migrateSignInState: MigrateSignInState? = null var hostedUISignInState: HostedUISignInState? = null var deviceSRPSignInState: DeviceSRPSignInState? = null + var setupTOTPState: SetupTOTPState? = null override fun build(): SignInState = when (signInState) { is SigningInWithSRP -> SigningInWithSRP(srpSignInState) @@ -198,6 +264,7 @@ internal sealed class SignInState : State { is SigningInWithHostedUI -> SigningInWithHostedUI(hostedUISignInState) is SigningInWithSRPCustom -> SigningInWithSRPCustom(srpSignInState) is ResolvingDeviceSRP -> ResolvingDeviceSRP(deviceSRPSignInState) + is SetupTOTPState -> ResolvingTOTPSetup(setupTOTPState) else -> signInState } } diff --git a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt new file mode 100644 index 0000000000..a32beaa2ac --- /dev/null +++ b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework + +import android.net.Uri + +data class TOTPSetupDetails( + val sharedSecret: String, + val username: String, +) { + fun getSetupURI( + issuer: String, + accountName: String = username, + ): Uri { + TODO() + } +} diff --git a/core/src/main/java/com/amplifyframework/auth/MFAType.kt b/core/src/main/java/com/amplifyframework/auth/MFAType.kt new file mode 100644 index 0000000000..34f6ac43aa --- /dev/null +++ b/core/src/main/java/com/amplifyframework/auth/MFAType.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth + +enum class MFAType { + SMS, + TOTP, +} diff --git a/core/src/main/java/com/amplifyframework/auth/result/step/AuthNextSignInStep.java b/core/src/main/java/com/amplifyframework/auth/result/step/AuthNextSignInStep.java index ad926c956e..5736912c60 100644 --- a/core/src/main/java/com/amplifyframework/auth/result/step/AuthNextSignInStep.java +++ b/core/src/main/java/com/amplifyframework/auth/result/step/AuthNextSignInStep.java @@ -19,11 +19,14 @@ import androidx.annotation.Nullable; import androidx.core.util.ObjectsCompat; +import com.amplifyframework.TOTPSetupDetails; import com.amplifyframework.auth.AuthCodeDeliveryDetails; +import com.amplifyframework.auth.MFAType; import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * This object represents all details around the next step in the sign in process. It holds an instance of the @@ -35,20 +38,29 @@ public final class AuthNextSignInStep { private final Map additionalInfo; private final AuthCodeDeliveryDetails codeDeliveryDetails; + private final TOTPSetupDetails totpSetupDetails; + private final Set allowedMFATypes; + /** * Gives details on the next step, if there is one, in the sign in flow. * @param signInStep the next step in the sign in flow (could be optional or required) * @param additionalInfo possible extra info to go with the next step (refer to plugin documentation) * @param codeDeliveryDetails Details about how a code was sent, if relevant to the current step + * @param totpSetupDetails Details to setup TOTP, if relevant to the current step + * @param allowedMFATypes Set of allowed MFA type, if relevant to the current step */ public AuthNextSignInStep( @NonNull AuthSignInStep signInStep, @NonNull Map additionalInfo, - @Nullable AuthCodeDeliveryDetails codeDeliveryDetails) { + @Nullable AuthCodeDeliveryDetails codeDeliveryDetails, + @Nullable TOTPSetupDetails totpSetupDetails, + @Nullable Set allowedMFATypes) { this.signInStep = Objects.requireNonNull(signInStep); this.additionalInfo = new HashMap<>(); this.additionalInfo.putAll(Objects.requireNonNull(additionalInfo)); this.codeDeliveryDetails = codeDeliveryDetails; + this.totpSetupDetails = totpSetupDetails; + this.allowedMFATypes = allowedMFATypes; } /** @@ -78,6 +90,24 @@ public AuthCodeDeliveryDetails getCodeDeliveryDetails() { return codeDeliveryDetails; } + /** + * Details about how to setup TOTP. + * @return Details about how to setup TOTP, if relevant to the current step - null otherwise + */ + @Nullable + public TOTPSetupDetails getTotpSetupDetails() { + return totpSetupDetails; + } + + /** + * Set of allowed MFA Types. + * @return Set of allowed MFA Types, if relevant to the current step - null otherwise + */ + @Nullable + public Set getAllowedMFATypes() { + return allowedMFATypes; + } + /** * When overriding, be sure to include signInStep, additionalInfo, and codeDeliveryDetails in the hash. * @return Hash code of this object @@ -87,7 +117,9 @@ public int hashCode() { return ObjectsCompat.hash( getSignInStep(), getAdditionalInfo(), - getCodeDeliveryDetails() + getCodeDeliveryDetails(), + getTotpSetupDetails(), + getAllowedMFATypes() ); } @@ -105,7 +137,9 @@ public boolean equals(Object obj) { AuthNextSignInStep authSignUpResult = (AuthNextSignInStep) obj; return ObjectsCompat.equals(getSignInStep(), authSignUpResult.getSignInStep()) && ObjectsCompat.equals(getAdditionalInfo(), authSignUpResult.getAdditionalInfo()) && - ObjectsCompat.equals(getCodeDeliveryDetails(), authSignUpResult.getCodeDeliveryDetails()); + ObjectsCompat.equals(getCodeDeliveryDetails(), authSignUpResult.getCodeDeliveryDetails()) && + ObjectsCompat.equals(getTotpSetupDetails(), authSignUpResult.getTotpSetupDetails()) && + ObjectsCompat.equals(getAllowedMFATypes(), authSignUpResult.getAllowedMFATypes()); } } @@ -119,6 +153,8 @@ public String toString() { "signInStep=" + getSignInStep() + ", additionalInfo=" + getAdditionalInfo() + ", codeDeliveryDetails=" + getCodeDeliveryDetails() + + ", totpSetupDetails=" + getTotpSetupDetails() + + ", allowedMFATypes=" + getAllowedMFATypes() + '}'; } } diff --git a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java index 4b2dd27558..3373104db3 100644 --- a/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java +++ b/core/src/main/java/com/amplifyframework/auth/result/step/AuthSignInStep.java @@ -27,7 +27,7 @@ public enum AuthSignInStep { * with the code sent via SMS text message to proceed with the sign in flow. */ CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, - + /** * Custom multifactor authentication is enabled on this account and requires you to call * {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} @@ -58,6 +58,27 @@ public enum AuthSignInStep { */ CONFIRM_SIGN_UP, + /** + * Admin requires user to setup TOTP. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with TOTP code to verify. + */ + CONTINUE_SIGN_IN_WITH_TOTP_SETUP, + + /** + * The user account is required to set MFA selection. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with preferred MFA option. + */ + CONTINUE_SIGN_IN_WITH_MFA_SELECTION, + + /** + * TOTP is enabled on this account and requires the user to confirm with the TOTP code. + * Call {@link com.amplifyframework.auth.AuthCategoryBehavior#confirmSignIn(String, Consumer, Consumer)} + * with TOTP Code. + */ + CONFIRM_SIGN_IN_WITH_TOTP_CODE, + /** * No further steps are needed in the sign in flow. */ From 3a51b43dbe60cc244eac6d910bad46d9a2003fcd Mon Sep 17 00:00:00 2001 From: sdhuka Date: Mon, 10 Jul 2023 15:22:51 -0500 Subject: [PATCH 02/28] add missing state machine changes --- .../auth/cognito/AuthStateMachine.kt | 26 ++++++++------ .../actions/SetupTOTPCognitoActions.kt | 34 +++++++++++++++++++ .../cognito/helpers/SignInChallengeHelper.kt | 6 ++-- 3 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt index c0467902a3..356754bdc7 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt @@ -24,6 +24,7 @@ import com.amplifyframework.auth.cognito.actions.FetchAuthSessionCognitoActions import com.amplifyframework.auth.cognito.actions.HostedUICognitoActions import com.amplifyframework.auth.cognito.actions.MigrateAuthCognitoActions import com.amplifyframework.auth.cognito.actions.SRPCognitoActions +import com.amplifyframework.auth.cognito.actions.SetupTOTPCognitoActions import com.amplifyframework.auth.cognito.actions.SignInChallengeCognitoActions import com.amplifyframework.auth.cognito.actions.SignInCognitoActions import com.amplifyframework.auth.cognito.actions.SignInCustomCognitoActions @@ -42,6 +43,7 @@ import com.amplifyframework.statemachine.codegen.states.HostedUISignInState import com.amplifyframework.statemachine.codegen.states.MigrateSignInState import com.amplifyframework.statemachine.codegen.states.RefreshSessionState import com.amplifyframework.statemachine.codegen.states.SRPSignInState +import com.amplifyframework.statemachine.codegen.states.SetupTOTPState import com.amplifyframework.statemachine.codegen.states.SignInChallengeState import com.amplifyframework.statemachine.codegen.states.SignInState import com.amplifyframework.statemachine.codegen.states.SignOutState @@ -49,7 +51,7 @@ import com.amplifyframework.statemachine.codegen.states.SignOutState internal class AuthStateMachine( resolver: StateMachineResolver, environment: Environment, - initialState: AuthState? = null + initialState: AuthState? = null, ) : StateMachine(resolver, environment, initialState = initialState) { constructor(environment: Environment, initialState: AuthState? = null) : this( @@ -62,7 +64,8 @@ internal class AuthStateMachine( SignInChallengeState.Resolver(SignInChallengeCognitoActions), HostedUISignInState.Resolver(HostedUICognitoActions), DeviceSRPSignInState.Resolver(DeviceSRPCognitoSignInActions), - SignInCognitoActions + SetupTOTPState.Resolver(SetupTOTPCognitoActions), + SignInCognitoActions, ), SignOutState.Resolver(SignOutCognitoActions), AuthenticationCognitoActions, @@ -71,15 +74,15 @@ internal class AuthStateMachine( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions), RefreshSessionState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions), - FetchAuthSessionCognitoActions + FetchAuthSessionCognitoActions, ), DeleteUserState.Resolver(DeleteUserCognitoActions), - AuthorizationCognitoActions + AuthorizationCognitoActions, ), - AuthCognitoActions + AuthCognitoActions, ), environment, - initialState + initialState, ) companion object { @@ -93,7 +96,8 @@ internal class AuthStateMachine( SignInChallengeState.Resolver(SignInChallengeCognitoActions).logging(), HostedUISignInState.Resolver(HostedUICognitoActions).logging(), DeviceSRPSignInState.Resolver(DeviceSRPCognitoSignInActions).logging(), - SignInCognitoActions + SetupTOTPState.Resolver(SetupTOTPCognitoActions).logging(), + SignInCognitoActions, ).logging(), SignOutState.Resolver(SignOutCognitoActions).logging(), AuthenticationCognitoActions, @@ -102,14 +106,14 @@ internal class AuthStateMachine( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions).logging(), RefreshSessionState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions).logging(), - FetchAuthSessionCognitoActions + FetchAuthSessionCognitoActions, ).logging(), DeleteUserState.Resolver(DeleteUserCognitoActions), - AuthorizationCognitoActions + AuthorizationCognitoActions, ).logging(), - AuthCognitoActions + AuthCognitoActions, ).logging(), - environment + environment, ) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt new file mode 100644 index 0000000000..0c96409a33 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.cognito.actions + +import com.amplifyframework.statemachine.Action +import com.amplifyframework.statemachine.codegen.actions.SetupTOTPActions +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent + +internal object SetupTOTPCognitoActions: SetupTOTPActions { + override fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action { + TODO("Not yet implemented") + } + + override fun verifyChallengeAnswer(eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer): Action { + TODO("Not yet implemented") + } + + override fun respondToAuthChallenge(eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge): Action { + TODO("Not yet implemented") + } + +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index 42c1ac377f..c344ec4c65 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -107,21 +107,21 @@ internal object SignInChallengeHelper { ) val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, mapOf(), deliveryDetails) + AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, mapOf(), deliveryDetails, null, null) ) onSuccess.accept(authSignInResult) } is ChallengeNameType.NewPasswordRequired -> { val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD, challengeParams, null) + AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD, challengeParams, null, null, null) ) onSuccess.accept(authSignInResult) } is ChallengeNameType.CustomChallenge -> { val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE, challengeParams, null) + AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE, challengeParams, null, null, null) ) onSuccess.accept(authSignInResult) } From b78d6155d67c532f780f246dfb674eacc4631942 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 11 Jul 2023 11:23:41 -0500 Subject: [PATCH 03/28] Add MFASetup implementation --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 41 +++++- .../actions/SetupTOTPCognitoActions.kt | 125 ++++++++++++++++-- .../cognito/helpers/SignInChallengeHelper.kt | 71 ++++++++-- .../codegen/actions/SetupTOTPActions.kt | 13 +- .../codegen/data/SignInTOTPSetupData.kt | 2 +- .../codegen/events/SetupTOTPEvent.kt | 14 +- .../codegen/states/SetupTOTPState.kt | 26 ++-- .../codegen/states/SignInState.kt | 9 +- 8 files changed, 257 insertions(+), 44 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 4c632b9417..d7d9996c66 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -22,6 +22,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.confirmForgotPassword import aws.sdk.kotlin.services.cognitoidentityprovider.confirmSignUp import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceRememberedStatusType import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeRequest @@ -81,8 +82,6 @@ import com.amplifyframework.auth.cognito.result.RevokeTokenError import com.amplifyframework.auth.cognito.usecases.ResetPasswordUseCase import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.auth.exceptions.InvalidStateException -import com.amplifyframework.auth.exceptions.NotAuthorizedException -import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.auth.exceptions.SessionExpiredException import com.amplifyframework.auth.exceptions.SignedOutException import com.amplifyframework.auth.exceptions.UnknownException @@ -131,6 +130,7 @@ import com.amplifyframework.statemachine.codegen.events.AuthenticationEvent import com.amplifyframework.statemachine.codegen.events.AuthorizationEvent import com.amplifyframework.statemachine.codegen.events.DeleteUserEvent import com.amplifyframework.statemachine.codegen.events.HostedUIEvent +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent import com.amplifyframework.statemachine.codegen.events.SignInChallengeEvent import com.amplifyframework.statemachine.codegen.events.SignOutEvent import com.amplifyframework.statemachine.codegen.states.AuthState @@ -139,6 +139,7 @@ import com.amplifyframework.statemachine.codegen.states.AuthorizationState import com.amplifyframework.statemachine.codegen.states.DeleteUserState import com.amplifyframework.statemachine.codegen.states.HostedUISignInState import com.amplifyframework.statemachine.codegen.states.SRPSignInState +import com.amplifyframework.statemachine.codegen.states.SetupTOTPState import com.amplifyframework.statemachine.codegen.states.SignInChallengeState import com.amplifyframework.statemachine.codegen.states.SignInState import com.amplifyframework.statemachine.codegen.states.SignOutState @@ -560,6 +561,21 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.cancel(token) SignInChallengeHelper.getNextStep(challengeState.challenge, onSuccess, onError) } + + totpSetupState is SetupTOTPState.WaitingForAnswer -> { + authStateMachine.cancel(token) + SignInChallengeHelper.getNextStep( + AuthChallenge( + ChallengeNameType.MfaSetup.value, + null, + null, + null, + ), + onSuccess, + onError, + totpSetupState.signInTOTPSetupData, + ) + } } } authNState is AuthenticationState.SignedIn && @@ -622,6 +638,15 @@ internal class RealAWSCognitoAuthPlugin( onError.accept(InvalidStateException()) } } + } + if (signInState is SignInState.ResolvingTOTPSetup ) { + when (signInState.setupTOTPState) { + is SetupTOTPState.WaitingForAnswer -> { + _confirmSignIn(challengeResponse, options, onSuccess, onError) + } + + else -> onError.accept(InvalidStateException()) + } } else { onError.accept(InvalidStateException()) } @@ -694,6 +719,18 @@ internal class RealAWSCognitoAuthPlugin( ) (signInState.challengeState as SignInChallengeState.Error).hasNewResponse = false } + signInState is SignInState.ResolvingTOTPSetup -> { + val setupData = (signInState.setupTOTPState as SetupTOTPState.WaitingForAnswer).signInTOTPSetupData + val event = SetupTOTPEvent( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + challengeResponse, + setupData.username, + setupData.session, + awsCognitoConfirmSignInOptions?.friendlyDeviceName, + ), + ) + authStateMachine.send(event) + } } }, { val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index 0c96409a33..aa6baf1ee9 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -14,21 +14,128 @@ */ package com.amplifyframework.auth.cognito.actions +import aws.sdk.kotlin.services.cognitoidentityprovider.associateSoftwareToken +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType +import aws.sdk.kotlin.services.cognitoidentityprovider.respondToAuthChallenge +import aws.sdk.kotlin.services.cognitoidentityprovider.verifySoftwareToken +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper import com.amplifyframework.statemachine.Action import com.amplifyframework.statemachine.codegen.actions.SetupTOTPActions +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent -internal object SetupTOTPCognitoActions: SetupTOTPActions { - override fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action { - TODO("Not yet implemented") +internal object SetupTOTPCognitoActions : SetupTOTPActions { + override fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action = Action( + "InitiateTOTPSetup", + ) { id, dispatcher -> + logger.verbose("$id Starting execution") + val evt = try { + val response = cognitoAuthService.cognitoIdentityProviderClient?.associateSoftwareToken { + session = eventType.totpSetupDetails.session + } + response?.secretCode?.let { secret -> + logger.verbose("New Session is ${response.session}") + SetupTOTPEvent( + SetupTOTPEvent.EventType.WaitForAnswer( + SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username), + ), + ) + } ?: SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")), + ) + } catch (e: Exception) { + SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")), + ) + } + logger.verbose("$id Sending event ${evt.type}") + dispatcher.send(evt) } - override fun verifyChallengeAnswer(eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer): Action { - TODO("Not yet implemented") - } + override fun verifyChallengeAnswer( + eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer, + ): Action = + Action("verifyChallengeAnswer") { id, dispatcher -> + logger.verbose("$id Starting execution") + val evt = try { + val response = cognitoAuthService.cognitoIdentityProviderClient?.verifySoftwareToken { + userCode = eventType.answer + this.session = eventType.session + this.friendlyDeviceName = eventType.friendlyDeviceName + } - override fun respondToAuthChallenge(eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge): Action { - TODO("Not yet implemented") - } + response?.let { + when (it.status) { + is VerifySoftwareTokenResponseType.Success -> { + logger.verbose("New Session is ${response.session}") + SetupTOTPEvent( + SetupTOTPEvent.EventType.RespondToAuthChallenge( + eventType.username, + it.session, + ), + ) + } + + else -> { + SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError( + Exception("Software token verification failed"), + ), + ) + } + } + } ?: SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")), + ) + } catch (exception: Exception) { + SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(exception), + ) + } + logger.verbose("$id Sending event ${evt.type}") + dispatcher.send(evt) + } + + override fun respondToAuthChallenge( + eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge, + ): Action = + Action("RespondToAuthChallenge") { id, dispatcher -> + logger.verbose("$id Starting execution") + val evt = try { + val challengeResponses = mutableMapOf() + + challengeResponses["USERNAME"] = eventType.username + val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { + this.session = eventType.session + this.challengeResponses = challengeResponses + challengeName = ChallengeNameType.MfaSetup + clientId = configuration.userPool?.appClient + } + + response?.let { + SignInChallengeHelper.evaluateNextStep( + username = eventType.username, + challengeNameType = response.challengeName, + session = response.session, + challengeParameters = response.challengeParameters, + authenticationResult = response.authenticationResult, + ) + } ?: SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")), + ) + } catch (exception: Exception) { + SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(exception), + ) + } + dispatcher.send(evt) + } + override fun resetToWaitingForAnswer(eventType: SetupTOTPEvent.EventType.ThrowAuthError): Action = + Action("ResetToWaitingForAnswer") { id, dispatcher -> + logger.verbose("$id Starting execution") + // TODO: Reset to waiting for answer + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index c344ec4c65..91e9d77014 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -18,8 +18,10 @@ package com.amplifyframework.auth.cognito.helpers import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResultType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.smithy.kotlin.runtime.time.Instant +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.exceptions.UnknownException import com.amplifyframework.auth.result.AuthSignInResult import com.amplifyframework.auth.result.step.AuthNextSignInStep @@ -30,6 +32,7 @@ import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens import com.amplifyframework.statemachine.codegen.data.DeviceMetadata import com.amplifyframework.statemachine.codegen.data.SignInMethod +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import com.amplifyframework.statemachine.codegen.data.SignedInData import com.amplifyframework.statemachine.codegen.events.AuthenticationEvent import com.amplifyframework.statemachine.codegen.events.SignInEvent @@ -43,7 +46,7 @@ internal object SignInChallengeHelper { session: String?, challengeParameters: Map?, authenticationResult: AuthenticationResultType?, - signInMethod: SignInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH) + signInMethod: SignInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH), ): StateMachineEvent { return when { authenticationResult != null -> { @@ -56,33 +59,38 @@ internal object SignInChallengeHelper { username, Date(), signInMethod, - tokens + tokens, ) it.newDeviceMetadata?.let { metadata -> SignInEvent( SignInEvent.EventType.ConfirmDevice( DeviceMetadata.Metadata( metadata.deviceKey ?: "", - metadata.deviceGroupKey ?: "" + metadata.deviceGroupKey ?: "", ), - signedInData - ) + signedInData, + ), ) } ?: AuthenticationEvent( AuthenticationEvent.EventType.SignInCompleted( signedInData, - DeviceMetadata.Empty - ) + DeviceMetadata.Empty, + ), ) } } challengeNameType is ChallengeNameType.SmsMfa || challengeNameType is ChallengeNameType.CustomChallenge || - challengeNameType is ChallengeNameType.NewPasswordRequired -> { + challengeNameType is ChallengeNameType.NewPasswordRequired || + challengeNameType is ChallengeNameType.SoftwareTokenMfa -> { val challenge = AuthChallenge(challengeNameType.value, username, session, challengeParameters) SignInEvent(SignInEvent.EventType.ReceivedChallenge(challenge)) } + challengeNameType is ChallengeNameType.MfaSetup -> { + val setupTOTPData = SignInTOTPSetupData("", session, username) + SignInEvent(SignInEvent.EventType.InitiateTOTPSetup(setupTOTPData)) + } challengeNameType is ChallengeNameType.DeviceSrpAuth -> { SignInEvent(SignInEvent.EventType.InitiateSignInWithDeviceSRP(username, mapOf())) } @@ -93,7 +101,9 @@ internal object SignInChallengeHelper { fun getNextStep( challenge: AuthChallenge, onSuccess: Consumer, - onError: Consumer + onError: Consumer, + signInTOTPSetupData: SignInTOTPSetupData? = null, + allowedMFAType: Set? = null, ) { val challengeParams = challenge.parameters?.toMutableMap() ?: mapOf() @@ -102,29 +112,62 @@ internal object SignInChallengeHelper { val deliveryDetails = AuthCodeDeliveryDetails( challengeParams.getValue("CODE_DELIVERY_DESTINATION"), AuthCodeDeliveryDetails.DeliveryMedium.fromString( - challengeParams.getValue("CODE_DELIVERY_DELIVERY_MEDIUM") - ) + challengeParams.getValue("CODE_DELIVERY_DELIVERY_MEDIUM"), + ), ) val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, mapOf(), deliveryDetails, null, null) + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, + mapOf(), + deliveryDetails, + null, + null, + ), ) onSuccess.accept(authSignInResult) } is ChallengeNameType.NewPasswordRequired -> { val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD, challengeParams, null, null, null) + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD, + challengeParams, + null, + null, + null, + ), ) onSuccess.accept(authSignInResult) } is ChallengeNameType.CustomChallenge -> { val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE, challengeParams, null, null, null) + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE, + challengeParams, + null, + null, + null, + ), ) onSuccess.accept(authSignInResult) } + is ChallengeNameType.MfaSetup -> { + signInTOTPSetupData?.let { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, + challengeParams, + null, + TOTPSetupDetails(it.secretCode, it.username), + allowedMFAType, + ), + ) + onSuccess.accept(authSignInResult) + } ?: onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) + } else -> onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt index c7b7703917..6aba02ed40 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt @@ -19,6 +19,15 @@ import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent internal interface SetupTOTPActions { fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action - fun verifyChallengeAnswer(eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer): Action - fun respondToAuthChallenge(eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge): Action + fun verifyChallengeAnswer( + eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer, + ): Action + + fun respondToAuthChallenge( + eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge, + ): Action + + fun resetToWaitingForAnswer( + eventType: SetupTOTPEvent.EventType.ThrowAuthError, + ): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt index 7d104003f5..789678ac54 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt @@ -16,6 +16,6 @@ package com.amplifyframework.statemachine.codegen.data internal data class SignInTOTPSetupData( val secretCode: String, - val session: String, + val session: String?, val username: String ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt index effd03d996..b8c3986bbd 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt @@ -14,7 +14,6 @@ */ package com.amplifyframework.statemachine.codegen.events -import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import java.util.Date @@ -24,10 +23,17 @@ internal class SetupTOTPEvent(val eventType: EventType, override val time: Date? sealed class EventType { data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData) : EventType() - data class WaitForAnswer(val totpSetupDetails: TOTPSetupDetails) : EventType() + data class WaitForAnswer(val totpSetupDetails: SignInTOTPSetupData) : EventType() data class ThrowAuthError(val exception: Exception) : EventType() - data class VerifyChallengeAnswer(val answer: String) : EventType() - data class RespondToAuthChallenge(val id: String = "") : EventType() + data class VerifyChallengeAnswer( + val answer: String, + val username: String, + val session: String?, + val friendlyDeviceName: String?, + ) : + EventType() + + data class RespondToAuthChallenge(val username: String, val session: String?) : EventType() data class Verified(val id: String = "") : EventType() } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index 9924347e02..2b955dce20 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -14,7 +14,6 @@ */ package com.amplifyframework.statemachine.codegen.states -import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.statemachine.State import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.StateMachineResolver @@ -26,14 +25,14 @@ import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent internal sealed class SetupTOTPState : State { data class NotStarted(val id: String = "") : SetupTOTPState() data class SetupTOTP(val signInTOTPSetupData: SignInTOTPSetupData) : SetupTOTPState() - data class WaitingForAnswer(val setupTOTPSetupDetails: TOTPSetupDetails) : SetupTOTPState() - data class Verifying(val code: String) : SetupTOTPState() - data class RespondingToAuthChallenge(val id: String = "") : SetupTOTPState() + data class WaitingForAnswer(val signInTOTPSetupData: SignInTOTPSetupData) : SetupTOTPState() + data class Verifying(val code: String, val username: String, val session: String?) : SetupTOTPState() + data class RespondingToAuthChallenge(val username: String, val session: String?) : SetupTOTPState() data class Success(val id: String = "") : SetupTOTPState() data class Error(val exception: Exception) : SetupTOTPState() class Resolver(private val setupTOTPActions: SetupTOTPActions) : StateMachineResolver { - override val defaultState = NotStarted() + override val defaultState = NotStarted("default") override fun resolve(oldState: SetupTOTPState, event: StateMachineEvent): StateResolution { val defaultResolution = StateResolution(oldState) @@ -69,7 +68,11 @@ internal sealed class SetupTOTPState : State { is WaitingForAnswer -> when (challengeEvent) { is SetupTOTPEvent.EventType.VerifyChallengeAnswer -> { StateResolution( - Verifying(challengeEvent.answer), + Verifying( + challengeEvent.answer, + oldState.signInTOTPSetupData.username, + oldState.signInTOTPSetupData.session, + ), listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), ) } @@ -84,8 +87,12 @@ internal sealed class SetupTOTPState : State { is Verifying -> when (challengeEvent) { is SetupTOTPEvent.EventType.RespondToAuthChallenge -> { StateResolution( - RespondingToAuthChallenge(), - listOf(setupTOTPActions.respondToAuthChallenge(challengeEvent)), + RespondingToAuthChallenge(oldState.username, oldState.session), + listOf( + setupTOTPActions.respondToAuthChallenge( + challengeEvent, + ), + ), ) } @@ -113,7 +120,8 @@ internal sealed class SetupTOTPState : State { is Error -> when (challengeEvent) { is SetupTOTPEvent.EventType.VerifyChallengeAnswer -> { StateResolution( - Verifying(challengeEvent.answer), + // TODO: Fix this + Verifying(challengeEvent.answer, "", null), listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), ) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index e5a4ddfb40..a1ec19d04b 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -15,6 +15,7 @@ package com.amplifyframework.statemachine.codegen.states +import android.util.Log import com.amplifyframework.statemachine.State import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.StateMachineResolver @@ -98,6 +99,7 @@ internal sealed class SignInState : State { } oldState.setupTOTPState?.let { setupTOTPResolver.resolve(it, event) }?.let { + Log.d("SignInState", "new state ${it.newState}") builder.setupTOTPState = it.newState actions += it.actions } @@ -110,6 +112,7 @@ internal sealed class SignInState : State { ): StateResolution { val signInEvent = asSignInEvent(event) val defaultResolution = StateResolution(oldState) + Log.d("SignInState", "resolving $oldState, for evt: $signInEvent") return when (oldState) { is NotStarted -> when (signInEvent) { is SignInEvent.EventType.InitiateSignInWithSRP -> StateResolution( @@ -159,7 +162,7 @@ internal sealed class SignInState : State { } is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( - ResolvingTOTPSetup(SetupTOTPState.NotStarted()), + ResolvingTOTPSetup(oldState.setupTOTPState), listOf(signInActions.initiateTOTOSetupAction(signInEvent)), ) @@ -179,7 +182,7 @@ internal sealed class SignInState : State { } is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( - ResolvingTOTPSetup(SetupTOTPState.NotStarted()), + ResolvingTOTPSetup(oldState.setupTOTPState), listOf(signInActions.initiateTOTOSetupAction(signInEvent)), ) @@ -264,7 +267,7 @@ internal sealed class SignInState : State { is SigningInWithHostedUI -> SigningInWithHostedUI(hostedUISignInState) is SigningInWithSRPCustom -> SigningInWithSRPCustom(srpSignInState) is ResolvingDeviceSRP -> ResolvingDeviceSRP(deviceSRPSignInState) - is SetupTOTPState -> ResolvingTOTPSetup(setupTOTPState) + is ResolvingTOTPSetup -> ResolvingTOTPSetup(setupTOTPState) else -> signInState } } From 8c4071abf5e654942ba2a53d5f4c9e78888fda9a Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 14 Jul 2023 16:03:47 -0500 Subject: [PATCH 04/28] Add authenticated TOTP APIs --- .../auth/cognito/AWSCognitoAuthPlugin.kt | 79 +++++++ .../cognito/CognitoAuthExceptionConverter.kt | 3 + .../auth/cognito/KotlinAuthFacadeInternal.kt | 55 +++++ .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 196 ++++++++++++++++++ .../auth/cognito/UserMFAPreference.kt | 55 +++++ .../auth/cognito/actions/SRPCognitoActions.kt | 1 + .../actions/SignInChallengeCognitoActions.kt | 5 +- .../EnableSoftwareTokenMfaException.kt | 28 +++ .../cognito/helpers/SignInChallengeHelper.kt | 41 +++- .../AWSCognitoAuthVerifyTOTPSetupOptions.java | 37 ++++ .../statemachine/StateMachine.kt | 4 +- .../codegen/states/SignInChallengeState.kt | 5 + .../codegen/states/SignInState.kt | 3 - .../amplifyframework/auth/AuthCategory.java | 21 +- .../auth/AuthCategoryBehavior.java | 19 ++ .../java/com/amplifyframework/auth/MFAType.kt | 15 +- .../options/AuthVerifyTOTPSetupOptions.java | 47 +++++ 17 files changed, 602 insertions(+), 12 deletions(-) create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt create mode 100644 aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java create mode 100644 core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt index c6b10b6cd6..cbda031ea0 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.Intent import androidx.annotation.VisibleForTesting import com.amplifyframework.AmplifyException +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.auth.AWSCognitoAuthMetadataType import com.amplifyframework.auth.AuthCodeDeliveryDetails @@ -48,6 +49,7 @@ import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult @@ -768,6 +770,49 @@ class AWSCognitoAuthPlugin : AuthPlugin() { ) } + override fun setUpTOTP(onSuccess: Consumer, onError: Consumer) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + val result = queueFacade.setupMFA() + onSuccess.accept(result) + } catch (e: Exception) { + onError.accept(e.toAuthException()) + } + } + ) + } + + override fun verifyTOTPSetup(code: String, onSuccess: Action, onError: Consumer) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + queueFacade.verifyTOTPSetup(code) + onSuccess.call() + } catch (e: Exception) { + onError.accept(e.toAuthException()) + } + } + ) + } + + override fun verifyTOTPSetup( + code: String, + options: AuthVerifyTOTPSetupOptions, + onSuccess: Action, + onError: Consumer + ) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + queueFacade.verifyTOTPSetup(code, options) + onSuccess.call() + } catch (e: Exception) { + onError.accept(e.toAuthException()) + } + } + ) + } override fun getEscapeHatch() = realPlugin.escapeHatch() override fun getPluginKey() = AWS_COGNITO_AUTH_PLUGIN_KEY @@ -852,4 +897,38 @@ class AWSCognitoAuthPlugin : AuthPlugin() { } ) } + + fun fetchMFAPreference( + onSuccess: Consumer, + onError: Consumer + ) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + val result = queueFacade.fetchMFAPreference() + onSuccess.accept(result) + } catch (e: Exception) { + onError.accept(e.toAuthException()) + } + } + ) + } + + fun updateMFAPreference( + sms: MFAPreference?, + totp: MFAPreference?, + onSuccess: Action, + onError: Consumer + ) { + queueChannel.trySend( + pluginScope.launch(start = CoroutineStart.LAZY) { + try { + queueFacade.updateMFAPreference(sms, totp) + onSuccess.call() + } catch (e: Exception) { + onError.accept(e.toAuthException()) + } + } + ) + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt index e3fa1914f4..17a5f5c29f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/CognitoAuthExceptionConverter.kt @@ -18,6 +18,7 @@ package com.amplifyframework.auth.cognito import aws.sdk.kotlin.services.cognitoidentityprovider.model.AliasExistsException import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryFailureException import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.EnableSoftwareTokenMfaException import aws.sdk.kotlin.services.cognitoidentityprovider.model.ExpiredCodeException import aws.sdk.kotlin.services.cognitoidentityprovider.model.InvalidParameterException import aws.sdk.kotlin.services.cognitoidentityprovider.model.InvalidPasswordException @@ -88,6 +89,8 @@ internal class CognitoAuthExceptionConverter { com.amplifyframework.auth.cognito.exceptions.service.TooManyRequestsException(error) is PasswordResetRequiredException -> com.amplifyframework.auth.cognito.exceptions.service.PasswordResetRequiredException(error) + is EnableSoftwareTokenMfaException -> + com.amplifyframework.auth.cognito.exceptions.service.EnableSoftwareTokenMfaException(error) else -> UnknownException(fallbackMessage, error) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt index ca4b4dc456..f8a4400474 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt @@ -17,6 +17,7 @@ package com.amplifyframework.auth.cognito import android.app.Activity import android.content.Intent +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthDevice import com.amplifyframework.auth.AuthProvider @@ -38,6 +39,7 @@ import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult @@ -520,4 +522,57 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth ) } } + + suspend fun setupMFA(): TOTPSetupDetails { + return suspendCoroutine { continuation -> + delegate.setUpTOTP( + { continuation.resume(it) }, + { continuation.resumeWithException(it) } + ) + } + } + + suspend fun verifyTOTPSetup(code: String) { + return suspendCoroutine { continuation -> + delegate.verifyTOTPSetup( + code, + { continuation.resume(Unit) }, + { continuation.resumeWithException(it) } + ) + } + } + + suspend fun verifyTOTPSetup(code: String, options: AuthVerifyTOTPSetupOptions) { + return suspendCoroutine { continuation -> + delegate.verifyTOTPSetup( + code, + options, + { continuation.resume(Unit) }, + { continuation.resumeWithException(it) } + ) + } + } + + suspend fun fetchMFAPreference(): UserMFAPreference { + return suspendCoroutine { continuation -> + delegate.fetchMFAPreference( + { continuation.resume(it) }, + { continuation.resumeWithException(it) } + ) + } + } + + suspend fun updateMFAPreference( + sms: MFAPreference?, + totp: MFAPreference? + ) { + return suspendCoroutine { continuation -> + delegate.updateMFAPreference( + sms, + totp, + { continuation.resume(Unit) }, + { continuation.resumeWithException(it) } + ) + } + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index d7d9996c66..44f5c2778f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -18,8 +18,10 @@ package com.amplifyframework.auth.cognito import android.app.Activity import android.content.Intent import androidx.annotation.WorkerThread +import aws.sdk.kotlin.services.cognitoidentityprovider.associateSoftwareToken import aws.sdk.kotlin.services.cognitoidentityprovider.confirmForgotPassword import aws.sdk.kotlin.services.cognitoidentityprovider.confirmSignUp +import aws.sdk.kotlin.services.cognitoidentityprovider.getUser import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType @@ -28,13 +30,18 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceRememberedSta import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ListDevicesRequest +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateDeviceStatusRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.resendConfirmationCode +import aws.sdk.kotlin.services.cognitoidentityprovider.setUserMfaPreference import aws.sdk.kotlin.services.cognitoidentityprovider.signUp +import aws.sdk.kotlin.services.cognitoidentityprovider.verifySoftwareToken import com.amplifyframework.AmplifyException +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.auth.AWSCognitoAuthMetadataType import com.amplifyframework.auth.AWSCredentials @@ -49,6 +56,7 @@ import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.AuthUser import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidOauthConfigurationException import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException import com.amplifyframework.auth.cognito.exceptions.invalidstate.SignedInException @@ -97,6 +105,7 @@ import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult @@ -2023,6 +2032,193 @@ internal class RealAWSCognitoAuthPlugin( } } + override fun setUpTOTP(onSuccess: Consumer, onError: Consumer) { + authStateMachine.getCurrentState { authState -> + when (authState.authNState) { + is AuthenticationState.SignedIn -> { + GlobalScope.launch { + try { + val accessToken = getSession().userPoolTokensResult.value?.accessToken + accessToken?.let { token -> + SessionHelper.getUsername(token)?.let { username -> + authEnvironment.cognitoAuthService + .cognitoIdentityProviderClient?.associateSoftwareToken { + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username + ) + ) + } + } + } + } ?: onError.accept(SignedOutException()) + } catch (error: Exception) { + onError.accept( + CognitoAuthExceptionConverter.lookup( + error, + "Cannot find a multi-factor authentication (MFA) method.", + ) + ) + } + } + } + + else -> onError.accept(InvalidStateException()) + } + } + } + + override fun verifyTOTPSetup( + code: String, + onSuccess: Action, + onError: Consumer + ) { + verifyTotp(code, null, onSuccess, onError) + } + + override fun verifyTOTPSetup( + code: String, + options: AuthVerifyTOTPSetupOptions, + onSuccess: Action, + onError: Consumer + ) { + verifyTotp(code, options.friendlyDeviceName, onSuccess, onError) + } + + fun fetchMFAPreference( + onSuccess: Consumer, + onError: Consumer + ) { + authStateMachine.getCurrentState { authState -> + when (authState.authNState) { + is AuthenticationState.SignedIn -> { + GlobalScope.launch { + try { + val accessToken = getSession().userPoolTokensResult.value?.accessToken + accessToken?.let { token -> + authEnvironment.cognitoAuthService + .cognitoIdentityProviderClient?.getUser { + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) + } + } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) + } + } ?: onError.accept(SignedOutException()) + } catch (error: Exception) { + onError.accept( + CognitoAuthExceptionConverter.lookup( + error, + "Cannot update the MFA preferences", + ) + ) + } + } + } + + else -> onError.accept(InvalidStateException()) + } + } + } + + fun updateMFAPreference( + sms: MFAPreference?, + totp: MFAPreference?, + onSuccess: Action, + onError: Consumer + ) { + authStateMachine.getCurrentState { authState -> + when (authState.authNState) { + is AuthenticationState.SignedIn -> { + GlobalScope.launch { + try { + val accessToken = getSession().userPoolTokensResult.value?.accessToken + accessToken?.let { token -> + authEnvironment.cognitoAuthService.cognitoIdentityProviderClient?.setUserMfaPreference { + this.accessToken = token + this.smsMfaSettings = sms?.let { + SmsMfaSettingsType.invoke { + enabled = it.mfaEnabled + preferredMfa = it.mfaPreferred + } + } + this.softwareTokenMfaSettings = totp?.let { + SoftwareTokenMfaSettingsType.invoke { + enabled = it.mfaEnabled + preferredMfa = it.mfaPreferred + } + } + }?.also { + onSuccess.call() + } + } ?: onError.accept(SignedOutException()) + } catch (error: Exception) { + onError.accept( + CognitoAuthExceptionConverter.lookup( + error, + "Amazon Cognito cannot update the MFA preferences", + ) + ) + } + } + } + + else -> onError.accept(InvalidStateException()) + } + } + } + + private fun verifyTotp( + code: String, + friendlyDeviceName: String?, + onSuccess: Action, + onError: Consumer + ) { + authStateMachine.getCurrentState { authState -> + when (authState.authNState) { + is AuthenticationState.SignedIn -> { + GlobalScope.launch { + try { + val accessToken = getSession().userPoolTokensResult.value?.accessToken + accessToken?.let { token -> + authEnvironment.cognitoAuthService + .cognitoIdentityProviderClient?.verifySoftwareToken { + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + onSuccess.call() + } + } ?: onError.accept(SignedOutException()) + } catch (error: Exception) { + onError.accept( + CognitoAuthExceptionConverter.lookup( + error, + "Amazon Cognito cannot find a multi-factor authentication (MFA) method.", + ) + ) + } + } + } + + else -> onError.accept(InvalidStateException()) + } + } + } + private fun _clearFederationToIdentityPool(onSuccess: Action, onError: Consumer) { _signOut(sendHubEvent = false) { when (it) { diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt new file mode 100644 index 0000000000..3f8144c115 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.cognito + +import com.amplifyframework.auth.MFAType + +public data class UserMFAPreference( + val enabled: Set?, + val preferred: MFAType? +) + +public sealed class MFAPreference { + abstract val mfaEnabled: Boolean + abstract val mfaPreferred: Boolean + + object Disabled : MFAPreference() { + override val mfaEnabled: Boolean + get() = false + override val mfaPreferred: Boolean + get() = false + } + + object Enabled : MFAPreference() { + override val mfaEnabled: Boolean + get() = false + override val mfaPreferred: Boolean + get() = false + } + + object Preferred : MFAPreference() { + override val mfaEnabled: Boolean + get() = true + override val mfaPreferred: Boolean + get() = true + } + + object NotPreferred : MFAPreference() { + override val mfaEnabled: Boolean + get() = true + override val mfaPreferred: Boolean + get() = false + } +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt index 4e6e211e57..be21124656 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt @@ -206,6 +206,7 @@ internal object SRPCognitoActions : SRPActions { encodedContextData?.let { userContextData { encodedData = it } } } if (response != null) { + logger.verbose("SRP Session: ${response.session}") SignInChallengeHelper.evaluateNextStep( username, response.challengeName, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt index 71dc49d44f..769bbd0222 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt @@ -56,7 +56,7 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { val encodedContextData = username?.let { getUserContextData(it) } val pinpointEndpointId = getPinpointEndpointId() - + logger.verbose("SRP Session: ${challenge.session}") val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { clientId = configuration.userPool?.appClient challengeName = ChallengeNameType.fromValue(challenge.challengeName) @@ -90,7 +90,8 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { return when (ChallengeNameType.fromValue(challengeName)) { is ChallengeNameType.SmsMfa -> "SMS_MFA_CODE" is ChallengeNameType.NewPasswordRequired -> "NEW_PASSWORD" - is ChallengeNameType.CustomChallenge -> "ANSWER" + is ChallengeNameType.CustomChallenge, ChallengeNameType.SelectMfaType -> "ANSWER" + is ChallengeNameType.SoftwareTokenMfa -> "SOFTWARE_TOKEN_MFA_CODE" else -> null } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt new file mode 100644 index 0000000000..6239a37dee --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt @@ -0,0 +1,28 @@ +package com.amplifyframework.auth.cognito.exceptions.service + +import com.amplifyframework.auth.exceptions.ServiceException + +/* + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +/** + * Software Token MFA is not enabled for the user. + * @param cause The underlying cause of this exception + */ +open class EnableSoftwareTokenMfaException(cause: Throwable?) : + ServiceException( + "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool.", + "Enable the software token MFA for the user.", + cause + ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index 91e9d77014..370b6d75ec 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -82,7 +82,8 @@ internal object SignInChallengeHelper { challengeNameType is ChallengeNameType.SmsMfa || challengeNameType is ChallengeNameType.CustomChallenge || challengeNameType is ChallengeNameType.NewPasswordRequired || - challengeNameType is ChallengeNameType.SoftwareTokenMfa -> { + challengeNameType is ChallengeNameType.SoftwareTokenMfa || + challengeNameType is ChallengeNameType.SelectMfaType -> { val challenge = AuthChallenge(challengeNameType.value, username, session, challengeParameters) SignInEvent(SignInEvent.EventType.ReceivedChallenge(challenge)) @@ -153,6 +154,19 @@ internal object SignInChallengeHelper { ) onSuccess.accept(authSignInResult) } + is ChallengeNameType.SoftwareTokenMfa -> { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, + mapOf(), + null, + null, + null, + ), + ) + onSuccess.accept(authSignInResult) + } is ChallengeNameType.MfaSetup -> { signInTOTPSetupData?.let { val authSignInResult = AuthSignInResult( @@ -168,7 +182,32 @@ internal object SignInChallengeHelper { onSuccess.accept(authSignInResult) } ?: onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) } + is ChallengeNameType.SelectMfaType -> { + val authSignInResult = AuthSignInResult( + false, + AuthNextSignInStep( + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION, + mapOf(), + null, + null, + challengeParams["MFAS_CAN_CHOOSE"]?.let { getAllowedMFATypes(it) }, + ), + ) + onSuccess.accept(authSignInResult) + } else -> onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) } } + + private fun getAllowedMFATypes(allowedMFAType: String): Set { + val result = mutableSetOf() + allowedMFAType.replace(Regex("\\[|\\]|\""), "").split(",").forEach { + when (it) { + "SMS_MFA" -> result.add(MFAType.SMS) + "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) + else -> TODO("throw exception") + } + } + return result + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java new file mode 100644 index 0000000000..1d09c651b4 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amplifyframework.auth.cognito.options; + +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; + +public final class AWSCognitoAuthVerifyTOTPSetupOptions extends AuthVerifyTOTPSetupOptions { + + private AWSCognitoAuthVerifyTOTPSetupOptions(String friendlyDeviceName) { + super(friendlyDeviceName); + } + + public static final class CognitoBuilder extends Builder { + + @Override + public CognitoBuilder getThis() { + return this; + } + + public AWSCognitoAuthVerifyTOTPSetupOptions build() { + return new AWSCognitoAuthVerifyTOTPSetupOptions(super.getFriendlyDeviceName()); + } + } +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt index 42b4a9a169..98994b9e81 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt @@ -137,9 +137,9 @@ internal open class StateMachine { val defaultResolution = StateResolution(oldState) val challengeEvent = asSignInChallengeEvent(event) + val signInEvent = (event as? SignInEvent)?.eventType return when (oldState) { is NotStarted -> when (challengeEvent) { is SignInChallengeEvent.EventType.WaitForAnswer -> { @@ -65,6 +67,9 @@ internal sealed class SignInChallengeState : State { else -> defaultResolution } is Verifying -> when (challengeEvent) { + is SignInChallengeEvent.EventType.WaitForAnswer -> { + StateResolution(WaitingForAnswer(challengeEvent.challenge)) + } is SignInChallengeEvent.EventType.Verified -> StateResolution(Verified()) is SignInChallengeEvent.EventType.ThrowError -> { StateResolution(Error(challengeEvent.exception, challengeEvent.challenge, true), listOf()) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index a1ec19d04b..6f06253923 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -15,7 +15,6 @@ package com.amplifyframework.statemachine.codegen.states -import android.util.Log import com.amplifyframework.statemachine.State import com.amplifyframework.statemachine.StateMachineEvent import com.amplifyframework.statemachine.StateMachineResolver @@ -99,7 +98,6 @@ internal sealed class SignInState : State { } oldState.setupTOTPState?.let { setupTOTPResolver.resolve(it, event) }?.let { - Log.d("SignInState", "new state ${it.newState}") builder.setupTOTPState = it.newState actions += it.actions } @@ -112,7 +110,6 @@ internal sealed class SignInState : State { ): StateResolution { val signInEvent = asSignInEvent(event) val defaultResolution = StateResolution(oldState) - Log.d("SignInState", "resolving $oldState, for evt: $signInEvent") return when (oldState) { is NotStarted -> when (signInEvent) { is SignInEvent.EventType.InitiateSignInWithSRP -> StateResolution( diff --git a/core/src/main/java/com/amplifyframework/auth/AuthCategory.java b/core/src/main/java/com/amplifyframework/auth/AuthCategory.java index 51d144e202..519e7c0bdc 100644 --- a/core/src/main/java/com/amplifyframework/auth/AuthCategory.java +++ b/core/src/main/java/com/amplifyframework/auth/AuthCategory.java @@ -20,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.amplifyframework.TOTPSetupDetails; import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions; import com.amplifyframework.auth.options.AuthConfirmSignInOptions; import com.amplifyframework.auth.options.AuthConfirmSignUpOptions; @@ -32,6 +33,7 @@ import com.amplifyframework.auth.options.AuthSignUpOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions; +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; import com.amplifyframework.auth.options.AuthWebUISignInOptions; import com.amplifyframework.auth.result.AuthResetPasswordResult; import com.amplifyframework.auth.result.AuthSignInResult; @@ -387,12 +389,29 @@ public void signOut(@NonNull Consumer onComplete) { ) { getSelectedPlugin().signOut(options, onComplete); } - + @Override public void deleteUser( @NonNull Action onSuccess, @NonNull Consumer onError) { getSelectedPlugin().deleteUser(onSuccess, onError); } + + @Override + public void setUpTOTP(@NonNull Consumer onSuccess, @NonNull Consumer onError) { + getSelectedPlugin().setUpTOTP(onSuccess, onError); + } + + @Override + public void verifyTOTPSetup(@NonNull String code, @NonNull Action onSuccess, + @NonNull Consumer onError) { + getSelectedPlugin().verifyTOTPSetup(code, onSuccess, onError); + } + + @Override + public void verifyTOTPSetup(@NonNull String code, @NonNull AuthVerifyTOTPSetupOptions options, + @NonNull Action onSuccess, @NonNull Consumer onError) { + getSelectedPlugin().verifyTOTPSetup(code, options, onSuccess, onError); + } } diff --git a/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java b/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java index 82d7254ca7..5b779d5167 100644 --- a/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java +++ b/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java @@ -20,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.amplifyframework.TOTPSetupDetails; import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions; import com.amplifyframework.auth.options.AuthConfirmSignInOptions; import com.amplifyframework.auth.options.AuthConfirmSignUpOptions; @@ -32,6 +33,7 @@ import com.amplifyframework.auth.options.AuthSignUpOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions; +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; import com.amplifyframework.auth.options.AuthWebUISignInOptions; import com.amplifyframework.auth.result.AuthResetPasswordResult; import com.amplifyframework.auth.result.AuthSignInResult; @@ -512,4 +514,21 @@ void signOut( void deleteUser( @NonNull Action onSuccess, @NonNull Consumer onError); + + void setUpTOTP( + @NonNull Consumer onSuccess, + @NonNull Consumer onError); + + void verifyTOTPSetup( + @NonNull String code, + @NonNull Action onSuccess, + @NonNull Consumer onError + ); + + void verifyTOTPSetup( + @NonNull String code, + @NonNull AuthVerifyTOTPSetupOptions options, + @NonNull Action onSuccess, + @NonNull Consumer onError + ); } diff --git a/core/src/main/java/com/amplifyframework/auth/MFAType.kt b/core/src/main/java/com/amplifyframework/auth/MFAType.kt index 34f6ac43aa..b32dc80351 100644 --- a/core/src/main/java/com/amplifyframework/auth/MFAType.kt +++ b/core/src/main/java/com/amplifyframework/auth/MFAType.kt @@ -14,7 +14,16 @@ */ package com.amplifyframework.auth -enum class MFAType { - SMS, - TOTP, +enum class MFAType(val value: String) { + SMS("SMS_MFA"), + TOTP("SOFTWARE_TOKEN_MFA"); + companion object { + fun toMFAType(value: String): MFAType { + return when (value) { + MFAType.SMS.value -> MFAType.SMS + MFAType.TOTP.value -> MFAType.TOTP + else -> throw IllegalArgumentException("Unsupported MFA type") + } + } + } } diff --git a/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java new file mode 100644 index 0000000000..0e42cbd530 --- /dev/null +++ b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java @@ -0,0 +1,47 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.options; + +public class AuthVerifyTOTPSetupOptions { + private String friendlyDeviceName; + + protected AuthVerifyTOTPSetupOptions(String friendlyDeviceName) { + this.friendlyDeviceName = friendlyDeviceName; + } + + public String getFriendlyDeviceName() { + return friendlyDeviceName; + } + + public abstract static class Builder> { + private String friendlyDeviceName; + + protected String getFriendlyDeviceName() { + return friendlyDeviceName; + } + + public abstract T getThis(); + + public T friendlyDeviceName(String friendlyDeviceName) { + this.friendlyDeviceName = friendlyDeviceName; + return getThis(); + } + + public AuthVerifyTOTPSetupOptions build() { + return new AuthVerifyTOTPSetupOptions(friendlyDeviceName); + } + + } +} From efd021adec07ebbe87adcfdc79dd968af4fc141a Mon Sep 17 00:00:00 2001 From: sdhuka Date: Thu, 27 Jul 2023 08:38:25 -0500 Subject: [PATCH 05/28] add confirmsignIn changes --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 102 ++++++++++-------- .../codegen/states/SignInChallengeState.kt | 3 - 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 44f5c2778f..b21305ecc4 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -90,6 +90,8 @@ import com.amplifyframework.auth.cognito.result.RevokeTokenError import com.amplifyframework.auth.cognito.usecases.ResetPasswordUseCase import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.auth.exceptions.InvalidStateException +import com.amplifyframework.auth.exceptions.NotAuthorizedException +import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.auth.exceptions.SessionExpiredException import com.amplifyframework.auth.exceptions.SignedOutException import com.amplifyframework.auth.exceptions.UnknownException @@ -126,6 +128,7 @@ import com.amplifyframework.hub.HubEvent import com.amplifyframework.logging.Logger import com.amplifyframework.statemachine.StateChangeListenerToken import com.amplifyframework.statemachine.codegen.data.AmplifyCredential +import com.amplifyframework.statemachine.codegen.data.AuthChallenge import com.amplifyframework.statemachine.codegen.data.AuthConfiguration import com.amplifyframework.statemachine.codegen.data.DeviceMetadata import com.amplifyframework.statemachine.codegen.data.FederatedToken @@ -553,6 +556,7 @@ internal class RealAWSCognitoAuthPlugin( val signInState = authNState.signInState val srpSignInState = (signInState as? SignInState.SigningInWithSRP)?.srpSignInState val challengeState = (signInState as? SignInState.ResolvingChallenge)?.challengeState + val totpSetupState = (signInState as? SignInState.ResolvingTOTPSetup)?.setupTOTPState when { srpSignInState is SRPSignInState.Error -> { authStateMachine.cancel(token) @@ -578,11 +582,11 @@ internal class RealAWSCognitoAuthPlugin( ChallengeNameType.MfaSetup.value, null, null, - null, + null ), onSuccess, onError, - totpSetupState.signInTOTPSetupData, + totpSetupState.signInTOTPSetupData ) } } @@ -641,28 +645,26 @@ internal class RealAWSCognitoAuthPlugin( if (signInState is SignInState.ResolvingChallenge) { when (signInState.challengeState) { is SignInChallengeState.WaitingForAnswer, is SignInChallengeState.Error -> { - _confirmSignIn(challengeResponse, options, onSuccess, onError) + _confirmSignIn(signInState, challengeResponse, options, onSuccess, onError) } else -> { onError.accept(InvalidStateException()) } } } - if (signInState is SignInState.ResolvingTOTPSetup ) { + if (signInState is SignInState.ResolvingTOTPSetup) { when (signInState.setupTOTPState) { is SetupTOTPState.WaitingForAnswer -> { - _confirmSignIn(challengeResponse, options, onSuccess, onError) + _confirmSignIn(signInState, challengeResponse, options, onSuccess, onError) } - else -> onError.accept(InvalidStateException()) } - } else { - onError.accept(InvalidStateException()) } } } private fun _confirmSignIn( + signInState: SignInState, challengeResponse: String, options: AuthConfirmSignInOptions, onSuccess: Consumer, @@ -690,7 +692,8 @@ internal class RealAWSCognitoAuthPlugin( authStateMachine.cancel(token) onError.accept( CognitoAuthExceptionConverter.lookup( - signInState.exception, "Confirm Sign in failed." + signInState.exception, + "Confirm Sign in failed." ) ) } @@ -699,16 +702,23 @@ internal class RealAWSCognitoAuthPlugin( (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse -> { authStateMachine.cancel(token) val signInChallengeState = signInState.challengeState as SignInChallengeState.WaitingForAnswer - var signInStep = AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE - if (signInChallengeState.challenge.challengeName == "SMS_MFA") { - signInStep = AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE - } else if (signInChallengeState.challenge.challengeName == "NEW_PASSWORD_REQUIRED") { - signInStep = AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD + val signInStep = when (signInChallengeState.challenge.challengeName) { + "SMS_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE + "NEW_PASSWORD_REQUIRED" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD + "SOFTWARE_TOKEN_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE + "SELECT_MFA_TYPE" -> AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION + "MFA_SETUP" -> AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP + else -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE } - val authSignInResult = AuthSignInResult( false, - AuthNextSignInStep(signInStep, signInChallengeState.challenge.parameters ?: mapOf(), null) + AuthNextSignInStep( + signInStep, + signInChallengeState.challenge.parameters ?: mapOf(), + null, + null, + null + ) ) onSuccess.accept(authSignInResult) (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse = false @@ -728,29 +738,38 @@ internal class RealAWSCognitoAuthPlugin( ) (signInState.challengeState as SignInChallengeState.Error).hasNewResponse = false } - signInState is SignInState.ResolvingTOTPSetup -> { - val setupData = (signInState.setupTOTPState as SetupTOTPState.WaitingForAnswer).signInTOTPSetupData + } + }, + { + val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions + when (signInState) { + is SignInState.ResolvingChallenge -> { + val event = SignInChallengeEvent( + SignInChallengeEvent.EventType.VerifyChallengeAnswer( + challengeResponse, + awsCognitoConfirmSignInOptions?.metadata ?: mapOf() + ) + ) + authStateMachine.send(event) + } + + is SignInState.ResolvingTOTPSetup -> { + val setupData = + (signInState.setupTOTPState as SetupTOTPState.WaitingForAnswer).signInTOTPSetupData val event = SetupTOTPEvent( SetupTOTPEvent.EventType.VerifyChallengeAnswer( challengeResponse, setupData.username, setupData.session, - awsCognitoConfirmSignInOptions?.friendlyDeviceName, - ), + awsCognitoConfirmSignInOptions?.friendlyDeviceName + ) ) authStateMachine.send(event) } + + else -> onError.accept(InvalidStateException()) } - }, { - val awsCognitoConfirmSignInOptions = options as? AWSCognitoAuthConfirmSignInOptions - val event = SignInChallengeEvent( - SignInChallengeEvent.EventType.VerifyChallengeAnswer( - challengeResponse, - awsCognitoConfirmSignInOptions?.metadata ?: mapOf() - ) - ) - authStateMachine.send(event) - } + } ) } @@ -1047,7 +1066,9 @@ internal class RealAWSCognitoAuthPlugin( ) } _fetchAuthSession(onSuccess, onError) - } else onSuccess.accept(credential.getCognitoSession()) + } else { + onSuccess.accept(credential.getCognitoSession()) + } } is AuthorizationState.Error -> { val error = authZState.exception @@ -1264,7 +1285,8 @@ internal class RealAWSCognitoAuthPlugin( val pinpointEndpointId = authEnvironment.getPinpointEndpointId() ResetPasswordUseCase( - cognitoIdentityProviderClient, appClient, + cognitoIdentityProviderClient, + appClient, configuration.userPool?.appClientSecret ).execute( username, @@ -1406,7 +1428,6 @@ internal class RealAWSCognitoAuthPlugin( when (authState.authNState) { // Check if user signed in is AuthenticationState.SignedIn -> { - GlobalScope.launch { try { val accessToken = getSession().userPoolTokensResult.value?.accessToken @@ -1492,9 +1513,8 @@ internal class RealAWSCognitoAuthPlugin( private suspend fun updateUserAttributes( attributes: List, - userAttributesOptionsMetadata: Map?, + userAttributesOptionsMetadata: Map? ): MutableMap { - return suspendCoroutine { continuation -> authStateMachine.getCurrentState { authState -> @@ -1543,14 +1563,12 @@ internal class RealAWSCognitoAuthPlugin( response: UpdateUserAttributesResponse?, userAttributeList: List ): MutableMap { - val finalResult = HashMap() response?.codeDeliveryDetailsList?.let { val codeDeliveryDetailsList = it for (item in codeDeliveryDetailsList) { item.attributeName?.let { - val deliveryMedium = AuthCodeDeliveryDetails.DeliveryMedium.fromString(item.deliveryMedium?.value) val authCodeDeliveryDetails = AuthCodeDeliveryDetails( item.destination.toString(), @@ -1613,7 +1631,6 @@ internal class RealAWSCognitoAuthPlugin( getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it codeDeliveryDetails.attributeName?.let { - val deliveryMedium = AuthCodeDeliveryDetails.DeliveryMedium.fromString( codeDeliveryDetails.deliveryMedium?.value ) @@ -1923,7 +1940,6 @@ internal class RealAWSCognitoAuthPlugin( authZState is AuthorizationState.SessionEstablished || authZState is AuthorizationState.Error ) -> { - val existingCredential = when (authZState) { is AuthorizationState.SessionEstablished -> authZState.amplifyCredential is AuthorizationState.Error -> { @@ -2060,7 +2076,7 @@ internal class RealAWSCognitoAuthPlugin( onError.accept( CognitoAuthExceptionConverter.lookup( error, - "Cannot find a multi-factor authentication (MFA) method.", + "Cannot find a multi-factor authentication (MFA) method." ) ) } @@ -2122,7 +2138,7 @@ internal class RealAWSCognitoAuthPlugin( onError.accept( CognitoAuthExceptionConverter.lookup( error, - "Cannot update the MFA preferences", + "Cannot update the MFA preferences" ) ) } @@ -2169,7 +2185,7 @@ internal class RealAWSCognitoAuthPlugin( onError.accept( CognitoAuthExceptionConverter.lookup( error, - "Amazon Cognito cannot update the MFA preferences", + "Amazon Cognito cannot update the MFA preferences" ) ) } @@ -2207,7 +2223,7 @@ internal class RealAWSCognitoAuthPlugin( onError.accept( CognitoAuthExceptionConverter.lookup( error, - "Amazon Cognito cannot find a multi-factor authentication (MFA) method.", + "Amazon Cognito cannot find a multi-factor authentication (MFA) method." ) ) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt index 5b90e668e2..d26299c83a 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInChallengeState.kt @@ -67,9 +67,6 @@ internal sealed class SignInChallengeState : State { else -> defaultResolution } is Verifying -> when (challengeEvent) { - is SignInChallengeEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer(challengeEvent.challenge)) - } is SignInChallengeEvent.EventType.Verified -> StateResolution(Verified()) is SignInChallengeEvent.EventType.ThrowError -> { StateResolution(Error(challengeEvent.exception, challengeEvent.challenge, true), listOf()) From 447f2ed48b5bff6fad2fa4430b64cbfb7f7776d2 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Thu, 27 Jul 2023 09:22:52 -0500 Subject: [PATCH 06/28] fix checkstyle and lint --- .../auth/cognito/AuthStateMachine.kt | 26 +++--- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 84 ++++++++++--------- .../auth/cognito/actions/SRPCognitoActions.kt | 10 ++- .../actions/SetupTOTPCognitoActions.kt | 32 +++---- .../cognito/actions/SignInCognitoActions.kt | 14 ++-- .../cognito/helpers/SignInChallengeHelper.kt | 44 +++++----- .../AWSCognitoAuthVerifyTOTPSetupOptions.java | 30 +++++-- .../statemachine/StateMachine.kt | 4 +- .../codegen/actions/SetupTOTPActions.kt | 6 +- .../codegen/events/SetupTOTPEvent.kt | 2 +- .../codegen/events/SignInEvent.kt | 12 +-- .../codegen/states/SetupTOTPState.kt | 26 +++--- .../codegen/states/SignInState.kt | 26 +++--- .../com/amplifyframework/TOTPSetupDetails.kt | 4 +- .../auth/AuthCategoryBehavior.java | 18 ++++ .../options/AuthVerifyTOTPSetupOptions.java | 36 ++++---- 16 files changed, 205 insertions(+), 169 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt index 356754bdc7..a781329481 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AuthStateMachine.kt @@ -51,7 +51,7 @@ import com.amplifyframework.statemachine.codegen.states.SignOutState internal class AuthStateMachine( resolver: StateMachineResolver, environment: Environment, - initialState: AuthState? = null, + initialState: AuthState? = null ) : StateMachine(resolver, environment, initialState = initialState) { constructor(environment: Environment, initialState: AuthState? = null) : this( @@ -65,24 +65,24 @@ internal class AuthStateMachine( HostedUISignInState.Resolver(HostedUICognitoActions), DeviceSRPSignInState.Resolver(DeviceSRPCognitoSignInActions), SetupTOTPState.Resolver(SetupTOTPCognitoActions), - SignInCognitoActions, + SignInCognitoActions ), SignOutState.Resolver(SignOutCognitoActions), - AuthenticationCognitoActions, + AuthenticationCognitoActions ), AuthorizationState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions), RefreshSessionState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions), - FetchAuthSessionCognitoActions, + FetchAuthSessionCognitoActions ), DeleteUserState.Resolver(DeleteUserCognitoActions), - AuthorizationCognitoActions, + AuthorizationCognitoActions ), - AuthCognitoActions, + AuthCognitoActions ), environment, - initialState, + initialState ) companion object { @@ -97,23 +97,23 @@ internal class AuthStateMachine( HostedUISignInState.Resolver(HostedUICognitoActions).logging(), DeviceSRPSignInState.Resolver(DeviceSRPCognitoSignInActions).logging(), SetupTOTPState.Resolver(SetupTOTPCognitoActions).logging(), - SignInCognitoActions, + SignInCognitoActions ).logging(), SignOutState.Resolver(SignOutCognitoActions).logging(), - AuthenticationCognitoActions, + AuthenticationCognitoActions ).logging(), AuthorizationState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions).logging(), RefreshSessionState.Resolver( FetchAuthSessionState.Resolver(FetchAuthSessionCognitoActions).logging(), - FetchAuthSessionCognitoActions, + FetchAuthSessionCognitoActions ).logging(), DeleteUserState.Resolver(DeleteUserCognitoActions), - AuthorizationCognitoActions, + AuthorizationCognitoActions ).logging(), - AuthCognitoActions, + AuthCognitoActions ).logging(), - environment, + environment ) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index b21305ecc4..d66c970fca 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -79,6 +79,7 @@ import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignOutOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignUpOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttributeOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttributesOptions +import com.amplifyframework.auth.cognito.options.AWSCognitoAuthVerifyTOTPSetupOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthWebUISignInOptions import com.amplifyframework.auth.cognito.options.AuthFlowType import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions @@ -1411,8 +1412,8 @@ internal class RealAWSCognitoAuthPlugin( try { authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) + changePasswordRequest + ) onSuccess.call() } catch (e: Exception) { onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) @@ -1538,8 +1539,8 @@ internal class RealAWSCognitoAuthPlugin( } val userAttributeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.updateUserAttributes( - userAttributesRequest - ) + userAttributesRequest + ) continuation.resume( getUpdateUserAttributeResult(userAttributeResponse, userAttributes) @@ -1625,8 +1626,8 @@ internal class RealAWSCognitoAuthPlugin( val getUserAttributeVerificationCodeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - getUserAttributeVerificationCodeRequest - ) + getUserAttributeVerificationCodeRequest + ) getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it @@ -1692,8 +1693,8 @@ internal class RealAWSCognitoAuthPlugin( } authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifyUserAttribute( - verifyUserAttributeRequest - ) + verifyUserAttributeRequest + ) onSuccess.call() } ?: onError.accept(InvalidUserPoolConfigurationException()) } catch (e: Exception) { @@ -2033,10 +2034,10 @@ internal class RealAWSCognitoAuthPlugin( authNState is AuthenticationState.FederatedToIdentityPool && authZState is AuthorizationState.SessionEstablished ) || ( - authZState is AuthorizationState.Error && - authZState.exception is SessionError && - authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated - ) -> { + authZState is AuthorizationState.Error && + authZState.exception is SessionError && + authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated + ) -> { val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool()) authStateMachine.send(event) _clearFederationToIdentityPool(onSuccess, onError) @@ -2059,17 +2060,17 @@ internal class RealAWSCognitoAuthPlugin( SessionHelper.getUsername(token)?.let { username -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.associateSoftwareToken { - this.accessToken = token - }?.also { response -> - response.secretCode?.let { secret -> - onSuccess.accept( - TOTPSetupDetails( - secret, - username - ) + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username ) - } + ) } + } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { @@ -2102,7 +2103,8 @@ internal class RealAWSCognitoAuthPlugin( onSuccess: Action, onError: Consumer ) { - verifyTotp(code, options.friendlyDeviceName, onSuccess, onError) + val cognitoOptions = options as? AWSCognitoAuthVerifyTOTPSetupOptions + verifyTotp(code, cognitoOptions?.friendlyDeviceName, onSuccess, onError) } fun fetchMFAPreference( @@ -2118,21 +2120,21 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUser { - this.accessToken = token - }?.also { response -> - var enabledSet: MutableSet? = null - var preferred: MFAType? = null - if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() - response.userMfaSettingList?.forEach { mfaType -> - enabledSet.add(MFAType.toMFAType(mfaType)) - } - } - response.preferredMfaSetting?.let { preferredMFA -> - preferred = MFAType.toMFAType(preferredMFA) + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) } - onSuccess.accept(UserMFAPreference(enabledSet, preferred)) } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( @@ -2212,12 +2214,12 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifySoftwareToken { - this.userCode = code - this.friendlyDeviceName = friendlyDeviceName - this.accessToken = token - }?.also { - onSuccess.call() - } + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + onSuccess.call() + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt index be21124656..22cf8e952d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt @@ -83,7 +83,9 @@ internal object SRPCognitoActions : SRPActions { SRPEvent( SRPEvent.EventType.RespondPasswordVerifier( - challengeParams, event.metadata, initiateAuthResponse.session + challengeParams, + event.metadata, + initiateAuthResponse.session ) ) } ?: throw Exception("Auth challenge parameters are empty.") @@ -142,7 +144,9 @@ internal object SRPCognitoActions : SRPActions { SRPEvent( SRPEvent.EventType.RespondPasswordVerifier( - challengeParams, event.metadata, initiateAuthResponse.session + challengeParams, + event.metadata, + initiateAuthResponse.session ) ) } ?: throw ServiceException( @@ -188,7 +192,7 @@ internal object SRPCognitoActions : SRPActions { KEY_USERNAME to username, KEY_PASSWORD_CLAIM_SECRET_BLOCK to secretBlock, KEY_PASSWORD_CLAIM_SIGNATURE to srpHelper.getSignature(salt, srpB, secretBlock), - KEY_TIMESTAMP to srpHelper.dateString, + KEY_TIMESTAMP to srpHelper.dateString ) secretHash?.let { challengeParams[KEY_SECRET_HASH] = it } challengeParams[KEY_DEVICE_KEY] = deviceKey diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index aa6baf1ee9..8cf860ce26 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -28,7 +28,7 @@ import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent internal object SetupTOTPCognitoActions : SetupTOTPActions { override fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action = Action( - "InitiateTOTPSetup", + "InitiateTOTPSetup" ) { id, dispatcher -> logger.verbose("$id Starting execution") val evt = try { @@ -39,15 +39,15 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { logger.verbose("New Session is ${response.session}") SetupTOTPEvent( SetupTOTPEvent.EventType.WaitForAnswer( - SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username), - ), + SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username) + ) ) } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")), + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")) ) } catch (e: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")), + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")) ) } logger.verbose("$id Sending event ${evt.type}") @@ -55,7 +55,7 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { } override fun verifyChallengeAnswer( - eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer, + eventType: SetupTOTPEvent.EventType.VerifyChallengeAnswer ): Action = Action("verifyChallengeAnswer") { id, dispatcher -> logger.verbose("$id Starting execution") @@ -73,25 +73,25 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { SetupTOTPEvent( SetupTOTPEvent.EventType.RespondToAuthChallenge( eventType.username, - it.session, - ), + it.session + ) ) } else -> { SetupTOTPEvent( SetupTOTPEvent.EventType.ThrowAuthError( - Exception("Software token verification failed"), - ), + Exception("Software token verification failed") + ) ) } } } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")), + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) ) } catch (exception: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(exception), + SetupTOTPEvent.EventType.ThrowAuthError(exception) ) } logger.verbose("$id Sending event ${evt.type}") @@ -99,7 +99,7 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { } override fun respondToAuthChallenge( - eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge, + eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge ): Action = Action("RespondToAuthChallenge") { id, dispatcher -> logger.verbose("$id Starting execution") @@ -120,14 +120,14 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { challengeNameType = response.challengeName, session = response.session, challengeParameters = response.challengeParameters, - authenticationResult = response.authenticationResult, + authenticationResult = response.authenticationResult ) } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")), + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) ) } catch (exception: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(exception), + SetupTOTPEvent.EventType.ThrowAuthError(exception) ) } dispatcher.send(evt) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 8891196746..1c611904d5 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -49,7 +49,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartCustomAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = CustomSignInEvent( - CustomSignInEvent.EventType.InitiateCustomSignIn(event.username, event.metadata), + CustomSignInEvent.EventType.InitiateCustomSignIn(event.username, event.metadata) ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -59,7 +59,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartMigrationAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = SignInEvent( - SignInEvent.EventType.InitiateMigrateAuth(event.username, event.password, event.metadata), + SignInEvent.EventType.InitiateMigrateAuth(event.username, event.password, event.metadata) ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -77,7 +77,7 @@ internal object SignInCognitoActions : SignInActions { Action("StartDeviceSRPAuth") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = DeviceSRPSignInEvent( - DeviceSRPSignInEvent.EventType.RespondDeviceSRPChallenge(event.username, event.metadata), + DeviceSRPSignInEvent.EventType.RespondDeviceSRPChallenge(event.username, event.metadata) ) logger.verbose("$id Sending event ${evt.type}") dispatcher.send(evt) @@ -109,20 +109,20 @@ internal object SignInCognitoActions : SignInActions { this.passwordVerifier = deviceVerifierMap["verifier"] this.salt = deviceVerifierMap["salt"] } - }, + } ) ?: throw ServiceException("Sign in failed", AmplifyException.TODO_RECOVERY_SUGGESTION) val updatedDeviceMetadata = deviceMetadata.copy(deviceSecret = deviceVerifierMap["secret"]) credentialStoreClient.storeCredentials( CredentialType.Device(event.signedInData.username), - AmplifyCredential.DeviceData(updatedDeviceMetadata), + AmplifyCredential.DeviceData(updatedDeviceMetadata) ) AuthenticationEvent( AuthenticationEvent.EventType.SignInCompleted( event.signedInData, - DeviceMetadata.Metadata(deviceKey, deviceGroupKey), - ), + DeviceMetadata.Metadata(deviceKey, deviceGroupKey) + ) ) } catch (e: Exception) { SignInEvent(SignInEvent.EventType.ThrowError(e)) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index 370b6d75ec..5803b1ec4c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -46,7 +46,7 @@ internal object SignInChallengeHelper { session: String?, challengeParameters: Map?, authenticationResult: AuthenticationResultType?, - signInMethod: SignInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH), + signInMethod: SignInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH) ): StateMachineEvent { return when { authenticationResult != null -> { @@ -59,23 +59,23 @@ internal object SignInChallengeHelper { username, Date(), signInMethod, - tokens, + tokens ) it.newDeviceMetadata?.let { metadata -> SignInEvent( SignInEvent.EventType.ConfirmDevice( DeviceMetadata.Metadata( metadata.deviceKey ?: "", - metadata.deviceGroupKey ?: "", + metadata.deviceGroupKey ?: "" ), - signedInData, - ), + signedInData + ) ) } ?: AuthenticationEvent( AuthenticationEvent.EventType.SignInCompleted( signedInData, - DeviceMetadata.Empty, - ), + DeviceMetadata.Empty + ) ) } } @@ -104,7 +104,7 @@ internal object SignInChallengeHelper { onSuccess: Consumer, onError: Consumer, signInTOTPSetupData: SignInTOTPSetupData? = null, - allowedMFAType: Set? = null, + allowedMFAType: Set? = null ) { val challengeParams = challenge.parameters?.toMutableMap() ?: mapOf() @@ -113,8 +113,8 @@ internal object SignInChallengeHelper { val deliveryDetails = AuthCodeDeliveryDetails( challengeParams.getValue("CODE_DELIVERY_DESTINATION"), AuthCodeDeliveryDetails.DeliveryMedium.fromString( - challengeParams.getValue("CODE_DELIVERY_DELIVERY_MEDIUM"), - ), + challengeParams.getValue("CODE_DELIVERY_DELIVERY_MEDIUM") + ) ) val authSignInResult = AuthSignInResult( false, @@ -123,8 +123,8 @@ internal object SignInChallengeHelper { mapOf(), deliveryDetails, null, - null, - ), + null + ) ) onSuccess.accept(authSignInResult) } @@ -136,8 +136,8 @@ internal object SignInChallengeHelper { challengeParams, null, null, - null, - ), + null + ) ) onSuccess.accept(authSignInResult) } @@ -149,8 +149,8 @@ internal object SignInChallengeHelper { challengeParams, null, null, - null, - ), + null + ) ) onSuccess.accept(authSignInResult) } @@ -162,8 +162,8 @@ internal object SignInChallengeHelper { mapOf(), null, null, - null, - ), + null + ) ) onSuccess.accept(authSignInResult) } @@ -176,8 +176,8 @@ internal object SignInChallengeHelper { challengeParams, null, TOTPSetupDetails(it.secretCode, it.username), - allowedMFAType, - ), + allowedMFAType + ) ) onSuccess.accept(authSignInResult) } ?: onError.accept(UnknownException(cause = Exception("Challenge type not supported."))) @@ -190,8 +190,8 @@ internal object SignInChallengeHelper { mapOf(), null, null, - challengeParams["MFAS_CAN_CHOOSE"]?.let { getAllowedMFATypes(it) }, - ), + challengeParams["MFAS_CAN_CHOOSE"]?.let { getAllowedMFATypes(it) } + ) ) onSuccess.accept(authSignInResult) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java index 1d09c651b4..a3c2ced45b 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java @@ -17,21 +17,41 @@ import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; +/** + * Cognito extension of update verify totp setup options to add the platform specific fields. + */ public final class AWSCognitoAuthVerifyTOTPSetupOptions extends AuthVerifyTOTPSetupOptions { + private String friendlyDeviceName; + private AWSCognitoAuthVerifyTOTPSetupOptions(String friendlyDeviceName) { - super(friendlyDeviceName); + this.friendlyDeviceName = friendlyDeviceName; + } + + /** + * Return the friendlyDeviceName to set during cognito TOTP setup. + * @return friendlyDeviceName string + * */ + public String getFriendlyDeviceName() { + return friendlyDeviceName; } + /** + * The builder for this class. + */ public static final class CognitoBuilder extends Builder { + private String friendlyDeviceName; - @Override - public CognitoBuilder getThis() { - return this; + private String getFriendlyDeviceName() { + return friendlyDeviceName; } + /** + * Construct and return the object with the values set in the builder. + * @return a new instance of AWSCognitoAuthVerifyTOTPSetupOptions with the values specified in the builder. + */ public AWSCognitoAuthVerifyTOTPSetupOptions build() { - return new AWSCognitoAuthVerifyTOTPSetupOptions(super.getFriendlyDeviceName()); + return new AWSCognitoAuthVerifyTOTPSetupOptions(getFriendlyDeviceName()); } } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt index 98994b9e81..42b4a9a169 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/StateMachine.kt @@ -137,9 +137,9 @@ internal open class StateMachine, + val metadata: Map ) : EventType() data class InitiateSignInWithCustom( val username: String, - val metadata: Map, + val metadata: Map ) : EventType() data class InitiateCustomSignInWithSRP( val username: String, val password: String, - val metadata: Map, + val metadata: Map ) : EventType() data class InitiateMigrateAuth( val username: String, val password: String, - val metadata: Map, + val metadata: Map ) : EventType() data class InitiateHostedUISignIn(val hostedUISignInData: SignInData.HostedUISignInData) : EventType() data class SignedIn(val id: String = "") : EventType() data class InitiateSignInWithDeviceSRP( val username: String, - val metadata: Map, + val metadata: Map ) : EventType() data class ConfirmDevice( val deviceMetadata: DeviceMetadata.Metadata, - val signedInData: SignedInData, + val signedInData: SignedInData ) : EventType() data class FinalizeSignIn(val id: String = "") : EventType() data class ReceivedChallenge(val challenge: AuthChallenge) : EventType() diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index 2b955dce20..a817d5f3f1 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -42,12 +42,12 @@ internal sealed class SetupTOTPState : State { is SetupTOTPEvent.EventType.SetupTOTP -> { StateResolution( SetupTOTP(challengeEvent.totpSetupDetails), - listOf(setupTOTPActions.initiateTOTPSetup(challengeEvent)), + listOf(setupTOTPActions.initiateTOTPSetup(challengeEvent)) ) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception), + Error(challengeEvent.exception) ) else -> defaultResolution @@ -59,7 +59,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception), + Error(challengeEvent.exception) ) else -> defaultResolution @@ -71,14 +71,14 @@ internal sealed class SetupTOTPState : State { Verifying( challengeEvent.answer, oldState.signInTOTPSetupData.username, - oldState.signInTOTPSetupData.session, + oldState.signInTOTPSetupData.session ), - listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), + listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)) ) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception), + Error(challengeEvent.exception) ) else -> defaultResolution @@ -90,14 +90,14 @@ internal sealed class SetupTOTPState : State { RespondingToAuthChallenge(oldState.username, oldState.session), listOf( setupTOTPActions.respondToAuthChallenge( - challengeEvent, - ), - ), + challengeEvent + ) + ) ) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception), + Error(challengeEvent.exception) ) else -> defaultResolution @@ -106,12 +106,12 @@ internal sealed class SetupTOTPState : State { is RespondingToAuthChallenge -> when (challengeEvent) { is SetupTOTPEvent.EventType.Verified -> { StateResolution( - Success(), + Success() ) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception), + Error(challengeEvent.exception) ) else -> defaultResolution @@ -122,7 +122,7 @@ internal sealed class SetupTOTPState : State { StateResolution( // TODO: Fix this Verifying(challengeEvent.answer, "", null), - listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)), + listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)) ) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index 6f06253923..0d8a4c1098 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -53,7 +53,7 @@ internal sealed class SignInState : State { private val hostedUISignInResolver: StateMachineResolver, private val deviceSRPSignInResolver: StateMachineResolver, private val setupTOTPResolver: StateMachineResolver, - private val signInActions: SignInActions, + private val signInActions: SignInActions ) : StateMachineResolver { override val defaultState = NotStarted() @@ -106,7 +106,7 @@ internal sealed class SignInState : State { private fun resolveSignInEvent( oldState: SignInState, - event: StateMachineEvent, + event: StateMachineEvent ): StateResolution { val signInEvent = asSignInEvent(event) val defaultResolution = StateResolution(oldState) @@ -114,34 +114,34 @@ internal sealed class SignInState : State { is NotStarted -> when (signInEvent) { is SignInEvent.EventType.InitiateSignInWithSRP -> StateResolution( SigningInWithSRP(oldState.srpSignInState), - listOf(signInActions.startSRPAuthAction(signInEvent)), + listOf(signInActions.startSRPAuthAction(signInEvent)) ) is SignInEvent.EventType.InitiateSignInWithCustom -> StateResolution( SigningInWithCustom(oldState.customSignInState), - listOf(signInActions.startCustomAuthAction(signInEvent)), + listOf(signInActions.startCustomAuthAction(signInEvent)) ) is SignInEvent.EventType.InitiateHostedUISignIn -> StateResolution( SigningInWithHostedUI(HostedUISignInState.NotStarted()), - listOf(signInActions.startHostedUIAuthAction(signInEvent)), + listOf(signInActions.startHostedUIAuthAction(signInEvent)) ) is SignInEvent.EventType.InitiateMigrateAuth -> StateResolution( SigningInViaMigrateAuth(MigrateSignInState.NotStarted()), - listOf(signInActions.startMigrationAuthAction(signInEvent)), + listOf(signInActions.startMigrationAuthAction(signInEvent)) ) is SignInEvent.EventType.InitiateCustomSignInWithSRP -> StateResolution( SigningInWithSRPCustom(oldState.srpSignInState), - listOf(signInActions.startCustomAuthWithSRPAction(signInEvent)), + listOf(signInActions.startCustomAuthWithSRPAction(signInEvent)) ) else -> defaultResolution } is SigningInWithSRP, is SigningInWithCustom, is SigningInViaMigrateAuth, - is SigningInWithSRPCustom, + is SigningInWithSRPCustom -> when (signInEvent) { is SignInEvent.EventType.ReceivedChallenge -> { val action = signInActions.initResolveChallenge(signInEvent) @@ -150,7 +150,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateSignInWithDeviceSRP -> StateResolution( ResolvingDeviceSRP(DeviceSRPSignInState.NotStarted()), - listOf(signInActions.startDeviceSRPAuthAction(signInEvent)), + listOf(signInActions.startDeviceSRPAuthAction(signInEvent)) ) is SignInEvent.EventType.ConfirmDevice -> { @@ -160,7 +160,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(oldState.setupTOTPState), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) @@ -180,7 +180,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(oldState.setupTOTPState), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) @@ -200,7 +200,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateSignInWithDeviceSRP -> StateResolution( ResolvingDeviceSRP(DeviceSRPSignInState.NotStarted()), - listOf(signInActions.startDeviceSRPAuthAction(signInEvent)), + listOf(signInActions.startDeviceSRPAuthAction(signInEvent)) ) is SignInEvent.EventType.FinalizeSignIn -> { @@ -218,7 +218,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(SetupTOTPState.NotStarted()), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)), + listOf(signInActions.initiateTOTOSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) diff --git a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt index a32beaa2ac..aae808ea13 100644 --- a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt +++ b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt @@ -18,11 +18,11 @@ import android.net.Uri data class TOTPSetupDetails( val sharedSecret: String, - val username: String, + val username: String ) { fun getSetupURI( issuer: String, - accountName: String = username, + accountName: String = username ): Uri { TODO() } diff --git a/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java b/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java index 5b779d5167..91d4177212 100644 --- a/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java +++ b/core/src/main/java/com/amplifyframework/auth/AuthCategoryBehavior.java @@ -515,16 +515,34 @@ void deleteUser( @NonNull Action onSuccess, @NonNull Consumer onError); + /** + * Setup TOTP for the currently signed in user. + * @param onSuccess Success callback + * @param onError Error callback + */ void setUpTOTP( @NonNull Consumer onSuccess, @NonNull Consumer onError); + /** + * Verify TOTP setup for the currently signed in user. + * @param code TOTP code to verify TOTP setup + * @param onSuccess Success callback + * @param onError Error callback + */ void verifyTOTPSetup( @NonNull String code, @NonNull Action onSuccess, @NonNull Consumer onError ); + /** + * Verify TOTP setup for the currently signed in user. + * @param code TOTP code to verify TOTP setup + * @param options additional options to verify totp setup + * @param onSuccess Success callback + * @param onError Error callback + */ void verifyTOTPSetup( @NonNull String code, @NonNull AuthVerifyTOTPSetupOptions options, diff --git a/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java index 0e42cbd530..ea62e19604 100644 --- a/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java +++ b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java @@ -12,35 +12,27 @@ * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ + package com.amplifyframework.auth.options; +/** + * The shared options among all Auth plugins. + * Note: This is currently empty but exists here to support common verify totp setup options. + */ public class AuthVerifyTOTPSetupOptions { - private String friendlyDeviceName; - - protected AuthVerifyTOTPSetupOptions(String friendlyDeviceName) { - this.friendlyDeviceName = friendlyDeviceName; - } - - public String getFriendlyDeviceName() { - return friendlyDeviceName; - } + /** + * The builder for this class. + * @param The type of builder - used to support plugin extensions of this. + */ public abstract static class Builder> { - private String friendlyDeviceName; - - protected String getFriendlyDeviceName() { - return friendlyDeviceName; - } - - public abstract T getThis(); - - public T friendlyDeviceName(String friendlyDeviceName) { - this.friendlyDeviceName = friendlyDeviceName; - return getThis(); - } + /** + * Build an instance of AuthVerifyTOTPSetupOptions (or one of its subclasses). + * @return an instance of AuthVerifyTOTPSetupOptions (or one of its subclasses) + */ public AuthVerifyTOTPSetupOptions build() { - return new AuthVerifyTOTPSetupOptions(friendlyDeviceName); + return new AuthVerifyTOTPSetupOptions(); } } From 09392e608db1bd281968518f61ce1f08ce9d88b5 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Thu, 27 Jul 2023 10:19:32 -0500 Subject: [PATCH 07/28] fix bug --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 110 +++++++++++------- .../cognito/helpers/SignInChallengeHelper.kt | 2 +- .../codegen/states/SetupTOTPState.kt | 7 +- 3 files changed, 73 insertions(+), 46 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index d66c970fca..cc50b2df37 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -678,6 +678,7 @@ internal class RealAWSCognitoAuthPlugin( val authNState = authState.authNState val authZState = authState.authZState val signInState = (authNState as? AuthenticationState.SigningIn)?.signInState + val totpSetupState = (signInState as? SignInState.ResolvingTOTPSetup)?.setupTOTPState when { authNState is AuthenticationState.SignedIn && authZState is AuthorizationState.SessionEstablished -> { @@ -703,12 +704,17 @@ internal class RealAWSCognitoAuthPlugin( (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse -> { authStateMachine.cancel(token) val signInChallengeState = signInState.challengeState as SignInChallengeState.WaitingForAnswer + var allowedMFATypes: Set? = null val signInStep = when (signInChallengeState.challenge.challengeName) { "SMS_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE "NEW_PASSWORD_REQUIRED" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD "SOFTWARE_TOKEN_MFA" -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE - "SELECT_MFA_TYPE" -> AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION - "MFA_SETUP" -> AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP + "SELECT_MFA_TYPE" -> { + signInChallengeState.challenge.parameters?.get("MFAS_CAN_CHOOSE")?.let { + allowedMFATypes = SignInChallengeHelper.getAllowedMFATypes(it) + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION + } else -> AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE } val authSignInResult = AuthSignInResult( @@ -718,13 +724,31 @@ internal class RealAWSCognitoAuthPlugin( signInChallengeState.challenge.parameters ?: mapOf(), null, null, - null + allowedMFATypes ) ) onSuccess.accept(authSignInResult) (signInState.challengeState as SignInChallengeState.WaitingForAnswer).hasNewResponse = false } + signInState is SignInState.ResolvingTOTPSetup && + totpSetupState is SetupTOTPState.WaitingForAnswer && + totpSetupState.hasNewResponse -> { + authStateMachine.cancel(token) + SignInChallengeHelper.getNextStep( + AuthChallenge( + ChallengeNameType.MfaSetup.value, + null, + null, + null + ), + onSuccess, + onError, + totpSetupState.signInTOTPSetupData + ) + totpSetupState.hasNewResponse = false + } + signInState is SignInState.ResolvingChallenge && signInState.challengeState is SignInChallengeState.Error && (signInState.challengeState as SignInChallengeState.Error).hasNewResponse -> { @@ -1412,8 +1436,8 @@ internal class RealAWSCognitoAuthPlugin( try { authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) + changePasswordRequest + ) onSuccess.call() } catch (e: Exception) { onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) @@ -1539,8 +1563,8 @@ internal class RealAWSCognitoAuthPlugin( } val userAttributeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.updateUserAttributes( - userAttributesRequest - ) + userAttributesRequest + ) continuation.resume( getUpdateUserAttributeResult(userAttributeResponse, userAttributes) @@ -1626,8 +1650,8 @@ internal class RealAWSCognitoAuthPlugin( val getUserAttributeVerificationCodeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - getUserAttributeVerificationCodeRequest - ) + getUserAttributeVerificationCodeRequest + ) getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it @@ -1693,8 +1717,8 @@ internal class RealAWSCognitoAuthPlugin( } authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifyUserAttribute( - verifyUserAttributeRequest - ) + verifyUserAttributeRequest + ) onSuccess.call() } ?: onError.accept(InvalidUserPoolConfigurationException()) } catch (e: Exception) { @@ -2034,10 +2058,10 @@ internal class RealAWSCognitoAuthPlugin( authNState is AuthenticationState.FederatedToIdentityPool && authZState is AuthorizationState.SessionEstablished ) || ( - authZState is AuthorizationState.Error && - authZState.exception is SessionError && - authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated - ) -> { + authZState is AuthorizationState.Error && + authZState.exception is SessionError && + authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated + ) -> { val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool()) authStateMachine.send(event) _clearFederationToIdentityPool(onSuccess, onError) @@ -2060,17 +2084,17 @@ internal class RealAWSCognitoAuthPlugin( SessionHelper.getUsername(token)?.let { username -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.associateSoftwareToken { - this.accessToken = token - }?.also { response -> - response.secretCode?.let { secret -> - onSuccess.accept( - TOTPSetupDetails( - secret, - username + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username + ) ) - ) + } } - } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { @@ -2120,21 +2144,21 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUser { - this.accessToken = token - }?.also { response -> - var enabledSet: MutableSet? = null - var preferred: MFAType? = null - if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() - response.userMfaSettingList?.forEach { mfaType -> - enabledSet.add(MFAType.toMFAType(mfaType)) + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) + } } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) } - response.preferredMfaSetting?.let { preferredMFA -> - preferred = MFAType.toMFAType(preferredMFA) - } - onSuccess.accept(UserMFAPreference(enabledSet, preferred)) - } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( @@ -2214,12 +2238,12 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifySoftwareToken { - this.userCode = code - this.friendlyDeviceName = friendlyDeviceName - this.accessToken = token - }?.also { - onSuccess.call() - } + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + onSuccess.call() + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index 5803b1ec4c..f3090b6caf 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -199,7 +199,7 @@ internal object SignInChallengeHelper { } } - private fun getAllowedMFATypes(allowedMFAType: String): Set { + fun getAllowedMFATypes(allowedMFAType: String): Set { val result = mutableSetOf() allowedMFAType.replace(Regex("\\[|\\]|\""), "").split(",").forEach { when (it) { diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index a817d5f3f1..af00e0e3fa 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -25,7 +25,10 @@ import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent internal sealed class SetupTOTPState : State { data class NotStarted(val id: String = "") : SetupTOTPState() data class SetupTOTP(val signInTOTPSetupData: SignInTOTPSetupData) : SetupTOTPState() - data class WaitingForAnswer(val signInTOTPSetupData: SignInTOTPSetupData) : SetupTOTPState() + data class WaitingForAnswer( + val signInTOTPSetupData: SignInTOTPSetupData, + var hasNewResponse: Boolean = false + ) : SetupTOTPState() data class Verifying(val code: String, val username: String, val session: String?) : SetupTOTPState() data class RespondingToAuthChallenge(val username: String, val session: String?) : SetupTOTPState() data class Success(val id: String = "") : SetupTOTPState() @@ -55,7 +58,7 @@ internal sealed class SetupTOTPState : State { is SetupTOTP -> when (challengeEvent) { is SetupTOTPEvent.EventType.WaitForAnswer -> { - StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails)) + StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails, true)) } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( From fd10f312f84cf5620e10dedd5dda2d0d470a88eb Mon Sep 17 00:00:00 2001 From: sdhuka Date: Thu, 27 Jul 2023 10:36:25 -0500 Subject: [PATCH 08/28] remove extra logging --- .../amplifyframework/auth/cognito/actions/SRPCognitoActions.kt | 1 - .../auth/cognito/actions/SignInChallengeCognitoActions.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt index 22cf8e952d..ad756218a7 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActions.kt @@ -210,7 +210,6 @@ internal object SRPCognitoActions : SRPActions { encodedContextData?.let { userContextData { encodedData = it } } } if (response != null) { - logger.verbose("SRP Session: ${response.session}") SignInChallengeHelper.evaluateNextStep( username, response.challengeName, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt index 769bbd0222..55c92442a1 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInChallengeCognitoActions.kt @@ -56,7 +56,6 @@ internal object SignInChallengeCognitoActions : SignInChallengeActions { val encodedContextData = username?.let { getUserContextData(it) } val pinpointEndpointId = getPinpointEndpointId() - logger.verbose("SRP Session: ${challenge.session}") val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { clientId = configuration.userPool?.appClient challengeName = ChallengeNameType.fromValue(challenge.challengeName) From c85884a0560cd4893a5bca78a46083c8b569422b Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 28 Jul 2023 11:18:01 -0500 Subject: [PATCH 09/28] address PR comments --- .../auth/cognito/AWSCognitoAuthPlugin.kt | 12 ++---------- .../auth/cognito/actions/SetupTOTPCognitoActions.kt | 7 ------- .../auth/cognito/actions/SignInCognitoActions.kt | 2 +- .../service/EnableSoftwareTokenMfaException.kt | 12 ++++++------ .../statemachine/codegen/actions/SetupTOTPActions.kt | 4 ---- .../statemachine/codegen/actions/SignInActions.kt | 2 +- .../statemachine/codegen/data/SignInTOTPSetupData.kt | 2 +- .../statemachine/codegen/states/SetupTOTPState.kt | 4 ++++ .../statemachine/codegen/states/SignInState.kt | 6 +++--- 9 files changed, 18 insertions(+), 33 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt index cbda031ea0..625b64e662 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt @@ -33,6 +33,7 @@ import com.amplifyframework.auth.AuthUser import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.cognito.asf.UserContextDataProvider +import com.amplifyframework.auth.cognito.options.AWSCognitoAuthVerifyTOTPSetupOptions import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult import com.amplifyframework.auth.exceptions.ConfigurationException @@ -784,16 +785,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { } override fun verifyTOTPSetup(code: String, onSuccess: Action, onError: Consumer) { - queueChannel.trySend( - pluginScope.launch(start = CoroutineStart.LAZY) { - try { - queueFacade.verifyTOTPSetup(code) - onSuccess.call() - } catch (e: Exception) { - onError.accept(e.toAuthException()) - } - } - ) + verifyTOTPSetup(code, AWSCognitoAuthVerifyTOTPSetupOptions.CognitoBuilder().build(), onSuccess, onError) } override fun verifyTOTPSetup( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index 8cf860ce26..ff9a001250 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -36,7 +36,6 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { session = eventType.totpSetupDetails.session } response?.secretCode?.let { secret -> - logger.verbose("New Session is ${response.session}") SetupTOTPEvent( SetupTOTPEvent.EventType.WaitForAnswer( SignInTOTPSetupData(secret, response.session, eventType.totpSetupDetails.username) @@ -132,10 +131,4 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { } dispatcher.send(evt) } - - override fun resetToWaitingForAnswer(eventType: SetupTOTPEvent.EventType.ThrowAuthError): Action = - Action("ResetToWaitingForAnswer") { id, dispatcher -> - logger.verbose("$id Starting execution") - // TODO: Reset to waiting for answer - } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 1c611904d5..84e2ab747e 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -139,7 +139,7 @@ internal object SignInCognitoActions : SignInActions { dispatcher.send(evt) } - override fun initiateTOTOSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = + override fun initiateTOTPSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = Action("initiateTOTOSetup") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP(event.signInTOTPSetupData)) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt index 6239a37dee..2cdd44c28a 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/exceptions/service/EnableSoftwareTokenMfaException.kt @@ -1,9 +1,5 @@ -package com.amplifyframework.auth.cognito.exceptions.service - -import com.amplifyframework.auth.exceptions.ServiceException - /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. @@ -16,12 +12,16 @@ import com.amplifyframework.auth.exceptions.ServiceException * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ +package com.amplifyframework.auth.cognito.exceptions.service + +import com.amplifyframework.auth.AuthException + /** * Software Token MFA is not enabled for the user. * @param cause The underlying cause of this exception */ open class EnableSoftwareTokenMfaException(cause: Throwable?) : - ServiceException( + AuthException( "Software token TOTP multi-factor authentication (MFA) is not enabled for the user pool.", "Enable the software token MFA for the user.", cause diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt index 718eed2835..5d47ddc599 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SetupTOTPActions.kt @@ -26,8 +26,4 @@ internal interface SetupTOTPActions { fun respondToAuthChallenge( eventType: SetupTOTPEvent.EventType.RespondToAuthChallenge ): Action - - fun resetToWaitingForAnswer( - eventType: SetupTOTPEvent.EventType.ThrowAuthError - ): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt index afb4af1363..ba8f868f3f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/actions/SignInActions.kt @@ -27,5 +27,5 @@ internal interface SignInActions { fun initResolveChallenge(event: SignInEvent.EventType.ReceivedChallenge): Action fun confirmDevice(event: SignInEvent.EventType.ConfirmDevice): Action fun startHostedUIAuthAction(event: SignInEvent.EventType.InitiateHostedUISignIn): Action - fun initiateTOTOSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup): Action + fun initiateTOTPSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup): Action } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt index 789678ac54..95b065a2dc 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/SignInTOTPSetupData.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index af00e0e3fa..2411240f6c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -129,6 +129,10 @@ internal sealed class SetupTOTPState : State { ) } + is SetupTOTPEvent.EventType.WaitForAnswer -> { + StateResolution(WaitingForAnswer(challengeEvent.totpSetupDetails, true)) + } + else -> defaultResolution } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt index 0d8a4c1098..b5317633b1 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SignInState.kt @@ -160,7 +160,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(oldState.setupTOTPState), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)) + listOf(signInActions.initiateTOTPSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) @@ -180,7 +180,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(oldState.setupTOTPState), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)) + listOf(signInActions.initiateTOTPSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) @@ -218,7 +218,7 @@ internal sealed class SignInState : State { is SignInEvent.EventType.InitiateTOTPSetup -> StateResolution( ResolvingTOTPSetup(SetupTOTPState.NotStarted()), - listOf(signInActions.initiateTOTOSetupAction(signInEvent)) + listOf(signInActions.initiateTOTPSetupAction(signInEvent)) ) is SignInEvent.EventType.ThrowError -> StateResolution(Error(signInEvent.exception)) From a40cc8402458a3f2bce213a3291e2aa3790ecdb5 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 28 Jul 2023 11:36:20 -0500 Subject: [PATCH 10/28] reset hasNewResponse --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index cc50b2df37..230cc71764 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -589,6 +589,7 @@ internal class RealAWSCognitoAuthPlugin( onError, totpSetupState.signInTOTPSetupData ) + totpSetupState?.hasNewResponse = false } } } @@ -1436,8 +1437,8 @@ internal class RealAWSCognitoAuthPlugin( try { authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) + changePasswordRequest + ) onSuccess.call() } catch (e: Exception) { onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) @@ -1563,8 +1564,8 @@ internal class RealAWSCognitoAuthPlugin( } val userAttributeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.updateUserAttributes( - userAttributesRequest - ) + userAttributesRequest + ) continuation.resume( getUpdateUserAttributeResult(userAttributeResponse, userAttributes) @@ -1650,8 +1651,8 @@ internal class RealAWSCognitoAuthPlugin( val getUserAttributeVerificationCodeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - getUserAttributeVerificationCodeRequest - ) + getUserAttributeVerificationCodeRequest + ) getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it @@ -1717,8 +1718,8 @@ internal class RealAWSCognitoAuthPlugin( } authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifyUserAttribute( - verifyUserAttributeRequest - ) + verifyUserAttributeRequest + ) onSuccess.call() } ?: onError.accept(InvalidUserPoolConfigurationException()) } catch (e: Exception) { @@ -2058,10 +2059,10 @@ internal class RealAWSCognitoAuthPlugin( authNState is AuthenticationState.FederatedToIdentityPool && authZState is AuthorizationState.SessionEstablished ) || ( - authZState is AuthorizationState.Error && - authZState.exception is SessionError && - authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated - ) -> { + authZState is AuthorizationState.Error && + authZState.exception is SessionError && + authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated + ) -> { val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool()) authStateMachine.send(event) _clearFederationToIdentityPool(onSuccess, onError) @@ -2084,17 +2085,17 @@ internal class RealAWSCognitoAuthPlugin( SessionHelper.getUsername(token)?.let { username -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.associateSoftwareToken { - this.accessToken = token - }?.also { response -> - response.secretCode?.let { secret -> - onSuccess.accept( - TOTPSetupDetails( - secret, - username - ) + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username ) - } + ) } + } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { @@ -2144,21 +2145,21 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUser { - this.accessToken = token - }?.also { response -> - var enabledSet: MutableSet? = null - var preferred: MFAType? = null - if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() - response.userMfaSettingList?.forEach { mfaType -> - enabledSet.add(MFAType.toMFAType(mfaType)) - } - } - response.preferredMfaSetting?.let { preferredMFA -> - preferred = MFAType.toMFAType(preferredMFA) + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) } - onSuccess.accept(UserMFAPreference(enabledSet, preferred)) } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( @@ -2238,12 +2239,12 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifySoftwareToken { - this.userCode = code - this.friendlyDeviceName = friendlyDeviceName - this.accessToken = token - }?.also { - onSuccess.call() - } + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + onSuccess.call() + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( From 119a4c4d484670a1561b0d7d1e8e50b0bd35570c Mon Sep 17 00:00:00 2001 From: sdhuka Date: Mon, 31 Jul 2023 15:17:09 -0500 Subject: [PATCH 11/28] add unit tests --- aws-auth-cognito/build.gradle.kts | 2 + .../auth/cognito/KotlinAuthFacadeInternal.kt | 11 - .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 80 ++--- .../actions/SetupTOTPCognitoActions.kt | 3 +- .../AWSCognitoAuthVerifyTOTPSetupOptions.java | 10 + .../auth/cognito/AWSCognitoAuthPluginTest.kt | 49 +++ .../auth/cognito/StateTransitionTestBase.kt | 10 +- .../auth/cognito/StateTransitionTests.kt | 2 + .../actions/SetupTOTPCognitoActionsTest.kt | 282 ++++++++++++++++++ .../serializers/AuthStatesSerializer.kt | 15 +- 10 files changed, 404 insertions(+), 60 deletions(-) create mode 100644 aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt diff --git a/aws-auth-cognito/build.gradle.kts b/aws-auth-cognito/build.gradle.kts index 05ba57a791..a484e708f6 100644 --- a/aws-auth-cognito/build.gradle.kts +++ b/aws-auth-cognito/build.gradle.kts @@ -39,6 +39,8 @@ dependencies { implementation(dependency.aws.cognitoidentityprovider) testImplementation(project(":testutils")) + testImplementation(project(":core")) + testImplementation(project(":aws-core")) //noinspection GradleDependency testImplementation(testDependency.json) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt index f8a4400474..96ce06d54a 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt @@ -531,17 +531,6 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth ) } } - - suspend fun verifyTOTPSetup(code: String) { - return suspendCoroutine { continuation -> - delegate.verifyTOTPSetup( - code, - { continuation.resume(Unit) }, - { continuation.resumeWithException(it) } - ) - } - } - suspend fun verifyTOTPSetup(code: String, options: AuthVerifyTOTPSetupOptions) { return suspendCoroutine { continuation -> delegate.verifyTOTPSetup( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 230cc71764..409063c200 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -1437,8 +1437,8 @@ internal class RealAWSCognitoAuthPlugin( try { authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) + changePasswordRequest + ) onSuccess.call() } catch (e: Exception) { onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) @@ -1564,8 +1564,8 @@ internal class RealAWSCognitoAuthPlugin( } val userAttributeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.updateUserAttributes( - userAttributesRequest - ) + userAttributesRequest + ) continuation.resume( getUpdateUserAttributeResult(userAttributeResponse, userAttributes) @@ -1651,8 +1651,8 @@ internal class RealAWSCognitoAuthPlugin( val getUserAttributeVerificationCodeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - getUserAttributeVerificationCodeRequest - ) + getUserAttributeVerificationCodeRequest + ) getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it @@ -1718,8 +1718,8 @@ internal class RealAWSCognitoAuthPlugin( } authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifyUserAttribute( - verifyUserAttributeRequest - ) + verifyUserAttributeRequest + ) onSuccess.call() } ?: onError.accept(InvalidUserPoolConfigurationException()) } catch (e: Exception) { @@ -2059,10 +2059,10 @@ internal class RealAWSCognitoAuthPlugin( authNState is AuthenticationState.FederatedToIdentityPool && authZState is AuthorizationState.SessionEstablished ) || ( - authZState is AuthorizationState.Error && - authZState.exception is SessionError && - authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated - ) -> { + authZState is AuthorizationState.Error && + authZState.exception is SessionError && + authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated + ) -> { val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool()) authStateMachine.send(event) _clearFederationToIdentityPool(onSuccess, onError) @@ -2085,17 +2085,17 @@ internal class RealAWSCognitoAuthPlugin( SessionHelper.getUsername(token)?.let { username -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.associateSoftwareToken { - this.accessToken = token - }?.also { response -> - response.secretCode?.let { secret -> - onSuccess.accept( - TOTPSetupDetails( - secret, - username + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username + ) ) - ) + } } - } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { @@ -2145,21 +2145,21 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUser { - this.accessToken = token - }?.also { response -> - var enabledSet: MutableSet? = null - var preferred: MFAType? = null - if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() - response.userMfaSettingList?.forEach { mfaType -> - enabledSet.add(MFAType.toMFAType(mfaType)) + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) + } } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) } - response.preferredMfaSetting?.let { preferredMFA -> - preferred = MFAType.toMFAType(preferredMFA) - } - onSuccess.accept(UserMFAPreference(enabledSet, preferred)) - } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( @@ -2239,12 +2239,12 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifySoftwareToken { - this.userCode = code - this.friendlyDeviceName = friendlyDeviceName - this.accessToken = token - }?.also { - onSuccess.call() - } + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + onSuccess.call() + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index ff9a001250..a44b77aa9f 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -46,7 +46,7 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { ) } catch (e: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")) + SetupTOTPEvent.EventType.ThrowAuthError(e) ) } logger.verbose("$id Sending event ${evt.type}") @@ -68,7 +68,6 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { response?.let { when (it.status) { is VerifySoftwareTokenResponseType.Success -> { - logger.verbose("New Session is ${response.session}") SetupTOTPEvent( SetupTOTPEvent.EventType.RespondToAuthChallenge( eventType.username, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java index a3c2ced45b..f8794014d3 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/options/AWSCognitoAuthVerifyTOTPSetupOptions.java @@ -46,6 +46,16 @@ private String getFriendlyDeviceName() { return friendlyDeviceName; } + /** + * Friendly device name to be set in Cognito. + * @param friendlyDeviceName String input for friendlyDeviceName + * @return current CognitoBuilder instance + * */ + public CognitoBuilder setFriendlyDeviceName(String friendlyDeviceName) { + this.friendlyDeviceName = friendlyDeviceName; + return this; + } + /** * Construct and return the object with the values set in the builder. * @return a new instance of AWSCognitoAuthVerifyTOTPSetupOptions with the values specified in the builder. diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt index 4a7738e2be..a180bb6ef9 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTest.kt @@ -17,6 +17,7 @@ package com.amplifyframework.auth.cognito import android.app.Activity import android.content.Intent +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthDevice import com.amplifyframework.auth.AuthException @@ -25,6 +26,7 @@ import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.AuthUser import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.cognito.options.AWSCognitoAuthVerifyTOTPSetupOptions import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions @@ -681,6 +683,53 @@ class AWSCognitoAuthPluginTest { verify(timeout = CHANNEL_TIMEOUT) { realPlugin.clearFederationToIdentityPool(any(), any()) } } + @Test + fun setUpTOTP() { + val expectedOnSuccess = Consumer { } + val expectedOnError = Consumer { } + authPlugin.setUpTOTP(expectedOnSuccess, expectedOnError) + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.setUpTOTP(any(), any()) } + } + + @Test + fun verifyTOTPSetup() { + val code = "123456" + val expectedOnSuccess = Action { } + val expectedOnError = Consumer { } + authPlugin.verifyTOTPSetup(code, expectedOnSuccess, expectedOnError) + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.verifyTOTPSetup(code, any(), any(), any()) } + } + + @Test + fun verifyTOTPSetupWithOptions() { + val code = "123456" + val options = AWSCognitoAuthVerifyTOTPSetupOptions.CognitoBuilder().setFriendlyDeviceName("DEVICE_NAME").build() + val expectedOnSuccess = Action { } + val expectedOnError = Consumer { } + authPlugin.verifyTOTPSetup(code, options, expectedOnSuccess, expectedOnError) + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.verifyTOTPSetup(code, options, any(), any()) } + } + + @Test + fun fetchMFAPreferences() { + val expectedOnSuccess = Consumer { } + val expectedOnError = Consumer { } + authPlugin.fetchMFAPreference(expectedOnSuccess, expectedOnError) + verify(timeout = CHANNEL_TIMEOUT) { realPlugin.fetchMFAPreference(any(), any()) } + } + + @Test + fun updateMFAPreferences() { + val smsPreference = MFAPreference.Enabled + val totpPreference = MFAPreference.Preferred + val onSuccess = Action { } + val onError = Consumer { } + authPlugin.updateMFAPreference(smsPreference, totpPreference, onSuccess, onError) + verify(timeout = CHANNEL_TIMEOUT) { + realPlugin.updateMFAPreference(smsPreference, totpPreference, any(), any()) + } + } + @Test fun verifyPluginKey() { assertEquals("awsCognitoAuthPlugin", authPlugin.pluginKey) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt index b5f2e803a2..22f250799c 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTestBase.kt @@ -27,6 +27,7 @@ import com.amplifyframework.statemachine.codegen.actions.FetchAuthSessionActions import com.amplifyframework.statemachine.codegen.actions.HostedUIActions import com.amplifyframework.statemachine.codegen.actions.MigrateAuthActions import com.amplifyframework.statemachine.codegen.actions.SRPActions +import com.amplifyframework.statemachine.codegen.actions.SetupTOTPActions import com.amplifyframework.statemachine.codegen.actions.SignInActions import com.amplifyframework.statemachine.codegen.actions.SignInChallengeActions import com.amplifyframework.statemachine.codegen.actions.SignOutActions @@ -117,13 +118,16 @@ open class StateTransitionTestBase { @Mock internal lateinit var mockDeleteUserActions: DeleteUserCognitoActions + @Mock + internal lateinit var mockSetupTOTPActions: SetupTOTPActions + private val dummyCredential = AmplifyCredential.UserAndIdentityPool( SignedInData( "userId", "username", Date(0), SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH), - CognitoUserPoolTokens("idToken", "accessToken", "refreshToken", 123123L), + CognitoUserPoolTokens("idToken", "accessToken", "refreshToken", 123123L) ), "identityPool", AWSCredentials( @@ -342,7 +346,9 @@ open class StateTransitionTestBase { dispatcher.send( SRPEvent( SRPEvent.EventType.RespondPasswordVerifier( - mapOf(), mapOf(), "sample_session" + mapOf(), + mapOf(), + "sample_session" ) ) ) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt index f8a09bbbb3..0dbe3ae41f 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/StateTransitionTests.kt @@ -38,6 +38,7 @@ import com.amplifyframework.statemachine.codegen.states.HostedUISignInState import com.amplifyframework.statemachine.codegen.states.MigrateSignInState import com.amplifyframework.statemachine.codegen.states.RefreshSessionState import com.amplifyframework.statemachine.codegen.states.SRPSignInState +import com.amplifyframework.statemachine.codegen.states.SetupTOTPState import com.amplifyframework.statemachine.codegen.states.SignInChallengeState import com.amplifyframework.statemachine.codegen.states.SignInState import com.amplifyframework.statemachine.codegen.states.SignOutState @@ -92,6 +93,7 @@ class StateTransitionTests : StateTransitionTestBase() { SignInChallengeState.Resolver(mockSignInChallengeActions), HostedUISignInState.Resolver(mockHostedUIActions), DeviceSRPSignInState.Resolver(mockDeviceSRPSignInActions), + SetupTOTPState.Resolver(mockSetupTOTPActions), mockSignInActions ), SignOutState.Resolver(mockSignOutActions), diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt new file mode 100644 index 0000000000..334e5be4f7 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt @@ -0,0 +1,282 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.cognito.actions + +import androidx.test.core.app.ApplicationProvider +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AssociateSoftwareTokenRequest +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AssociateSoftwareTokenResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaNotFoundException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType +import aws.sdk.kotlin.services.cognitoidentityprovider.verifySoftwareToken +import com.amplifyframework.auth.cognito.AWSCognitoAuthService +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.StoreClientBehavior +import com.amplifyframework.logging.Logger +import com.amplifyframework.statemachine.EventDispatcher +import com.amplifyframework.statemachine.StateMachineEvent +import com.amplifyframework.statemachine.codegen.data.AuthConfiguration +import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData +import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import kotlin.test.assertEquals +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +class SetupTOTPCognitoActionsTest { + + private val configuration = mockk() + private val cognitoAuthService = mockk() + private val credentialStoreClient = mockk() + private val logger = mockk() + private val cognitoIdentityProviderClientMock = mockk() + private val dispatcher = mockk() + + private val capturedEvent = slot() + + private lateinit var authEnvironment: AuthEnvironment + + @Before + fun setup() { + every { logger.verbose(any()) }.answers {} + every { dispatcher.send(capture(capturedEvent)) }.answers { } + every { cognitoAuthService.cognitoIdentityProviderClient }.answers { cognitoIdentityProviderClientMock } + authEnvironment = AuthEnvironment( + ApplicationProvider.getApplicationContext(), + configuration, + cognitoAuthService, + credentialStoreClient, + null, + null, + logger + ) + } + + @Test + fun `initiateTOTPSetup send waitForAnswer on success`() = runTest { + val secretCode = "SECRET_CODE" + val session = "SESSION" + val username = "USERNAME" + coEvery { + cognitoIdentityProviderClientMock.associateSoftwareToken(any()) + }.answers { + AssociateSoftwareTokenResponse.invoke { + this.secretCode = secretCode + this.session = session + } + } + val initiateAction = SetupTOTPCognitoActions.initiateTOTPSetup( + SetupTOTPEvent.EventType.SetupTOTP( + SignInTOTPSetupData("", "SESSION", "USERNAME") + ) + ) + initiateAction.execute(dispatcher, authEnvironment) + + val expectedEvent = SetupTOTPEvent( + SetupTOTPEvent.EventType.WaitForAnswer(SignInTOTPSetupData(secretCode, session, username)) + ) + assertEquals( + expectedEvent.type, + capturedEvent.captured.type + ) + assertEquals( + secretCode, + ( + (capturedEvent.captured as SetupTOTPEvent).eventType as SetupTOTPEvent.EventType.WaitForAnswer + ).totpSetupDetails.secretCode + ) + } + + @Test + fun `initiateTOTPSetup send waitForAnswer on failure`() = runTest { + val session = "SESSION" + val serviceException = SoftwareTokenMfaNotFoundException { + message = "TOTP is not enabled" + } + coEvery { + cognitoIdentityProviderClientMock.associateSoftwareToken( + AssociateSoftwareTokenRequest.invoke { + this.session = session + } + ) + }.answers { + throw serviceException + } + val initiateAction = SetupTOTPCognitoActions.initiateTOTPSetup( + SetupTOTPEvent.EventType.SetupTOTP( + SignInTOTPSetupData("", "SESSION", "USERNAME") + ) + ) + initiateAction.execute(dispatcher, authEnvironment) + + val expectedEvent = SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(serviceException) + ) + assertEquals( + expectedEvent.type, + capturedEvent.captured.type + ) + assertEquals( + serviceException, + ((capturedEvent.captured as SetupTOTPEvent).eventType as SetupTOTPEvent.EventType.ThrowAuthError).exception + ) + } + + @Test + fun `verifyChallengeAnswer send RespondToAuthChallenge on success`() = runTest { + val answer = "123456" + val session = "SESSION" + val username = "USERNAME" + val friendlyDeviceName = "TEST_DEVICE" + coEvery { + cognitoIdentityProviderClientMock.verifySoftwareToken { + this.userCode = answer + this.session = session + this.friendlyDeviceName = friendlyDeviceName + } + }.answers { + VerifySoftwareTokenResponse.invoke { + this.session = session + this.status = VerifySoftwareTokenResponseType.Success + } + } + val expectedEvent = SetupTOTPEvent( + SetupTOTPEvent.EventType.RespondToAuthChallenge(username, session) + ) + + val verifyChallengeAnswerAction = SetupTOTPCognitoActions.verifyChallengeAnswer( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + answer, + username, + session, + friendlyDeviceName + ) + ) + verifyChallengeAnswerAction.execute(dispatcher, authEnvironment) + + assertEquals( + expectedEvent.type, + capturedEvent.captured.type + ) + + assertEquals( + session, + ( + (capturedEvent.captured as SetupTOTPEvent).eventType as SetupTOTPEvent.EventType.RespondToAuthChallenge + ).session + ) + } + + @Test + fun `verifyChallengeAnswer send RespondToAuthChallenge on Error`() = runTest { + val answer = "123456" + val session = "SESSION" + val username = "USERNAME" + val friendlyDeviceName = "TEST_DEVICE" + coEvery { + cognitoIdentityProviderClientMock.verifySoftwareToken { + this.userCode = answer + this.session = session + this.friendlyDeviceName = friendlyDeviceName + } + }.answers { + VerifySoftwareTokenResponse.invoke { + this.session = session + this.status = VerifySoftwareTokenResponseType.Error + } + } + val expectedEvent = SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) + ) + + val verifyChallengeAnswerAction = SetupTOTPCognitoActions.verifyChallengeAnswer( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + answer, + username, + session, + friendlyDeviceName + ) + ) + verifyChallengeAnswerAction.execute(dispatcher, authEnvironment) + + assertEquals( + expectedEvent.type, + capturedEvent.captured.type + ) + + assertEquals( + (expectedEvent.eventType as SetupTOTPEvent.EventType.ThrowAuthError).exception.message, + ( + (capturedEvent.captured as SetupTOTPEvent).eventType as SetupTOTPEvent.EventType.ThrowAuthError + ).exception.message + ) + } + + @Test + fun `verifyChallengeAnswer send RespondToAuthChallenge on exception`() = runTest { + val answer = "123456" + val session = "SESSION" + val username = "USERNAME" + val friendlyDeviceName = "TEST_DEVICE" + val serviceException = CodeMismatchException { + message = "Invalid Code" + } + coEvery { + cognitoIdentityProviderClientMock.verifySoftwareToken { + this.userCode = answer + this.session = session + this.friendlyDeviceName = friendlyDeviceName + } + }.answers { + throw serviceException + } + val expectedEvent = SetupTOTPEvent( + SetupTOTPEvent.EventType.ThrowAuthError(serviceException) + ) + + val verifyChallengeAnswerAction = SetupTOTPCognitoActions.verifyChallengeAnswer( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + answer, + username, + session, + friendlyDeviceName + ) + ) + verifyChallengeAnswerAction.execute(dispatcher, authEnvironment) + + assertEquals( + expectedEvent.type, + capturedEvent.captured.type + ) + + assertEquals( + (expectedEvent.eventType as SetupTOTPEvent.EventType.ThrowAuthError).exception.message, + ( + (capturedEvent.captured as SetupTOTPEvent).eventType as SetupTOTPEvent.EventType.ThrowAuthError + ).exception.message + ) + } +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt index f3415d6609..d3a454353d 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/featuretest/serializers/AuthStatesSerializer.kt @@ -44,13 +44,17 @@ import kotlinx.serialization.modules.contextual @Serializable internal data class AuthStatesProxy( val type: String = "AuthState", - @Contextual @SerialName("AuthenticationState") + @Contextual + @SerialName("AuthenticationState") val authNState: AuthenticationState? = null, - @Contextual @SerialName("AuthorizationState") + @Contextual + @SerialName("AuthorizationState") val authZState: AuthorizationState? = null, - @Contextual @SerialName("SignInState") + @Contextual + @SerialName("SignInState") val signInState: SignInState = SignInState.NotStarted(), - @Contextual @SerialName("SignInChallengeState") + @Contextual + @SerialName("SignInChallengeState") val signInChallengeState: SignInChallengeState? = null, @Contextual val signedInData: SignedInData? = null, @@ -142,7 +146,7 @@ internal data class AuthStatesProxy( amplifyCredential = authState.amplifyCredential ) is AuthorizationState.SigningIn -> AuthStatesProxy( - type = "AuthorizationState.SigningIn", + type = "AuthorizationState.SigningIn" ) is AuthorizationState.SigningOut -> TODO() is AuthorizationState.StoringCredentials -> TODO() @@ -166,6 +170,7 @@ internal data class AuthStatesProxy( is SignInState.SigningInWithHostedUI -> TODO() is SignInState.SigningInWithSRP -> TODO() is SignInState.SigningInWithSRPCustom -> TODO() + is SignInState.ResolvingTOTPSetup -> TODO() } } is SignInChallengeState -> { From 972f6546a7ebc78538399303aef465a06fcf7409 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 1 Aug 2023 09:57:50 -0500 Subject: [PATCH 12/28] fix tests --- .../amplifyframework/rx/RxAuthBindingTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rxbindings/src/test/java/com/amplifyframework/rx/RxAuthBindingTest.java b/rxbindings/src/test/java/com/amplifyframework/rx/RxAuthBindingTest.java index 5191a2b5a4..ad39c202ab 100644 --- a/rxbindings/src/test/java/com/amplifyframework/rx/RxAuthBindingTest.java +++ b/rxbindings/src/test/java/com/amplifyframework/rx/RxAuthBindingTest.java @@ -227,7 +227,7 @@ public void testSignInSucceeds() throws InterruptedException { // Arrange a result on the result consumer AuthCodeDeliveryDetails details = new AuthCodeDeliveryDetails(RandomString.string(), DeliveryMedium.EMAIL); AuthSignInStep step = AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE; - AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details); + AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details, null, null); AuthSignInResult result = new AuthSignInResult(false, nextStep); doAnswer(invocation -> { // 0 = username, 1 = password, 2 = onResult, 3 = onFailure @@ -289,7 +289,7 @@ public void testConfirmSignInSucceeds() throws InterruptedException { // Arrange a successful result. AuthSignInStep step = AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE; AuthCodeDeliveryDetails details = new AuthCodeDeliveryDetails(RandomString.string(), DeliveryMedium.UNKNOWN); - AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details); + AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details, null, null); AuthSignInResult expected = new AuthSignInResult(true, nextStep); doAnswer(invocation -> { // 0 = confirm code, 1 = onResult, 2 = onFailure @@ -351,7 +351,7 @@ public void testSignInWithSocialWebUISucceeds() throws InterruptedException { // Arrange a successful result AuthSignInStep step = AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE; AuthCodeDeliveryDetails details = new AuthCodeDeliveryDetails(RandomString.string(), DeliveryMedium.PHONE); - AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details); + AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details, null, null); AuthSignInResult result = new AuthSignInResult(false, nextStep); doAnswer(invocation -> { // 0 = provider, 1 = activity, 2 = result consumer, 3 = failure consumer @@ -414,7 +414,7 @@ public void testSignInWithWebUISucceeds() throws InterruptedException { // Arrange a result AuthSignInStep step = AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE; AuthCodeDeliveryDetails details = new AuthCodeDeliveryDetails(RandomString.string(), DeliveryMedium.PHONE); - AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details); + AuthNextSignInStep nextStep = new AuthNextSignInStep(step, Collections.emptyMap(), details, null, null); AuthSignInResult result = new AuthSignInResult(false, nextStep); doAnswer(invocation -> { // 0 = activity, 1 = result consumer, 2 = failure consumer @@ -1048,10 +1048,10 @@ public void testDeleteUser() throws InterruptedException { onCompletion.call(); return null; }).when(delegate).deleteUser(anyAction(), anyConsumer()); - + // Act: call the binding TestObserver observer = auth.deleteUser().test(); - + // Assert: Completable completes with success observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); observer.assertNoErrors() @@ -1073,10 +1073,10 @@ public void testDeleteUserFails() throws InterruptedException { onFailure.accept(failure); return null; }).when(delegate).deleteUser(anyAction(), anyConsumer()); - + // Act: call the binding TestObserver observer = auth.deleteUser().test(); - + // Assert: failure is furnished via Rx Completable. observer.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); observer.assertNotComplete() From 597359967ae53c6d41b4528e484f82981c21f8c6 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Wed, 2 Aug 2023 13:12:10 -0500 Subject: [PATCH 13/28] add unit tests --- .../auth/cognito/UserMFAPreference.kt | 2 +- .../cognito/RealAWSCognitoAuthPluginTest.kt | 237 ++++++++++++++++++ 2 files changed, 238 insertions(+), 1 deletion(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt index 3f8144c115..61c629a461 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt @@ -34,7 +34,7 @@ public sealed class MFAPreference { object Enabled : MFAPreference() { override val mfaEnabled: Boolean - get() = false + get() = true override val mfaPreferred: Boolean get() = false } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt index d1053e6c03..e35e8708d9 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPluginTest.kt @@ -16,10 +16,13 @@ package com.amplifyframework.auth.cognito import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.getUser import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AssociateSoftwareTokenResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse @@ -32,17 +35,27 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResendConfirmationCodeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ResendConfirmationCodeResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SetUserMfaPreferenceRequest +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SetUserMfaPreferenceResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.SignUpRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.SignUpResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaNotFoundException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenRequest +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeResponse +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthException import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.SRPHelper @@ -50,6 +63,7 @@ import com.amplifyframework.auth.cognito.options.AWSCognitoAuthResendUserAttribu import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignInOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttributeOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttributesOptions +import com.amplifyframework.auth.cognito.options.AWSCognitoAuthVerifyTOTPSetupOptions import com.amplifyframework.auth.cognito.options.AuthFlowType import com.amplifyframework.auth.cognito.usecases.ResetPasswordUseCase import com.amplifyframework.auth.exceptions.InvalidStateException @@ -1625,4 +1639,227 @@ class RealAWSCognitoAuthPluginTest { "Auth flow types do not match expected" ) } + + @Test + fun `setupTOTP on success`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk>() + val totpSetupDetails = slot() + every { onSuccess.accept(capture(totpSetupDetails)) }.answers { listenLatch.countDown() } + val onError = mockk>() + + val session = "SESSION" + val secretCode = "SECRET_CODE" + coEvery { mockCognitoIPClient.associateSoftwareToken(any()) }.answers { + AssociateSoftwareTokenResponse.invoke { + this.session = session + this.secretCode = secretCode + } + } + + plugin.setUpTOTP(onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(totpSetupDetails.isCaptured) + verify(exactly = 1) { onSuccess.accept(any()) } + assertEquals(secretCode, totpSetupDetails.captured.sharedSecret) + } + + @Test + fun `setupTOTP on error`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + + val listenLatch = CountDownLatch(1) + val onSuccess = mockk>() + val onError = mockk>() + val authException = slot() + every { onError.accept(capture(authException)) }.answers { listenLatch.countDown() } + + val expectedErrorMessage = "Software token MFA not enabled" + coEvery { mockCognitoIPClient.associateSoftwareToken(any()) }.answers { + throw SoftwareTokenMfaNotFoundException.invoke { + message = expectedErrorMessage + } + } + + plugin.setUpTOTP(onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(authException.isCaptured) + verify(exactly = 1) { onError.accept(any()) } + assertEquals(expectedErrorMessage, authException.captured.cause?.message) + } + + @Test + fun `verifyTOTP on success`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + + val session = "SESSION" + val code = "123456" + val friendlyDeviceName = "DEVICE_NAME" + coEvery { + mockCognitoIPClient.verifySoftwareToken( + VerifySoftwareTokenRequest.invoke { + userCode = code + this.friendlyDeviceName = friendlyDeviceName + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + ) + }.answers { + VerifySoftwareTokenResponse.invoke { + status = VerifySoftwareTokenResponseType.Success + } + } + + plugin.verifyTOTPSetup( + code, + AWSCognitoAuthVerifyTOTPSetupOptions.CognitoBuilder().setFriendlyDeviceName(friendlyDeviceName).build(), + onSuccess, + onError + ) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + verify(exactly = 1) { onSuccess.call() } + } + + @Test + fun `verifyTOTP on error`() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + val onError = mockk>() + val authException = slot() + every { onError.accept(capture(authException)) }.answers { listenLatch.countDown() } + + val session = "SESSION" + val code = "123456" + val friendlyDeviceName = "DEVICE_NAME" + val errorMessage = "Invalid code" + coEvery { + mockCognitoIPClient.verifySoftwareToken( + VerifySoftwareTokenRequest.invoke { + userCode = code + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + ) + }.answers { + VerifySoftwareTokenResponse.invoke { + throw CodeMismatchException.invoke { + message = errorMessage + } + } + } + + plugin.verifyTOTPSetup( + code, + AWSCognitoAuthVerifyTOTPSetupOptions.CognitoBuilder().build(), + onSuccess, + onError + ) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(authException.isCaptured) + assertEquals(errorMessage, authException.captured.cause?.message) + verify(exactly = 1) { onError.accept(any()) } + } + + @Test + fun fetchMFAPreferences() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val userMFAPreference = slot() + val onSuccess = mockk>() + every { onSuccess.accept(capture(userMFAPreference)) }.answers { listenLatch.countDown() } + val onError = mockk>() + + coEvery { + mockCognitoIPClient.getUser { + accessToken = credentials.signedInData.cognitoUserPoolTokens.accessToken + } + }.answers { + GetUserResponse.invoke { + userMfaSettingList = listOf("SMS_MFA", "SOFTWARE_TOKEN_MFA") + preferredMfaSetting = "SOFTWARE_TOKEN_MFA" + } + } + plugin.fetchMFAPreference(onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(userMFAPreference.isCaptured) + assertEquals(setOf(MFAType.SMS, MFAType.TOTP), userMFAPreference.captured.enabled) + assertEquals(MFAType.TOTP, userMFAPreference.captured.preferred) + } + + @Test + fun updateMFAPreferences() { + val currentAuthState = mockk { + every { authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + every { authZState } returns AuthorizationState.SessionEstablished(credentials) + } + every { authStateMachine.getCurrentState(captureLambda()) } answers { + lambda<(AuthState) -> Unit>().invoke(currentAuthState) + } + val listenLatch = CountDownLatch(1) + val onSuccess = mockk() + every { onSuccess.call() }.answers { listenLatch.countDown() } + val onError = mockk>() + val setUserMFAPreferenceRequest = slot() + coEvery { mockCognitoIPClient.setUserMfaPreference(capture(setUserMFAPreferenceRequest)) }.answers { + SetUserMfaPreferenceResponse.invoke { + } + } + plugin.updateMFAPreference(MFAPreference.Enabled, MFAPreference.Preferred, onSuccess, onError) + + assertTrue { listenLatch.await(5, TimeUnit.SECONDS) } + assertTrue(setUserMFAPreferenceRequest.isCaptured) + assertEquals( + SmsMfaSettingsType.invoke { + enabled = true + preferredMfa = false + }, + setUserMFAPreferenceRequest.captured.smsMfaSettings + ) + assertEquals( + SoftwareTokenMfaSettingsType.invoke { + enabled = true + preferredMfa = true + }, + setUserMFAPreferenceRequest.captured.softwareTokenMfaSettings + ) + } } From 659db96841d39e98912ed9d65dc69b0bbecd9640 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 4 Aug 2023 15:11:19 -0500 Subject: [PATCH 14/28] throw exception on Error --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 9 ++++++++- .../auth/cognito/actions/SetupTOTPCognitoActions.kt | 8 ++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 409063c200..32e35e3c68 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -35,6 +35,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSet import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateDeviceStatusRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifyUserAttributeRequest import aws.sdk.kotlin.services.cognitoidentityprovider.resendConfirmationCode import aws.sdk.kotlin.services.cognitoidentityprovider.setUserMfaPreference @@ -2243,7 +2244,13 @@ internal class RealAWSCognitoAuthPlugin( this.friendlyDeviceName = friendlyDeviceName this.accessToken = token }?.also { - onSuccess.call() + when (it.status) { + is VerifySoftwareTokenResponseType.Success -> onSuccess.call() + else -> throw ServiceException( + message = "An unknown service error has occurred", + recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION + ) + } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index a44b77aa9f..8e6030e677 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -19,8 +19,10 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType import aws.sdk.kotlin.services.cognitoidentityprovider.respondToAuthChallenge import aws.sdk.kotlin.services.cognitoidentityprovider.verifySoftwareToken +import com.amplifyframework.AmplifyException import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper +import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.statemachine.Action import com.amplifyframework.statemachine.codegen.actions.SetupTOTPActions import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData @@ -75,11 +77,13 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { ) ) } - else -> { SetupTOTPEvent( SetupTOTPEvent.EventType.ThrowAuthError( - Exception("Software token verification failed") + ServiceException( + message = "An unknown service error has occurred", + recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION + ) ) ) } From 1c8eb0e39e392072f5968a8ce77ddffdcc4ac902 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 8 Aug 2023 14:54:25 -0500 Subject: [PATCH 15/28] address PR comments --- .../auth/cognito/actions/SignInCognitoActions.kt | 2 +- .../auth/cognito/helpers/SignInChallengeHelper.kt | 2 +- .../statemachine/codegen/states/SetupTOTPState.kt | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt index 84e2ab747e..2dfe25a605 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCognitoActions.kt @@ -140,7 +140,7 @@ internal object SignInCognitoActions : SignInActions { } override fun initiateTOTPSetupAction(event: SignInEvent.EventType.InitiateTOTPSetup) = - Action("initiateTOTOSetup") { id, dispatcher -> + Action("initiateTOTPSetup") { id, dispatcher -> logger.verbose("$id Starting execution") val evt = SetupTOTPEvent(SetupTOTPEvent.EventType.SetupTOTP(event.signInTOTPSetupData)) logger.verbose("$id Sending event ${evt.type}") diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt index f3090b6caf..b3e1c106bd 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SignInChallengeHelper.kt @@ -205,7 +205,7 @@ internal object SignInChallengeHelper { when (it) { "SMS_MFA" -> result.add(MFAType.SMS) "SOFTWARE_TOKEN_MFA" -> result.add(MFAType.TOTP) - else -> TODO("throw exception") + else -> throw UnknownException(cause = Exception("MFA type not supported.")) } } return result diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index 2411240f6c..0a9f41bb70 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -123,7 +123,6 @@ internal sealed class SetupTOTPState : State { is Error -> when (challengeEvent) { is SetupTOTPEvent.EventType.VerifyChallengeAnswer -> { StateResolution( - // TODO: Fix this Verifying(challengeEvent.answer, "", null), listOf(setupTOTPActions.verifyChallengeAnswer(challengeEvent)) ) From 27fab241ccbb333b6f49b03993a5b1fe46aebd4f Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 8 Aug 2023 15:19:08 -0500 Subject: [PATCH 16/28] add additional params to respondToAuthChallenge --- .../auth/cognito/actions/SetupTOTPCognitoActions.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index 8e6030e677..c09a96660c 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -29,6 +29,7 @@ import com.amplifyframework.statemachine.codegen.data.SignInTOTPSetupData import com.amplifyframework.statemachine.codegen.events.SetupTOTPEvent internal object SetupTOTPCognitoActions : SetupTOTPActions { + private const val KEY_DEVICE_KEY = "DEVICE_KEY" override fun initiateTOTPSetup(eventType: SetupTOTPEvent.EventType.SetupTOTP): Action = Action( "InitiateTOTPSetup" ) { id, dispatcher -> @@ -107,13 +108,19 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { logger.verbose("$id Starting execution") val evt = try { val challengeResponses = mutableMapOf() - challengeResponses["USERNAME"] = eventType.username + val deviceMetadata = getDeviceMetadata(eventType.username) + deviceMetadata?.deviceKey?.let { challengeResponses[KEY_DEVICE_KEY] = it } + val encodedContextData = getUserContextData(eventType.username) + val pinpointEndpointId = getPinpointEndpointId() + val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { this.session = eventType.session this.challengeResponses = challengeResponses challengeName = ChallengeNameType.MfaSetup clientId = configuration.userPool?.appClient + pinpointEndpointId?.let { analyticsMetadata { analyticsEndpointId = it } } + encodedContextData?.let { this.userContextData { encodedData = it } } } response?.let { From be4e61273efd91236f324596c32b08c42b805855 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Thu, 10 Aug 2023 16:35:04 -0500 Subject: [PATCH 17/28] add integ tests --- aws-auth-cognito/build.gradle.kts | 1 + .../cognito/AWSCognitoAuthPluginTOTPTests.kt | 130 ++++++++++++++++++ .../res/raw/amplifyconfiguration_totp.json | 51 +++++++ .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 63 +++++++-- .../actions/SetupTOTPCognitoActions.kt | 36 ++++- .../codegen/events/SetupTOTPEvent.kt | 2 +- .../codegen/states/SetupTOTPState.kt | 17 ++- .../actions/SetupTOTPCognitoActionsTest.kt | 10 +- settings.gradle.kts | 2 + .../testutils/sync/SynchronousAuth.java | 7 + 10 files changed, 289 insertions(+), 30 deletions(-) create mode 100644 aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt create mode 100644 aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json diff --git a/aws-auth-cognito/build.gradle.kts b/aws-auth-cognito/build.gradle.kts index a484e708f6..cfca8b09a0 100644 --- a/aws-auth-cognito/build.gradle.kts +++ b/aws-auth-cognito/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { androidTestImplementation(testDependency.androidx.test.runner) androidTestImplementation(testDependency.androidx.test.junit) androidTestImplementation(testDependency.kotlin.test.coroutines) + androidTestImplementation(testDependency.totp) androidTestImplementation(project(":aws-api")) androidTestImplementation(project(":testutils")) } diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt new file mode 100644 index 0000000000..187d17fe63 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amplifyframework.auth.cognito + +import android.content.Context +import android.util.Log +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.cognito.test.R +import com.amplifyframework.auth.options.AuthSignUpOptions +import com.amplifyframework.auth.result.step.AuthSignInStep +import com.amplifyframework.core.Amplify +import com.amplifyframework.core.AmplifyConfiguration +import com.amplifyframework.core.category.CategoryConfiguration +import com.amplifyframework.core.category.CategoryType +import com.amplifyframework.logging.AndroidLoggingPlugin +import com.amplifyframework.logging.LogLevel +import com.amplifyframework.testutils.Sleep +import com.amplifyframework.testutils.sync.SynchronousAuth +import dev.robinohs.totpkt.otp.totp.TotpGenerator +import dev.robinohs.totpkt.otp.totp.timesupport.generateCode +import java.util.Random +import java.util.UUID +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AWSCognitoAuthPluginTOTPTests { + + private lateinit var authPlugin: AWSCognitoAuthPlugin + private lateinit var synchronousAuth: SynchronousAuth + private val password = UUID.randomUUID().toString() + private val userName = "testUser${Random().nextInt()}" + private val email = "$userName@testdomain.com" + + @Before + fun setup() { + val context = ApplicationProvider.getApplicationContext() + Amplify.addPlugin(AndroidLoggingPlugin(LogLevel.VERBOSE)) + val config = AmplifyConfiguration.fromConfigFile(context, R.raw.amplifyconfiguration_totp) + val authConfig: CategoryConfiguration = config.forCategoryType(CategoryType.AUTH) + val authConfigJson = authConfig.getPluginConfig("awsCognitoAuthPlugin") + authPlugin = AWSCognitoAuthPlugin() + authPlugin.configure(authConfigJson, context) + synchronousAuth = SynchronousAuth.delegatingTo(authPlugin) + signUpNewUser(userName, password, email) + synchronousAuth.signOut() + } + + @After + fun tearDown() { + synchronousAuth.deleteUser() + } + + @Test + fun mfa_setup() { + val result = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) + val otp = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + System.currentTimeMillis(), + ) + synchronousAuth.confirmSignIn(otp) + val currentUser = synchronousAuth.currentUser + Assert.assertEquals(userName.lowercase(), currentUser.username) + } + + @Test + fun mfasetup_with_incorrect_otp() { + val result = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) + try { + synchronousAuth.confirmSignIn("123456") + } catch (e: Exception) { + Assert.assertEquals("Code mismatch", e.cause?.message) + val otp = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + System.currentTimeMillis(), + ) + synchronousAuth.confirmSignIn(otp) + val currentUser = synchronousAuth.currentUser + Assert.assertEquals(userName.lowercase(), currentUser.username) + } + } + + @Test + fun signIn_with_totp_after_mfa_setup() { + val result = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) + val otp = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + ) + Log.d("signIn_with_totp_after_mfa_setup", "otp is $otp") + synchronousAuth.confirmSignIn(otp) + synchronousAuth.signOut() + Sleep.milliseconds(30 * 1000) + val signInResult = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, signInResult.nextStep.signInStep) + val otpCode = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + ) + Log.d("signIn_with_totp_after_mfa_setup", "otp is $otp") + synchronousAuth.confirmSignIn(otpCode) + val currentUser = synchronousAuth.currentUser + Assert.assertEquals(userName.lowercase(), currentUser.username) + } + + private fun signUpNewUser(userName: String, password: String, email: String) { + val options = AuthSignUpOptions.builder() + .userAttribute(AuthUserAttributeKey.email(), email) + .build() + synchronousAuth.signUp(userName, password, options) + } +} diff --git a/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json b/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json new file mode 100644 index 0000000000..544873d187 --- /dev/null +++ b/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json @@ -0,0 +1,51 @@ +{ + "UserAgent": "aws-amplify-cli/2.0", + "Version": "1.0", + "auth": { + "plugins": { + "awsCognitoAuthPlugin": { + "UserAgent": "aws-amplify-cli/0.1.0", + "Version": "0.1.0", + "IdentityManager": { + "Default": {} + }, + "CredentialsProvider": { + "CognitoIdentity": { + "Default": { + "PoolId": "us-east-1:b192a6bb-1768-4d0b-be45-fee649f0e9b6", + "Region": "us-east-1" + } + } + }, + "CognitoUserPool": { + "Default": { + "PoolId": "us-east-1_4obUCtJG3", + "AppClientId": "7pb63v151de2frqteb8gdf832a", + "Region": "us-east-1" + } + }, + "Auth": { + "Default": { + "authenticationFlowType": "USER_SRP_AUTH", + "socialProviders": [], + "usernameAttributes": [], + "signupAttributes": [ + "EMAIL" + ], + "passwordProtectionSettings": { + "passwordPolicyMinLength": 8, + "passwordPolicyCharacters": [] + }, + "mfaConfiguration": "ON", + "mfaTypes": [ + "TOTP" + ], + "verificationMechanisms": [ + "EMAIL" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 32e35e3c68..0015de1285 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -513,7 +513,8 @@ internal class RealAWSCognitoAuthPlugin( ) // Continue sign in is AuthenticationState.SignedOut, - is AuthenticationState.Configured -> { + is AuthenticationState.Configured + -> { _signIn(username, password, signInOptions, onSuccess, onError) } is AuthenticationState.SignedIn -> onError.accept(SignedInException()) @@ -657,7 +658,7 @@ internal class RealAWSCognitoAuthPlugin( } if (signInState is SignInState.ResolvingTOTPSetup) { when (signInState.setupTOTPState) { - is SetupTOTPState.WaitingForAnswer -> { + is SetupTOTPState.WaitingForAnswer, is SetupTOTPState.Error -> { _confirmSignIn(signInState, challengeResponse, options, onSuccess, onError) } else -> onError.accept(InvalidStateException()) @@ -751,6 +752,19 @@ internal class RealAWSCognitoAuthPlugin( totpSetupState.hasNewResponse = false } + signInState is SignInState.ResolvingTOTPSetup && + totpSetupState is SetupTOTPState.Error && + totpSetupState.hasNewResponse -> { + authStateMachine.cancel(token) + onError.accept( + CognitoAuthExceptionConverter.lookup( + totpSetupState.exception, + "Confirm Sign in failed." + ) + ) + totpSetupState.hasNewResponse = false + } + signInState is SignInState.ResolvingChallenge && signInState.challengeState is SignInChallengeState.Error && (signInState.challengeState as SignInChallengeState.Error).hasNewResponse -> { @@ -781,17 +795,40 @@ internal class RealAWSCognitoAuthPlugin( } is SignInState.ResolvingTOTPSetup -> { - val setupData = - (signInState.setupTOTPState as SetupTOTPState.WaitingForAnswer).signInTOTPSetupData - val event = SetupTOTPEvent( - SetupTOTPEvent.EventType.VerifyChallengeAnswer( - challengeResponse, - setupData.username, - setupData.session, - awsCognitoConfirmSignInOptions?.friendlyDeviceName - ) - ) - authStateMachine.send(event) + when (signInState.setupTOTPState) { + is SetupTOTPState.WaitingForAnswer -> { + val setupData = + (signInState.setupTOTPState as SetupTOTPState.WaitingForAnswer).signInTOTPSetupData + + val event = SetupTOTPEvent( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + challengeResponse, + setupData.username, + setupData.session, + awsCognitoConfirmSignInOptions?.friendlyDeviceName + ) + ) + authStateMachine.send(event) + } + is SetupTOTPState.Error -> { + val username = + (signInState.setupTOTPState as SetupTOTPState.Error).username + val session = + (signInState.setupTOTPState as SetupTOTPState.Error).session + + val event = SetupTOTPEvent( + SetupTOTPEvent.EventType.VerifyChallengeAnswer( + challengeResponse, + username, + session, + awsCognitoConfirmSignInOptions?.friendlyDeviceName + ) + ) + authStateMachine.send(event) + } + + else -> onError.accept(InvalidStateException()) + } } else -> onError.accept(InvalidStateException()) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt index c09a96660c..7c00b0892d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActions.kt @@ -45,11 +45,19 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { ) ) } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token setup failed")) + SetupTOTPEvent.EventType.ThrowAuthError( + Exception("Software token setup failed"), + eventType.totpSetupDetails.username, + eventType.totpSetupDetails.session + ) ) } catch (e: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(e) + SetupTOTPEvent.EventType.ThrowAuthError( + e, + eventType.totpSetupDetails.username, + eventType.totpSetupDetails.session + ) ) } logger.verbose("$id Sending event ${evt.type}") @@ -84,17 +92,27 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { ServiceException( message = "An unknown service error has occurred", recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION - ) + ), + eventType.username, + eventType.session ) ) } } } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) + SetupTOTPEvent.EventType.ThrowAuthError( + Exception("Software token verification failed"), + eventType.username, + eventType.session + ) ) } catch (exception: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(exception) + SetupTOTPEvent.EventType.ThrowAuthError( + exception, + eventType.username, + eventType.session + ) ) } logger.verbose("$id Sending event ${evt.type}") @@ -132,11 +150,15 @@ internal object SetupTOTPCognitoActions : SetupTOTPActions { authenticationResult = response.authenticationResult ) } ?: SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) + SetupTOTPEvent.EventType.ThrowAuthError( + Exception("Software token verification failed"), + eventType.username, + eventType.session + ) ) } catch (exception: Exception) { SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(exception) + SetupTOTPEvent.EventType.ThrowAuthError(exception, eventType.username, eventType.session) ) } dispatcher.send(evt) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt index 6a2bbda8b8..9bf7adefae 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/events/SetupTOTPEvent.kt @@ -24,7 +24,7 @@ internal class SetupTOTPEvent(val eventType: EventType, override val time: Date? sealed class EventType { data class SetupTOTP(val totpSetupDetails: SignInTOTPSetupData) : EventType() data class WaitForAnswer(val totpSetupDetails: SignInTOTPSetupData) : EventType() - data class ThrowAuthError(val exception: Exception) : EventType() + data class ThrowAuthError(val exception: Exception, val username: String, val session: String?) : EventType() data class VerifyChallengeAnswer( val answer: String, val username: String, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt index 0a9f41bb70..abc12255c9 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/states/SetupTOTPState.kt @@ -32,7 +32,12 @@ internal sealed class SetupTOTPState : State { data class Verifying(val code: String, val username: String, val session: String?) : SetupTOTPState() data class RespondingToAuthChallenge(val username: String, val session: String?) : SetupTOTPState() data class Success(val id: String = "") : SetupTOTPState() - data class Error(val exception: Exception) : SetupTOTPState() + data class Error( + val exception: Exception, + val username: String, + val session: String?, + var hasNewResponse: Boolean = false + ) : SetupTOTPState() class Resolver(private val setupTOTPActions: SetupTOTPActions) : StateMachineResolver { override val defaultState = NotStarted("default") @@ -50,7 +55,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception) + Error(challengeEvent.exception, challengeEvent.username, challengeEvent.session) ) else -> defaultResolution @@ -62,7 +67,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception) + Error(challengeEvent.exception, challengeEvent.username, challengeEvent.session) ) else -> defaultResolution @@ -81,7 +86,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception) + Error(challengeEvent.exception, challengeEvent.username, challengeEvent.session) ) else -> defaultResolution @@ -100,7 +105,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception) + Error(challengeEvent.exception, challengeEvent.username, challengeEvent.session, true) ) else -> defaultResolution @@ -114,7 +119,7 @@ internal sealed class SetupTOTPState : State { } is SetupTOTPEvent.EventType.ThrowAuthError -> StateResolution( - Error(challengeEvent.exception) + Error(challengeEvent.exception, challengeEvent.username, challengeEvent.session) ) else -> defaultResolution diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt index 334e5be4f7..d45ac34f64 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SetupTOTPCognitoActionsTest.kt @@ -133,7 +133,7 @@ class SetupTOTPCognitoActionsTest { initiateAction.execute(dispatcher, authEnvironment) val expectedEvent = SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(serviceException) + SetupTOTPEvent.EventType.ThrowAuthError(serviceException, "USERNAME", "SESSION") ) assertEquals( expectedEvent.type, @@ -209,7 +209,11 @@ class SetupTOTPCognitoActionsTest { } } val expectedEvent = SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(Exception("Software token verification failed")) + SetupTOTPEvent.EventType.ThrowAuthError( + Exception("An unknown service error has occurred"), + "USERNAME", + "SESSION" + ) ) val verifyChallengeAnswerAction = SetupTOTPCognitoActions.verifyChallengeAnswer( @@ -254,7 +258,7 @@ class SetupTOTPCognitoActionsTest { throw serviceException } val expectedEvent = SetupTOTPEvent( - SetupTOTPEvent.EventType.ThrowAuthError(serviceException) + SetupTOTPEvent.EventType.ThrowAuthError(serviceException, "USERNAME", "SESSION") ) val verifyChallengeAnswerAction = SetupTOTPCognitoActions.verifyChallengeAnswer( diff --git a/settings.gradle.kts b/settings.gradle.kts index 46c6b09236..b8d79ad297 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -65,6 +65,8 @@ dependencyResolutionManagement { // AWS library("aws-sdk-core", "com.amazonaws:aws-android-sdk-core:2.62.2") + + library("totp", "dev.robinohs:totp-kt:1.0.1") } // Library dependencies create("dependency") { diff --git a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java index 442ee71436..97c2201d36 100644 --- a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java +++ b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java @@ -29,6 +29,7 @@ import com.amplifyframework.auth.AuthPlugin; import com.amplifyframework.auth.AuthProvider; import com.amplifyframework.auth.AuthSession; +import com.amplifyframework.auth.AuthUser; import com.amplifyframework.auth.AuthUserAttribute; import com.amplifyframework.auth.AuthUserAttributeKey; import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions; @@ -599,4 +600,10 @@ public void deleteUser() throws AuthException { asyncDelegate.deleteUser(() -> onResult.accept(VoidResult.instance()), onError) ); } + + public AuthUser getCurrentUser() throws AuthException { + return Await.result(AUTH_OPERATION_TIMEOUT_MS, (onResult, onError) -> + asyncDelegate.getCurrentUser(onResult, onError) + ); + } } From ac8f1db82e2e640c3b493135cfee8f729e42b611 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 07:17:01 -0500 Subject: [PATCH 18/28] delete config file --- .../res/raw/amplifyconfiguration_totp.json | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json diff --git a/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json b/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json deleted file mode 100644 index 544873d187..0000000000 --- a/aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "UserAgent": "aws-amplify-cli/2.0", - "Version": "1.0", - "auth": { - "plugins": { - "awsCognitoAuthPlugin": { - "UserAgent": "aws-amplify-cli/0.1.0", - "Version": "0.1.0", - "IdentityManager": { - "Default": {} - }, - "CredentialsProvider": { - "CognitoIdentity": { - "Default": { - "PoolId": "us-east-1:b192a6bb-1768-4d0b-be45-fee649f0e9b6", - "Region": "us-east-1" - } - } - }, - "CognitoUserPool": { - "Default": { - "PoolId": "us-east-1_4obUCtJG3", - "AppClientId": "7pb63v151de2frqteb8gdf832a", - "Region": "us-east-1" - } - }, - "Auth": { - "Default": { - "authenticationFlowType": "USER_SRP_AUTH", - "socialProviders": [], - "usernameAttributes": [], - "signupAttributes": [ - "EMAIL" - ], - "passwordProtectionSettings": { - "passwordPolicyMinLength": 8, - "passwordPolicyCharacters": [] - }, - "mfaConfiguration": "ON", - "mfaTypes": [ - "TOTP" - ], - "verificationMechanisms": [ - "EMAIL" - ] - } - } - } - } - } -} \ No newline at end of file From 5d18c63ebe6107feb03fae4908ce38d7a233b8ed Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 09:46:52 -0500 Subject: [PATCH 19/28] add test for mfa selection --- .../cognito/AWSCognitoAuthPluginTOTPTests.kt | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt index 187d17fe63..204b47bae3 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt @@ -18,7 +18,9 @@ import android.content.Context import android.util.Log import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.cognito.test.R import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.result.step.AuthSignInStep @@ -34,6 +36,8 @@ import dev.robinohs.totpkt.otp.totp.TotpGenerator import dev.robinohs.totpkt.otp.totp.timesupport.generateCode import java.util.Random import java.util.UUID +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import org.junit.After import org.junit.Assert import org.junit.Before @@ -74,7 +78,7 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) val otp = TotpGenerator().generateCode( result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), - System.currentTimeMillis(), + System.currentTimeMillis() ) synchronousAuth.confirmSignIn(otp) val currentUser = synchronousAuth.currentUser @@ -91,7 +95,7 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals("Code mismatch", e.cause?.message) val otp = TotpGenerator().generateCode( result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), - System.currentTimeMillis(), + System.currentTimeMillis() ) synchronousAuth.confirmSignIn(otp) val currentUser = synchronousAuth.currentUser @@ -104,16 +108,15 @@ class AWSCognitoAuthPluginTOTPTests { val result = synchronousAuth.signIn(userName, password) Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) val otp = TotpGenerator().generateCode( - result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() ) - Log.d("signIn_with_totp_after_mfa_setup", "otp is $otp") synchronousAuth.confirmSignIn(otp) synchronousAuth.signOut() Sleep.milliseconds(30 * 1000) val signInResult = synchronousAuth.signIn(userName, password) Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, signInResult.nextStep.signInStep) val otpCode = TotpGenerator().generateCode( - result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() ) Log.d("signIn_with_totp_after_mfa_setup", "otp is $otp") synchronousAuth.confirmSignIn(otpCode) @@ -121,10 +124,43 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(userName.lowercase(), currentUser.username) } + @Test + fun select_mfa_type() { + val result = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP, result.nextStep.signInStep) + val otp = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() + ) + synchronousAuth.confirmSignIn(otp) + synchronousAuth.updateUserAttribute(AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+19876543210")) + updateMFAPreference(MFAPreference.Enabled, MFAPreference.Enabled) + synchronousAuth.signOut() + val signInResult = synchronousAuth.signIn(userName, password) + Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION, signInResult.nextStep.signInStep) + val totpSignInResult = synchronousAuth.confirmSignIn(MFAType.TOTP.value) + Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, totpSignInResult.nextStep.signInStep) + Sleep.milliseconds(30 * 1000) + val otpCode = TotpGenerator().generateCode( + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() + ) + synchronousAuth.confirmSignIn(otpCode) + val currentUser = synchronousAuth.currentUser + Assert.assertEquals(userName.lowercase(), currentUser.username) + } + private fun signUpNewUser(userName: String, password: String, email: String) { val options = AuthSignUpOptions.builder() - .userAttribute(AuthUserAttributeKey.email(), email) - .build() + .userAttributes( + listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), email) + ) + ).build() synchronousAuth.signUp(userName, password, options) } + + private fun updateMFAPreference(sms: MFAPreference, totp: MFAPreference) { + val latch = CountDownLatch(1) + authPlugin.updateMFAPreference(sms, totp, { latch.countDown() }, { latch.countDown() }) + latch.await(5, TimeUnit.SECONDS) + } } From 8d4f6477f2fce1faf52cec9e97564105b575b56a Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 10:18:05 -0500 Subject: [PATCH 20/28] add totp config to the list to pull from S3 --- scripts/pull_backend_config_from_s3 | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/pull_backend_config_from_s3 b/scripts/pull_backend_config_from_s3 index c3d2985b63..e95fbcb103 100755 --- a/scripts/pull_backend_config_from_s3 +++ b/scripts/pull_backend_config_from_s3 @@ -53,6 +53,7 @@ readonly config_files=( # Auth "aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration.json" + "aws-auth-cognito/src/androidTest/res/raw/amplifyconfiguration_totp.json" "aws-auth-cognito/src/androidTest/res/raw/awsconfiguration.json" "aws-auth-cognito/src/androidTest/res/raw/credentials.json" ) From 0abbbb2b153a75515f15fbb26d5c8244c883b572 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 11:25:14 -0500 Subject: [PATCH 21/28] add else block --- .../auth/cognito/RealAWSCognitoAuthPlugin.kt | 95 ++++++++++--------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt index 0015de1285..7780b6f5b7 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt @@ -655,14 +655,15 @@ internal class RealAWSCognitoAuthPlugin( onError.accept(InvalidStateException()) } } - } - if (signInState is SignInState.ResolvingTOTPSetup) { + } else if (signInState is SignInState.ResolvingTOTPSetup) { when (signInState.setupTOTPState) { is SetupTOTPState.WaitingForAnswer, is SetupTOTPState.Error -> { _confirmSignIn(signInState, challengeResponse, options, onSuccess, onError) } else -> onError.accept(InvalidStateException()) } + } else { + onError.accept(InvalidStateException()) } } } @@ -1475,8 +1476,8 @@ internal class RealAWSCognitoAuthPlugin( try { authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) + changePasswordRequest + ) onSuccess.call() } catch (e: Exception) { onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) @@ -1602,8 +1603,8 @@ internal class RealAWSCognitoAuthPlugin( } val userAttributeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.updateUserAttributes( - userAttributesRequest - ) + userAttributesRequest + ) continuation.resume( getUpdateUserAttributeResult(userAttributeResponse, userAttributes) @@ -1689,8 +1690,8 @@ internal class RealAWSCognitoAuthPlugin( val getUserAttributeVerificationCodeResponse = authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUserAttributeVerificationCode( - getUserAttributeVerificationCodeRequest - ) + getUserAttributeVerificationCodeRequest + ) getUserAttributeVerificationCodeResponse?.codeDeliveryDetails?.let { val codeDeliveryDetails = it @@ -1756,8 +1757,8 @@ internal class RealAWSCognitoAuthPlugin( } authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifyUserAttribute( - verifyUserAttributeRequest - ) + verifyUserAttributeRequest + ) onSuccess.call() } ?: onError.accept(InvalidUserPoolConfigurationException()) } catch (e: Exception) { @@ -2097,10 +2098,10 @@ internal class RealAWSCognitoAuthPlugin( authNState is AuthenticationState.FederatedToIdentityPool && authZState is AuthorizationState.SessionEstablished ) || ( - authZState is AuthorizationState.Error && - authZState.exception is SessionError && - authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated - ) -> { + authZState is AuthorizationState.Error && + authZState.exception is SessionError && + authZState.exception.amplifyCredential is AmplifyCredential.IdentityPoolFederated + ) -> { val event = AuthenticationEvent(AuthenticationEvent.EventType.ClearFederationToIdentityPool()) authStateMachine.send(event) _clearFederationToIdentityPool(onSuccess, onError) @@ -2123,17 +2124,17 @@ internal class RealAWSCognitoAuthPlugin( SessionHelper.getUsername(token)?.let { username -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.associateSoftwareToken { - this.accessToken = token - }?.also { response -> - response.secretCode?.let { secret -> - onSuccess.accept( - TOTPSetupDetails( - secret, - username - ) + this.accessToken = token + }?.also { response -> + response.secretCode?.let { secret -> + onSuccess.accept( + TOTPSetupDetails( + secret, + username ) - } + ) } + } } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { @@ -2183,21 +2184,21 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.getUser { - this.accessToken = token - }?.also { response -> - var enabledSet: MutableSet? = null - var preferred: MFAType? = null - if (!response.userMfaSettingList.isNullOrEmpty()) { - enabledSet = mutableSetOf() - response.userMfaSettingList?.forEach { mfaType -> - enabledSet.add(MFAType.toMFAType(mfaType)) - } - } - response.preferredMfaSetting?.let { preferredMFA -> - preferred = MFAType.toMFAType(preferredMFA) + this.accessToken = token + }?.also { response -> + var enabledSet: MutableSet? = null + var preferred: MFAType? = null + if (!response.userMfaSettingList.isNullOrEmpty()) { + enabledSet = mutableSetOf() + response.userMfaSettingList?.forEach { mfaType -> + enabledSet.add(MFAType.toMFAType(mfaType)) } - onSuccess.accept(UserMFAPreference(enabledSet, preferred)) } + response.preferredMfaSetting?.let { preferredMFA -> + preferred = MFAType.toMFAType(preferredMFA) + } + onSuccess.accept(UserMFAPreference(enabledSet, preferred)) + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( @@ -2277,18 +2278,18 @@ internal class RealAWSCognitoAuthPlugin( accessToken?.let { token -> authEnvironment.cognitoAuthService .cognitoIdentityProviderClient?.verifySoftwareToken { - this.userCode = code - this.friendlyDeviceName = friendlyDeviceName - this.accessToken = token - }?.also { - when (it.status) { - is VerifySoftwareTokenResponseType.Success -> onSuccess.call() - else -> throw ServiceException( - message = "An unknown service error has occurred", - recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION - ) - } + this.userCode = code + this.friendlyDeviceName = friendlyDeviceName + this.accessToken = token + }?.also { + when (it.status) { + is VerifySoftwareTokenResponseType.Success -> onSuccess.call() + else -> throw ServiceException( + message = "An unknown service error has occurred", + recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION + ) } + } } ?: onError.accept(SignedOutException()) } catch (error: Exception) { onError.accept( From 1c50c66dbf13d8cf3966341b6417066004bf0511 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 11:59:45 -0500 Subject: [PATCH 22/28] add documentation --- .../com/amplifyframework/testutils/sync/SynchronousAuth.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java index 97c2201d36..158a0703aa 100644 --- a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java +++ b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java @@ -601,6 +601,11 @@ public void deleteUser() throws AuthException { ); } + /** + * Get the current signed in user. + * @return current autherticated user + * @throws AuthException exception + */ public AuthUser getCurrentUser() throws AuthException { return Await.result(AUTH_OPERATION_TIMEOUT_MS, (onResult, onError) -> asyncDelegate.getCurrentUser(onResult, onError) From 36e07e831887800d40950a360ee7d87b8cfbb1a0 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 12:00:12 -0500 Subject: [PATCH 23/28] fox type --- .../com/amplifyframework/testutils/sync/SynchronousAuth.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java index 158a0703aa..a4a242eeb7 100644 --- a/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java +++ b/testutils/src/main/java/com/amplifyframework/testutils/sync/SynchronousAuth.java @@ -603,7 +603,7 @@ public void deleteUser() throws AuthException { /** * Get the current signed in user. - * @return current autherticated user + * @return current authenticated user * @throws AuthException exception */ public AuthUser getCurrentUser() throws AuthException { From 9d54388baf61c63d1141705d2ea124dc260675ca Mon Sep 17 00:00:00 2001 From: sdhuka Date: Fri, 11 Aug 2023 12:24:18 -0500 Subject: [PATCH 24/28] remove sleep and add comments --- .../cognito/AWSCognitoAuthPluginTOTPTests.kt | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt index 204b47bae3..1d40fd3fd4 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt @@ -15,7 +15,6 @@ package com.amplifyframework.auth.cognito import android.content.Context -import android.util.Log import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amplifyframework.auth.AuthUserAttribute @@ -30,7 +29,6 @@ import com.amplifyframework.core.category.CategoryConfiguration import com.amplifyframework.core.category.CategoryType import com.amplifyframework.logging.AndroidLoggingPlugin import com.amplifyframework.logging.LogLevel -import com.amplifyframework.testutils.Sleep import com.amplifyframework.testutils.sync.SynchronousAuth import dev.robinohs.totpkt.otp.totp.TotpGenerator import dev.robinohs.totpkt.otp.totp.timesupport.generateCode @@ -72,6 +70,9 @@ class AWSCognitoAuthPluginTOTPTests { synchronousAuth.deleteUser() } + /* + * This test signs up a new user and goes thru successful MFA Setup process. + * */ @Test fun mfa_setup() { val result = synchronousAuth.signIn(userName, password) @@ -85,6 +86,10 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(userName.lowercase(), currentUser.username) } + /* + * This test signs up a new user, enter incorrect MFA code during verification and + * then enter correct OTP code to successfully set TOTP MFA. + * */ @Test fun mfasetup_with_incorrect_otp() { val result = synchronousAuth.signIn(userName, password) @@ -103,6 +108,9 @@ class AWSCognitoAuthPluginTOTPTests { } } + /* + * This test signs up a new user, successfully setup MFA, sign-out and then goes thru sign-in with TOTP. + * */ @Test fun signIn_with_totp_after_mfa_setup() { val result = synchronousAuth.signIn(userName, password) @@ -112,18 +120,23 @@ class AWSCognitoAuthPluginTOTPTests { ) synchronousAuth.confirmSignIn(otp) synchronousAuth.signOut() - Sleep.milliseconds(30 * 1000) + val signInResult = synchronousAuth.signIn(userName, password) Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, signInResult.nextStep.signInStep) val otpCode = TotpGenerator().generateCode( - result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code ) - Log.d("signIn_with_totp_after_mfa_setup", "otp is $otp") synchronousAuth.confirmSignIn(otpCode) val currentUser = synchronousAuth.currentUser Assert.assertEquals(userName.lowercase(), currentUser.username) } + /* + * This test signs up a new user, successfully setup MFA, update user attribute to add phone number, + * sign-out the user, goes thru MFA selection flow during sign-in, select TOTP MFA type, + * successfully sign-in using TOTP + * */ @Test fun select_mfa_type() { val result = synchronousAuth.signIn(userName, password) @@ -139,9 +152,9 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION, signInResult.nextStep.signInStep) val totpSignInResult = synchronousAuth.confirmSignIn(MFAType.TOTP.value) Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, totpSignInResult.nextStep.signInStep) - Sleep.milliseconds(30 * 1000) val otpCode = TotpGenerator().generateCode( - result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray() + result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), + System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code ) synchronousAuth.confirmSignIn(otpCode) val currentUser = synchronousAuth.currentUser From aa2195981cc7e53f8cacaba92be08e22e2772a13 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 15 Aug 2023 11:23:14 -0500 Subject: [PATCH 25/28] implement getSetupURI api --- core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt index aae808ea13..4caa844247 100644 --- a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt +++ b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt @@ -24,6 +24,6 @@ data class TOTPSetupDetails( issuer: String, accountName: String = username ): Uri { - TODO() + return Uri.parse("otpauth://totp/cognito%3A$accountName?secret=$sharedSecret&issuer=$issuer") } } From 239411baad26f5e992cfe002828742d479af42dc Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 15 Aug 2023 11:31:54 -0500 Subject: [PATCH 26/28] rename parameter --- core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt index 4caa844247..4aca076501 100644 --- a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt +++ b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt @@ -21,9 +21,9 @@ data class TOTPSetupDetails( val username: String ) { fun getSetupURI( - issuer: String, + appName: String, accountName: String = username ): Uri { - return Uri.parse("otpauth://totp/cognito%3A$accountName?secret=$sharedSecret&issuer=$issuer") + return Uri.parse("otpauth://totp/cognito%3A$accountName?secret=$sharedSecret&issuer=$appName") } } From e67e4274ba74c470c3e21f5580f4a85120447713 Mon Sep 17 00:00:00 2001 From: sdhuka Date: Tue, 15 Aug 2023 12:19:13 -0500 Subject: [PATCH 27/28] fix uri --- core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt index 4aca076501..a9d77de1f1 100644 --- a/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt +++ b/core/src/main/java/com/amplifyframework/TOTPSetupDetails.kt @@ -24,6 +24,6 @@ data class TOTPSetupDetails( appName: String, accountName: String = username ): Uri { - return Uri.parse("otpauth://totp/cognito%3A$accountName?secret=$sharedSecret&issuer=$appName") + return Uri.parse("otpauth://totp/$appName:$accountName?secret=$sharedSecret&issuer=$appName") } } From aad07b8a729cdc2b31671ec98eb3bda5ca4a49ed Mon Sep 17 00:00:00 2001 From: sdhuka Date: Wed, 16 Aug 2023 09:39:39 -0500 Subject: [PATCH 28/28] add rxjava and kotlin bindings --- .../cognito/AWSCognitoAuthPluginTOTPTests.kt | 4 +-- .../com/amplifyframework/kotlin/auth/Auth.kt | 18 +++++++++++++ .../kotlin/auth/KotlinAuthFacade.kt | 22 ++++++++++++++++ .../options/AuthVerifyTOTPSetupOptions.java | 21 +++++++++++++++ .../amplifyframework/rx/RxAuthBinding.java | 19 +++++++++++++- .../rx/RxAuthCategoryBehavior.java | 26 +++++++++++++++++++ 6 files changed, 107 insertions(+), 3 deletions(-) diff --git a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt index 1d40fd3fd4..3955c18580 100644 --- a/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt +++ b/aws-auth-cognito/src/androidTest/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPluginTOTPTests.kt @@ -125,7 +125,7 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, signInResult.nextStep.signInStep) val otpCode = TotpGenerator().generateCode( result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), - System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code + System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code ) synchronousAuth.confirmSignIn(otpCode) val currentUser = synchronousAuth.currentUser @@ -154,7 +154,7 @@ class AWSCognitoAuthPluginTOTPTests { Assert.assertEquals(AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE, totpSignInResult.nextStep.signInStep) val otpCode = TotpGenerator().generateCode( result.nextStep.totpSetupDetails!!.sharedSecret.toByteArray(), - System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code + System.currentTimeMillis() + 30 * 1000 // 30 sec is added to generate new OTP code ) synchronousAuth.confirmSignIn(otpCode) val currentUser = synchronousAuth.currentUser diff --git a/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/Auth.kt b/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/Auth.kt index a6e80ef849..60d31f8489 100644 --- a/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/Auth.kt +++ b/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/Auth.kt @@ -17,6 +17,7 @@ package com.amplifyframework.kotlin.auth import android.app.Activity import android.content.Intent +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthDevice import com.amplifyframework.auth.AuthException @@ -37,6 +38,7 @@ import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult @@ -327,4 +329,20 @@ interface Auth { */ @Throws(AuthException::class) suspend fun deleteUser() + + /** + * Setup TOTP for the currently signed in user. + * @return TOTP Setup details + */ + suspend fun setUpTOTP(): TOTPSetupDetails + + /** + * Verify TOTP setup for the currently signed in user. + * @param code TOTP code to verify TOTP setup + * @param options additional options to verify totp setup + */ + suspend fun verifyTOTPSetup( + code: String, + options: AuthVerifyTOTPSetupOptions = AuthVerifyTOTPSetupOptions.builder().build() + ) } diff --git a/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/KotlinAuthFacade.kt b/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/KotlinAuthFacade.kt index 41a3d03bce..bb95055a18 100644 --- a/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/KotlinAuthFacade.kt +++ b/core-kotlin/src/main/java/com/amplifyframework/kotlin/auth/KotlinAuthFacade.kt @@ -17,6 +17,7 @@ package com.amplifyframework.kotlin.auth import android.app.Activity import android.content.Intent +import com.amplifyframework.TOTPSetupDetails import com.amplifyframework.auth.AuthCategoryBehavior as Delegate import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.AuthDevice @@ -37,6 +38,7 @@ import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult @@ -336,4 +338,24 @@ class KotlinAuthFacade(private val delegate: Delegate = Amplify.Auth) : Auth { ) } } + + override suspend fun setUpTOTP(): TOTPSetupDetails { + return suspendCoroutine { continuation -> + delegate.setUpTOTP({ + continuation.resume(it) + }, { + continuation.resumeWithException(it) + }) + } + } + + override suspend fun verifyTOTPSetup(code: String, options: AuthVerifyTOTPSetupOptions) { + return suspendCoroutine { continuation -> + delegate.verifyTOTPSetup(code, options, { + continuation.resume(Unit) + }, { + continuation.resumeWithException(it) + }) + } + } } diff --git a/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java index ea62e19604..f422623203 100644 --- a/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java +++ b/core/src/main/java/com/amplifyframework/auth/options/AuthVerifyTOTPSetupOptions.java @@ -21,6 +21,20 @@ */ public class AuthVerifyTOTPSetupOptions { + /** + * protected constructor. + */ + protected AuthVerifyTOTPSetupOptions() { + } + + /** + * Get a builder to construct an instance of this object. + * @return a builder to construct an instance of this object. + */ + public static Builder builder() { + return new CoreBuilder(); + } + /** * The builder for this class. * @param The type of builder - used to support plugin extensions of this. @@ -36,4 +50,11 @@ public AuthVerifyTOTPSetupOptions build() { } } + + /** + * The specific implementation of builder for this as the parent class. + */ + public static final class CoreBuilder extends Builder { + + } } diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthBinding.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthBinding.java index f0b7d496ae..b37c3992e2 100644 --- a/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthBinding.java +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthBinding.java @@ -21,6 +21,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.amplifyframework.TOTPSetupDetails; import com.amplifyframework.auth.AuthCategoryBehavior; import com.amplifyframework.auth.AuthCodeDeliveryDetails; import com.amplifyframework.auth.AuthDevice; @@ -42,6 +43,7 @@ import com.amplifyframework.auth.options.AuthSignUpOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions; +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; import com.amplifyframework.auth.options.AuthWebUISignInOptions; import com.amplifyframework.auth.result.AuthResetPasswordResult; import com.amplifyframework.auth.result.AuthSignInResult; @@ -308,12 +310,27 @@ public Single signOut() { public Single signOut(@NonNull AuthSignOutOptions options) { return toSingle((onComplete, onError) -> delegate.signOut(options, onComplete)); } - + @Override public Completable deleteUser() { return toCompletable(delegate::deleteUser); } + @Override + public Single setUpTOTP() { + return toSingle(delegate::setUpTOTP); + } + + @Override + public Completable verifyTOTPSetup(@NonNull String code) { + return toCompletable((onComplete, onError) -> delegate.verifyTOTPSetup(code, onComplete, onError)); + } + + @Override + public Completable verifyTOTPSetup(@NonNull String code, @NonNull AuthVerifyTOTPSetupOptions options) { + return toCompletable((onComplete, onError) -> delegate.verifyTOTPSetup(code, options, onComplete, onError)); + } + private Single toSingle(VoidBehaviors.ResultEmitter behavior) { return VoidBehaviors.toSingle(behavior); } diff --git a/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthCategoryBehavior.java b/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthCategoryBehavior.java index bbc810f98d..1c5b4812f6 100644 --- a/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthCategoryBehavior.java +++ b/rxbindings/src/main/java/com/amplifyframework/rx/RxAuthCategoryBehavior.java @@ -20,6 +20,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.amplifyframework.TOTPSetupDetails; import com.amplifyframework.auth.AuthCategoryBehavior; import com.amplifyframework.auth.AuthCodeDeliveryDetails; import com.amplifyframework.auth.AuthDevice; @@ -41,6 +42,7 @@ import com.amplifyframework.auth.options.AuthSignUpOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions; import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions; +import com.amplifyframework.auth.options.AuthVerifyTOTPSetupOptions; import com.amplifyframework.auth.options.AuthWebUISignInOptions; import com.amplifyframework.auth.result.AuthResetPasswordResult; import com.amplifyframework.auth.result.AuthSignInResult; @@ -436,4 +438,28 @@ Single resendUserAttributeConfirmationCode( * emits an {@link AuthException} otherwise */ Completable deleteUser(); + + /** + * Setup TOTP for the currently signed in user. + * @return An Rx {@link Single} which emits {@link TOTPSetupDetails} on completion + */ + Single setUpTOTP(); + + /** + * Verify TOTP setup for the currently signed in user. + * @param code TOTP code to verify TOTP setup + * @return An Rx {@link Completable} which completes upon successfully verifying totp code; + * emits an {@link AuthException} otherwise + */ + Completable verifyTOTPSetup(@NonNull String code); + + /** + * Verify TOTP setup for the currently signed in user. + * @param code TOTP code to verify TOTP setup + * @param options additional options to verify totp setup + * @return An Rx {@link Completable} which completes upon successfully verifying totp code; + * emits an {@link AuthException} otherwise + */ + Completable verifyTOTPSetup(@NonNull String code, @NonNull AuthVerifyTOTPSetupOptions options); + }