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 abaacf0f12..812248b36c 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 @@ -313,13 +313,13 @@ class AWSCognitoAuthPlugin : AuthPlugin() { options: AuthResetPasswordOptions, onSuccess: Consumer, onError: Consumer - ) = enqueue(onSuccess, onError) { queueFacade.resetPassword(username, options) } + ) = enqueue(onSuccess, onError) { useCaseFactory.resetPassword().execute(username, options) } override fun resetPassword( username: String, onSuccess: Consumer, onError: Consumer - ) = enqueue(onSuccess, onError) { queueFacade.resetPassword(username) } + ) = enqueue(onSuccess, onError) { useCaseFactory.resetPassword().execute(username) } override fun confirmResetPassword( username: String, @@ -329,7 +329,7 @@ class AWSCognitoAuthPlugin : AuthPlugin() { onSuccess: Action, onError: Consumer ) = enqueue(onSuccess, onError) { - queueFacade.confirmResetPassword(username, newPassword, confirmationCode, options) + useCaseFactory.confirmResetPassword().execute(username, newPassword, confirmationCode, options) } override fun confirmResetPassword( @@ -338,14 +338,16 @@ class AWSCognitoAuthPlugin : AuthPlugin() { confirmationCode: String, onSuccess: Action, onError: Consumer - ) = enqueue(onSuccess, onError) { queueFacade.confirmResetPassword(username, newPassword, confirmationCode) } + ) = enqueue(onSuccess, onError) { + useCaseFactory.confirmResetPassword().execute(username, newPassword, confirmationCode) + } override fun updatePassword( oldPassword: String, newPassword: String, onSuccess: Action, onError: Consumer - ) = enqueue(onSuccess, onError) { queueFacade.updatePassword(oldPassword, newPassword) } + ) = enqueue(onSuccess, onError) { useCaseFactory.updatePassword().execute(oldPassword, newPassword) } override fun fetchUserAttributes(onSuccess: Consumer>, onError: Consumer) = enqueue(onSuccess, onError) { useCaseFactory.fetchUserAttributes().execute() } 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 26fbcf252a..abd7902dcb 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 @@ -22,17 +22,14 @@ import com.amplifyframework.auth.AuthProvider import com.amplifyframework.auth.AuthSession import com.amplifyframework.auth.cognito.options.FederateToIdentityPoolOptions import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult -import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions import com.amplifyframework.auth.options.AuthConfirmSignInOptions import com.amplifyframework.auth.options.AuthConfirmSignUpOptions import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.options.AuthResendSignUpCodeOptions -import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.options.AuthSignInOptions import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions -import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult import com.amplifyframework.auth.result.AuthSignOutResult import com.amplifyframework.auth.result.AuthSignUpResult @@ -194,60 +191,6 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth ) } - suspend fun resetPassword(username: String): AuthResetPasswordResult = suspendCoroutine { continuation -> - delegate.resetPassword( - username, - { continuation.resume(it) }, - { continuation.resumeWithException(it) } - ) - } - - suspend fun resetPassword(username: String, options: AuthResetPasswordOptions): AuthResetPasswordResult = - suspendCoroutine { continuation -> - delegate.resetPassword( - username, - options, - { continuation.resume(it) }, - { continuation.resumeWithException(it) } - ) - } - - suspend fun confirmResetPassword(username: String, newPassword: String, confirmationCode: String) = - suspendCoroutine { continuation -> - delegate.confirmResetPassword( - username, - newPassword, - confirmationCode, - { continuation.resume(Unit) }, - { continuation.resumeWithException(it) } - ) - } - - suspend fun confirmResetPassword( - username: String, - newPassword: String, - confirmationCode: String, - options: AuthConfirmResetPasswordOptions - ) = suspendCoroutine { continuation -> - delegate.confirmResetPassword( - username, - newPassword, - confirmationCode, - options, - { continuation.resume(Unit) }, - { continuation.resumeWithException(it) } - ) - } - - suspend fun updatePassword(oldPassword: String, newPassword: String) = suspendCoroutine { continuation -> - delegate.updatePassword( - oldPassword, - newPassword, - { continuation.resume(Unit) }, - { continuation.resumeWithException(it) } - ) - } - suspend fun signOut(): AuthSignOutResult = suspendCoroutine { continuation -> delegate.signOut { continuation.resume(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 1947d197fa..992e1fb977 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,11 +18,9 @@ package com.amplifyframework.auth.cognito import android.app.Activity import android.content.Intent import androidx.annotation.WorkerThread -import aws.sdk.kotlin.services.cognitoidentityprovider.confirmForgotPassword import aws.sdk.kotlin.services.cognitoidentityprovider.getUser import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType -import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType @@ -58,7 +56,6 @@ import com.amplifyframework.auth.cognito.helpers.getMFATypeOrNull import com.amplifyframework.auth.cognito.helpers.identityProviderName import com.amplifyframework.auth.cognito.helpers.isMfaSetupSelectionChallenge import com.amplifyframework.auth.cognito.helpers.value -import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmResetPasswordOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmSignInOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmSignUpOptions import com.amplifyframework.auth.cognito.options.AWSCognitoAuthResendSignUpCodeOptions @@ -73,7 +70,7 @@ import com.amplifyframework.auth.cognito.result.FederateToIdentityPoolResult import com.amplifyframework.auth.cognito.result.GlobalSignOutError import com.amplifyframework.auth.cognito.result.HostedUIError import com.amplifyframework.auth.cognito.result.RevokeTokenError -import com.amplifyframework.auth.cognito.usecases.ResetPasswordUseCase +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.exceptions.ConfigurationException import com.amplifyframework.auth.exceptions.InvalidStateException import com.amplifyframework.auth.exceptions.NotAuthorizedException @@ -81,17 +78,14 @@ import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.auth.exceptions.SessionExpiredException import com.amplifyframework.auth.exceptions.SignedOutException import com.amplifyframework.auth.exceptions.UnknownException -import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions import com.amplifyframework.auth.options.AuthConfirmSignInOptions import com.amplifyframework.auth.options.AuthConfirmSignUpOptions import com.amplifyframework.auth.options.AuthFetchSessionOptions import com.amplifyframework.auth.options.AuthResendSignUpCodeOptions -import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.options.AuthSignInOptions import com.amplifyframework.auth.options.AuthSignOutOptions import com.amplifyframework.auth.options.AuthSignUpOptions import com.amplifyframework.auth.options.AuthWebUISignInOptions -import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult import com.amplifyframework.auth.result.AuthSignOutResult import com.amplifyframework.auth.result.AuthSignUpResult @@ -144,9 +138,7 @@ import java.util.concurrent.atomic.AtomicReference import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.onStart @@ -486,21 +478,7 @@ internal class RealAWSCognitoAuthPlugin( encodedContextData?.let { this.userContextData { encodedData = it } } } - val deliveryDetails = response?.codeDeliveryDetails?.let { details -> - mapOf( - "DESTINATION" to details.destination, - "MEDIUM" to details.deliveryMedium?.value, - "ATTRIBUTE" to details.attributeName - ) - } - - val codeDeliveryDetails = AuthCodeDeliveryDetails( - deliveryDetails?.getValue("DESTINATION") ?: "", - AuthCodeDeliveryDetails.DeliveryMedium.fromString( - deliveryDetails?.getValue("MEDIUM") - ), - deliveryDetails?.getValue("ATTRIBUTE") - ) + val codeDeliveryDetails = response?.codeDeliveryDetails.toAuthCodeDeliveryDetails() onSuccess.accept(codeDeliveryDetails) logger.verbose("ResendSignUpCode Execution complete") } catch (exception: Exception) { @@ -1466,154 +1444,6 @@ internal class RealAWSCognitoAuthPlugin( ) } - @OptIn(DelicateCoroutinesApi::class) - fun resetPassword( - username: String, - options: AuthResetPasswordOptions, - onSuccess: Consumer, - onError: Consumer - ) { - try { - val cognitoIdentityProviderClient = requireNotNull( - authEnvironment.cognitoAuthService.cognitoIdentityProviderClient - ) - - val appClient = requireNotNull(configuration.userPool?.appClient) - GlobalScope.launch { - val encodedData = authEnvironment.getUserContextData(username) - val pinpointEndpointId = authEnvironment.getPinpointEndpointId() - - ResetPasswordUseCase( - cognitoIdentityProviderClient, - appClient, - configuration.userPool?.appClientSecret - ).execute( - username, - options, - encodedData, - pinpointEndpointId, - onSuccess, - onError - ) - } - } catch (ex: Exception) { - onError.accept(InvalidUserPoolConfigurationException()) - } - } - - fun resetPassword( - username: String, - onSuccess: Consumer, - onError: Consumer - ) { - resetPassword(username, AuthResetPasswordOptions.defaults(), onSuccess, onError) - } - - fun confirmResetPassword( - username: String, - newPassword: String, - confirmationCode: String, - options: AuthConfirmResetPasswordOptions, - onSuccess: Action, - onError: Consumer - ) { - authStateMachine.getCurrentState { authState -> - if (authState.authNState is AuthenticationState.NotConfigured) { - onError.accept( - ConfigurationException( - "Confirm Reset Password failed.", - "Cognito User Pool not configured. Please check amplifyconfiguration.json file." - ) - ) - return@getCurrentState - } - - GlobalScope.launch { - try { - val encodedContextData = authEnvironment.getUserContextData(username) - val pinpointEndpointId = authEnvironment.getPinpointEndpointId() - - authEnvironment.cognitoAuthService.cognitoIdentityProviderClient!!.confirmForgotPassword { - this.username = username - this.confirmationCode = confirmationCode - password = newPassword - secretHash = AuthHelper.getSecretHash( - username, - configuration.userPool?.appClient, - configuration.userPool?.appClientSecret - ) - clientMetadata = - (options as? AWSCognitoAuthConfirmResetPasswordOptions)?.metadata ?: mapOf() - clientId = configuration.userPool?.appClient - encodedContextData?.let { this.userContextData { encodedData = it } } - pinpointEndpointId?.let { - this.analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = it } - } - }.let { onSuccess.call() } - } catch (ex: Exception) { - onError.accept( - CognitoAuthExceptionConverter.lookup(ex, AmplifyException.REPORT_BUG_TO_AWS_SUGGESTION) - ) - } - } - } - } - - fun confirmResetPassword( - username: String, - newPassword: String, - confirmationCode: String, - onSuccess: Action, - onError: Consumer - ) { - confirmResetPassword( - username, - newPassword, - confirmationCode, - AuthConfirmResetPasswordOptions.defaults(), - onSuccess, - onError - ) - } - - fun updatePassword(oldPassword: String, newPassword: String, onSuccess: Action, onError: Consumer) { - authStateMachine.getCurrentState { authState -> - when (authState.authNState) { - // Check if user signed in - is AuthenticationState.SignedIn -> { - _updatePassword(oldPassword, newPassword, onSuccess, onError) - } - is AuthenticationState.SignedOut -> onError.accept(SignedOutException()) - else -> onError.accept(InvalidStateException()) - } - } - } - - private fun _updatePassword( - oldPassword: String, - newPassword: String, - onSuccess: Action, - onError: Consumer - ) { - GlobalScope.async { - val tokens = getSession().userPoolTokensResult - val changePasswordRequest = ChangePasswordRequest.invoke { - previousPassword = oldPassword - proposedPassword = newPassword - this.accessToken = tokens.value?.accessToken - } - try { - authEnvironment.cognitoAuthService - .cognitoIdentityProviderClient?.changePassword( - changePasswordRequest - ) - onSuccess.call() - } catch (e: Exception) { - onError.accept(CognitoAuthExceptionConverter.lookup(e, e.toString())) - } - } - } - fun signOut(onComplete: Consumer) { signOut(AuthSignOutOptions.builder().build(), onComplete) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActions.kt index b4f787e55d..78a92c488d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActions.kt @@ -19,9 +19,9 @@ 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.signUp -import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.helpers.AuthHelper +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.result.AuthSignUpResult import com.amplifyframework.auth.result.step.AuthNextSignUpStep import com.amplifyframework.auth.result.step.AuthSignUpStep @@ -68,13 +68,7 @@ internal object SignUpCognitoActions : SignUpActions { } } - val codeDeliveryDetails = AuthCodeDeliveryDetails( - response?.codeDeliveryDetails?.destination ?: "", - AuthCodeDeliveryDetails.DeliveryMedium.fromString( - response?.codeDeliveryDetails?.deliveryMedium?.value - ), - response?.codeDeliveryDetails?.attributeName - ) + val codeDeliveryDetails = response?.codeDeliveryDetails.toAuthCodeDeliveryDetails() val signUpData = SignUpData( username, event.signUpData.validationData, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt index 6ff04929e5..66a8a1e8b2 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt @@ -108,4 +108,21 @@ internal class AuthUseCaseFactory( fetchAuthSession = fetchAuthSession(), stateMachine = stateMachine ) + + fun updatePassword() = UpdatePasswordUseCase( + client = authEnvironment.requireIdentityProviderClient(), + fetchAuthSession = fetchAuthSession(), + stateMachine = stateMachine + ) + + fun resetPassword() = ResetPasswordUseCase( + client = authEnvironment.requireIdentityProviderClient(), + environment = authEnvironment + ) + + fun confirmResetPassword() = ConfirmResetPasswordUseCase( + client = authEnvironment.requireIdentityProviderClient(), + environment = authEnvironment, + stateMachine = stateMachine + ) } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCase.kt new file mode 100644 index 0000000000..1595f9f145 --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCase.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2025 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.usecases + +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.confirmForgotPassword +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.helpers.AuthHelper +import com.amplifyframework.auth.cognito.options.AWSCognitoAuthConfirmResetPasswordOptions +import com.amplifyframework.auth.exceptions.ConfigurationException +import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions +import com.amplifyframework.statemachine.codegen.states.AuthenticationState + +internal class ConfirmResetPasswordUseCase( + private val client: CognitoIdentityProviderClient, + private val environment: AuthEnvironment, + private val stateMachine: AuthStateMachine +) { + + suspend fun execute( + username: String, + newPassword: String, + code: String, + options: AuthConfirmResetPasswordOptions = AuthConfirmResetPasswordOptions.defaults() + ) { + stateMachine.throwIfNotConfigured() + + val awsOptions = options as? AWSCognitoAuthConfirmResetPasswordOptions + + val encodedContextData = environment.getUserContextData(username) + val pinpointEndpointId = environment.getPinpointEndpointId() + + client.confirmForgotPassword { + this.username = username + confirmationCode = code + password = newPassword + + secretHash = AuthHelper.getSecretHash( + username, + environment.configuration.userPool?.appClient, + environment.configuration.userPool?.appClientSecret + ) + clientMetadata = awsOptions?.metadata ?: emptyMap() + clientId = environment.configuration.userPool?.appClient + encodedContextData?.let { this.userContextData { encodedData = it } } + pinpointEndpointId?.let { + this.analyticsMetadata = AnalyticsMetadataType { analyticsEndpointId = it } + } + } + } + + private suspend fun AuthStateMachine.throwIfNotConfigured() { + if (getCurrentState().authNState is AuthenticationState.NotConfigured) { + throw ConfigurationException( + "Confirm Reset Password failed.", + "Cognito User Pool not configured. Please check your configuration file." + ) + } + } +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCase.kt index aa34987ae6..0fb8e32332 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCase.kt @@ -24,6 +24,7 @@ import com.amplifyframework.auth.cognito.exceptions.service.CodeDeliveryFailureE import com.amplifyframework.auth.cognito.options.AWSCognitoAuthResendUserAttributeConfirmationCodeOptions import com.amplifyframework.auth.cognito.requireAccessToken import com.amplifyframework.auth.cognito.requireSignedInState +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.options.AuthResendUserAttributeConfirmationCodeOptions internal class ResendUserAttributeConfirmationUseCase( diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt index 79c01e95b4..06df3c9373 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCase.kt @@ -18,85 +18,56 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient import aws.sdk.kotlin.services.cognitoidentityprovider.forgotPassword import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType -import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType -import com.amplifyframework.AmplifyException -import com.amplifyframework.auth.AuthCodeDeliveryDetails -import com.amplifyframework.auth.AuthException -import com.amplifyframework.auth.cognito.CognitoAuthExceptionConverter +import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.options.AWSCognitoAuthResetPasswordOptions +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep import com.amplifyframework.auth.result.step.AuthResetPasswordStep -import com.amplifyframework.core.Consumer -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import com.amplifyframework.statemachine.codegen.data.requireAppClientId /** * Business logic to reset password if user forgot the old password. */ internal class ResetPasswordUseCase( - private val cognitoIdentityProviderClient: CognitoIdentityProviderClient, - private val appClientId: String, - private val appClientSecret: String? + private val client: CognitoIdentityProviderClient, + private val environment: AuthEnvironment ) { suspend fun execute( username: String, - options: AuthResetPasswordOptions, - encodedContextData: String?, - pinpointEndpointId: String?, - onSuccess: Consumer, - onError: Consumer - ) { - try { - val response = withContext(Dispatchers.IO) { - cognitoIdentityProviderClient.forgotPassword { - this.username = username - this.clientMetadata = (options as? AWSCognitoAuthResetPasswordOptions)?.metadata ?: mapOf() - this.clientId = appClientId - this.secretHash = AuthHelper.getSecretHash( - username, - appClientId, - appClientSecret - ) - encodedContextData?.let { this.userContextData { encodedData = it } } - pinpointEndpointId?.let { - this.analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = it } - } - } - } + options: AuthResetPasswordOptions = AuthResetPasswordOptions.defaults() + ): AuthResetPasswordResult { + val awsOptions = options as? AWSCognitoAuthResetPasswordOptions - withContext(Dispatchers.Main) { - onSuccess.accept( - AuthResetPasswordResult( - false, - AuthNextResetPasswordStep( - AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, - mapOf(), - response.codeDeliveryDetails.toAuthCodeDeliveryDetails() - ) - ) - ) - } - } catch (ex: Exception) { - withContext(Dispatchers.Main) { - onError.accept( - CognitoAuthExceptionConverter.lookup(ex, AmplifyException.REPORT_BUG_TO_AWS_SUGGESTION) - ) + val appClientId = environment.configuration.userPool.requireAppClientId() + val appClientSecret = environment.configuration.userPool?.appClientSecret + val encodedContextData = environment.getUserContextData(username) + val pinpointEndpointId = environment.getPinpointEndpointId() + + val response = client.forgotPassword { + this.username = username + clientMetadata = awsOptions?.metadata ?: emptyMap() + clientId = appClientId + secretHash = AuthHelper.getSecretHash( + username, + appClientId, + appClientSecret + ) + encodedContextData?.let { this.userContextData { encodedData = it } } + pinpointEndpointId?.let { + this.analyticsMetadata = AnalyticsMetadataType { analyticsEndpointId = it } } } - } -} - -internal fun CodeDeliveryDetailsType?.toAuthCodeDeliveryDetails(): AuthCodeDeliveryDetails? { - if (this == null) return this - requireNotNull(destination) - - return AuthCodeDeliveryDetails( - destination.toString(), - AuthCodeDeliveryDetails.DeliveryMedium.fromString(deliveryMedium.toString()), - attributeName - ) + return AuthResetPasswordResult( + false, + AuthNextResetPasswordStep( + AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, + emptyMap(), + response.codeDeliveryDetails.toAuthCodeDeliveryDetails() + ) + ) + } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCase.kt new file mode 100644 index 0000000000..9e97172d0e --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCase.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 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.usecases + +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.changePassword +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.requireAccessToken +import com.amplifyframework.auth.cognito.requireSignedInState + +internal class UpdatePasswordUseCase( + private val client: CognitoIdentityProviderClient, + private val fetchAuthSession: FetchAuthSessionUseCase, + private val stateMachine: AuthStateMachine +) { + suspend fun execute(oldPassword: String, newPassword: String) { + stateMachine.requireSignedInState() + val token = fetchAuthSession.execute().requireAccessToken() + client.changePassword { + previousPassword = oldPassword + proposedPassword = newPassword + accessToken = token + } + } +} diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCase.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCase.kt index 96f9f93d8d..af02b400b4 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCase.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCase.kt @@ -26,6 +26,7 @@ import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttribu import com.amplifyframework.auth.cognito.options.AWSCognitoAuthUpdateUserAttributesOptions import com.amplifyframework.auth.cognito.requireAccessToken import com.amplifyframework.auth.cognito.requireSignedInState +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.options.AuthUpdateUserAttributeOptions import com.amplifyframework.auth.options.AuthUpdateUserAttributesOptions import com.amplifyframework.auth.result.AuthUpdateAttributeResult diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/util/CognitoExtensions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/util/CognitoExtensions.kt new file mode 100644 index 0000000000..b175e29a4d --- /dev/null +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/util/CognitoExtensions.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2025 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.util + +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType +import com.amplifyframework.auth.AuthCodeDeliveryDetails + +/** + * Always returns an [AuthCodeDeliveryDetails]. If this type is null then the delivery details will be empty. + */ +internal fun CodeDeliveryDetailsType?.toAuthCodeDeliveryDetails(): AuthCodeDeliveryDetails = AuthCodeDeliveryDetails( + this?.destination ?: "", + AuthCodeDeliveryDetails.DeliveryMedium.fromString(this?.deliveryMedium?.value), + this?.attributeName +) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/UserPoolConfiguration.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/UserPoolConfiguration.kt index 3bffc00ffd..f43b2cbd37 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/UserPoolConfiguration.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/UserPoolConfiguration.kt @@ -17,6 +17,7 @@ package com.amplifyframework.statemachine.codegen.data import com.amplifyframework.annotations.InternalAmplifyApi import com.amplifyframework.auth.AuthException +import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException import org.json.JSONObject /** @@ -49,17 +50,13 @@ data class UserPoolConfiguration internal constructor( * @return fresh configuration builder instance. */ @JvmStatic - fun builder(): Builder { - return Builder() - } + fun builder(): Builder = Builder() /** * Returns a builder object populated from JSON. * @return populated builder instance. */ - fun fromJson(configJson: JSONObject): Builder { - return Builder(configJson) - } + fun fromJson(configJson: JSONObject): Builder = Builder(configJson) inline operator fun invoke(block: Builder.() -> Unit) = Builder().apply(block).build() } @@ -67,9 +64,7 @@ data class UserPoolConfiguration internal constructor( /** * Builder class for constructing [UserPoolConfiguration]. */ - internal class Builder constructor( - configJson: JSONObject? = null - ) { + internal class Builder constructor(configJson: JSONObject? = null) { var region: String? = DEFAULT_REGION var endpoint: String? = null var poolId: String? = null @@ -160,3 +155,6 @@ data class UserPoolConfiguration internal constructor( PINPOINT_APP_ID("PinpointAppId") } } + +internal fun UserPoolConfiguration?.requireAppClientId() = + this?.appClient ?: throw InvalidUserPoolConfigurationException() 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 29d4c7bea7..dbb03df784 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 @@ -377,9 +377,11 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.resetPassword() + authPlugin.resetPassword(expectedUsername, expectedOnSuccess, expectedOnError) - verify(timeout = CHANNEL_TIMEOUT) { realPlugin.resetPassword(expectedUsername, any(), any()) } + coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute(expectedUsername, any()) } } @Test @@ -389,9 +391,11 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Consumer { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.resetPassword() + authPlugin.resetPassword(expectedUsername, expectedOptions, expectedOnSuccess, expectedOnError) - verify(timeout = CHANNEL_TIMEOUT) { realPlugin.resetPassword(expectedUsername, expectedOptions, any(), any()) } + coVerify(timeout = CHANNEL_TIMEOUT) { useCase.execute(expectedUsername, expectedOptions) } } @Test @@ -402,6 +406,8 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Action { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.confirmResetPassword() + authPlugin.confirmResetPassword( expectedUsername, expectedPassword, @@ -410,13 +416,11 @@ class AWSCognitoAuthPluginTest { expectedOnError ) - verify(timeout = CHANNEL_TIMEOUT) { - realPlugin.confirmResetPassword( + coVerify(timeout = CHANNEL_TIMEOUT) { + useCase.execute( expectedUsername, expectedPassword, - expectedCode, - any(), - any() + expectedCode ) } } @@ -430,6 +434,8 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Action { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.confirmResetPassword() + authPlugin.confirmResetPassword( expectedUsername, expectedPassword, @@ -439,14 +445,12 @@ class AWSCognitoAuthPluginTest { expectedOnError ) - verify(timeout = CHANNEL_TIMEOUT) { - realPlugin.confirmResetPassword( + coVerify(timeout = CHANNEL_TIMEOUT) { + useCase.execute( expectedUsername, expectedPassword, expectedCode, - expectedOptions, - any(), - any() + expectedOptions ) } } @@ -458,10 +462,12 @@ class AWSCognitoAuthPluginTest { val expectedOnSuccess = Action { } val expectedOnError = Consumer { } + val useCase = authPlugin.useCaseFactory.updatePassword() + authPlugin.updatePassword(expectedOldPassword, expectedNewPassword, expectedOnSuccess, expectedOnError) - verify(timeout = CHANNEL_TIMEOUT) { - realPlugin.updatePassword(expectedOldPassword, expectedNewPassword, any(), any()) + coVerify(timeout = CHANNEL_TIMEOUT) { + useCase.execute(expectedOldPassword, expectedNewPassword) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/MockAuthHelperRule.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/MockAuthHelperRule.kt new file mode 100644 index 0000000000..b2e0213590 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/MockAuthHelperRule.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2025 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.cognito.helpers.AuthHelper +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkObject +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Mocks the [AuthHelper] static API for a test + */ +class MockAuthHelperRule : TestRule { + override fun apply(base: Statement?, description: Description?): Statement = object : Statement() { + override fun evaluate() { + mockkObject(AuthHelper) + every { AuthHelper.getSecretHash(any(), any(), any()) } returns DEFAULT_HASH + try { + base?.evaluate() + } finally { + unmockkObject(AuthHelper) + } + } + } + + companion object { + val DEFAULT_HASH = "dummyHash" + } +} 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 6cfd37ca9c..c316a01956 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 @@ -18,10 +18,6 @@ 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.ChangePasswordResponse -import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException -import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest -import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserResponse @@ -40,15 +36,10 @@ import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.SRPHelper import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignInOptions import com.amplifyframework.auth.cognito.options.AuthFlowType -import com.amplifyframework.auth.cognito.usecases.ResetPasswordUseCase -import com.amplifyframework.auth.exceptions.InvalidStateException -import com.amplifyframework.auth.options.AuthConfirmResetPasswordOptions import com.amplifyframework.auth.options.AuthConfirmSignUpOptions import com.amplifyframework.auth.options.AuthResendSignUpCodeOptions -import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.options.AuthSignInOptions import com.amplifyframework.auth.options.AuthSignUpOptions -import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.AuthSignInResult import com.amplifyframework.auth.result.AuthSignUpResult import com.amplifyframework.core.Action @@ -70,7 +61,6 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.invoke import io.mockk.mockk -import io.mockk.mockkConstructor import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.slot @@ -307,212 +297,6 @@ class RealAWSCognitoAuthPluginTest { onError.shouldBeCalled() } - @Test - fun `update password with success`() { - // GIVEN - val onSuccess = ActionWithLatch() - - coEvery { - authService.cognitoIdentityProviderClient?.changePassword(any()) - } returns ChangePasswordResponse.invoke { } - - // WHEN - plugin.updatePassword("old", "new", onSuccess, mockk()) - - onSuccess.shouldBeCalled() - } - - @Test - fun `update password fails when not in SignedIn state`() { - // GIVEN - val onError = ConsumerWithLatch(expect = InvalidStateException()) - - setupCurrentAuthState(authNState = AuthenticationState.NotConfigured()) - - // WHEN - plugin.updatePassword("old", "new", mockk(), onError) - onError.shouldBeCalled() - } - - @Test - fun `update password fails when cognitoIdentityProviderClient not set`() { - val onError = ConsumerWithLatch() - - plugin.updatePassword("old", "new", mockk(), onError) - onError.shouldBeCalled() - } - - @Test - fun `reset password fails if cognitoIdentityProviderClient is not set`() { - // GIVEN - val onSuccess = mockk>() - val onError = ConsumerWithLatch(expect = InvalidUserPoolConfigurationException()) - - val userPool = UserPoolConfiguration.invoke { appClientId = "app Client Id" } - every { authService.cognitoIdentityProviderClient } returns null - every { authConfiguration.userPool } returns userPool - - // WHEN - plugin.resetPassword("user", AuthResetPasswordOptions.defaults(), onSuccess, onError) - - // THEN - onError.shouldBeCalled() - verify(exactly = 0) { onSuccess.accept(any()) } - } - - @Test - fun `reset password fails if appClientId is not set`() { - // GIVEN - val onSuccess = mockk>() - val onError = ConsumerWithLatch(expect = InvalidUserPoolConfigurationException()) - - val userPool = UserPoolConfiguration.invoke { appClientId = null } - every { authService.cognitoIdentityProviderClient } returns mockk() - every { authConfiguration.userPool } returns userPool - - // WHEN - plugin.resetPassword("user", AuthResetPasswordOptions.defaults(), onSuccess, onError) - - // THEN - onError.shouldBeCalled() - verify(exactly = 0) { onSuccess.accept(any()) } - } - - @Test - fun `reset password executes ResetPasswordUseCase if required params are set`() { - // GIVEN - val onSuccess = ConsumerWithLatch() - val onError = mockk>() - val options = mockk() - val username = "user" - val pinpointAppId = "abc" - val encodedData = "encodedData" - - coEvery { authEnvironment.getUserContextData(username) } returns encodedData - every { authEnvironment.getPinpointEndpointId() } returns pinpointAppId - - mockkConstructor(ResetPasswordUseCase::class) - - every { authService.cognitoIdentityProviderClient } returns mockk() - every { authConfiguration.userPool } returns UserPoolConfiguration.invoke { appClientId = "app Client Id" } - - coEvery { - anyConstructed().execute(any(), any(), any(), any(), any(), any()) - } answers { - arg>(4).accept(mockk()) - } - - // WHEN - plugin.resetPassword(username, options, onSuccess, onError) - - // THEN - onSuccess.shouldBeCalled() - } - - @Test - fun `confirmResetPassword fails if authentication state is NotConfigured`() { - // Given - setupCurrentAuthState(authNState = AuthenticationState.NotConfigured()) - val expectedError = AuthException( - "Confirm Reset Password failed.", - "Cognito User Pool not configured. Please check amplifyconfiguration.json file." - ) - val onError = ConsumerWithLatch(expect = expectedError) - - // When - plugin.confirmResetPassword("user", "pass", "code", mockk(), mockk(), onError) - - // Then - onError.shouldBeCalled() - } - - @Test - fun `confirmResetPassword calls confirmForgotPassword API with given arguments`() { - // GIVEN - val latch = CountDownLatch(1) - val requestBuilderCaptor = slot() - coEvery { mockCognitoIPClient.confirmForgotPassword(capture(requestBuilderCaptor)) } coAnswers { - ConfirmForgotPasswordResponse.invoke { } - } - - val user = "username" - val pass = "passworD" - val code = "007" - - val expectedRequestBuilder: ConfirmForgotPasswordRequest.Builder.() -> Unit = { - username = user - password = pass - confirmationCode = code - clientMetadata = mapOf() - clientId = appClientId - secretHash = AuthHelper.getSecretHash( - username, - appClientId, - appClientSecret - ) - userContextData = null - analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = expectedEndpointId } - } - - // WHEN - plugin.confirmResetPassword( - user, - pass, - code, - AuthConfirmResetPasswordOptions.defaults(), - { latch.countDown() }, - { latch.countDown() } - ) - - // THEN - assertTrue { latch.await(5, TimeUnit.SECONDS) } - assertEquals( - ConfirmForgotPasswordRequest.invoke(expectedRequestBuilder), - requestBuilderCaptor.captured - ) - } - - @Test - fun `onSuccess is called when confirmResetPassword call succeeds`() { - // GIVEN - val onSuccess = ActionWithLatch() - val user = "username" - val pass = "passworD" - val code = "007" - - coEvery { mockCognitoIPClient.confirmForgotPassword(any()) } coAnswers { - ConfirmForgotPasswordResponse.invoke { } - } - - // WHEN - plugin.confirmResetPassword(user, pass, code, AuthConfirmResetPasswordOptions.defaults(), onSuccess, mockk()) - - // THEN - onSuccess.shouldBeCalled() - } - - @Test - fun `AuthException is thrown when confirmForgotPassword API call fails`() { - // GIVEN - val onError = ConsumerWithLatch() - - val user = "username" - val pass = "passworD" - val code = "007" - - val expectedException = CognitoIdentityProviderException("Some SDK Message") - coEvery { mockCognitoIPClient.confirmForgotPassword(any()) } coAnswers { - throw expectedException - } - - // WHEN - plugin.confirmResetPassword(user, pass, code, AuthConfirmResetPasswordOptions.defaults(), mockk(), onError) - - // THEN - onError.shouldBeCalled() - assertEquals(expectedException, onError.captured.cause) - } - @Test fun `test resend signup code API with given arguments and auth signed out`() { setupCurrentAuthState(AuthenticationState.SignedOut(mockk())) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActionsTest.kt index 23167ce5b1..e723ade5d8 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActionsTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SignUpCognitoActionsTest.kt @@ -24,7 +24,7 @@ import com.amplifyframework.auth.cognito.AWSCognitoAuthService import com.amplifyframework.auth.cognito.AuthConfiguration import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.StoreClientBehavior -import com.amplifyframework.auth.cognito.usecases.toAuthCodeDeliveryDetails +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.result.step.AuthSignUpStep import com.amplifyframework.logging.Logger import com.amplifyframework.statemachine.EventDispatcher diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/CodeDeliveryDetailsTypeExtensionTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/CodeDeliveryDetailsTypeExtensionTest.kt index 1be2123a1a..8b6ba662fc 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/CodeDeliveryDetailsTypeExtensionTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/CodeDeliveryDetailsTypeExtensionTest.kt @@ -18,30 +18,18 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType import com.amplifyframework.auth.AuthCodeDeliveryDetails -import kotlin.test.assertEquals +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails +import io.kotest.matchers.shouldBe import org.junit.Test /** * Tests for [CodeDeliveryDetailsType.toAuthCodeDeliveryDetails] extension */ class CodeDeliveryDetailsTypeExtensionTest { - - @Test(expected = IllegalArgumentException::class) - fun `CodeDeliveryDetailsType cannot be converted to AuthCodeDeliveryDetails without destination`() { - // GIVEN - val deliveryDetailsType = CodeDeliveryDetailsType.invoke { - deliveryMedium = DeliveryMediumType.Email - attributeName = "dummy attribute" - } - - // WHEN - deliveryDetailsType.toAuthCodeDeliveryDetails() - } - @Test fun `CodeDeliveryDetailsType maps to AuthCodeDeliveryDetails`() { // GIVEN - val deliveryDetailsType = CodeDeliveryDetailsType.invoke { + val deliveryDetailsType = CodeDeliveryDetailsType { deliveryMedium = DeliveryMediumType.Email attributeName = "dummy attribute" destination = "dummy destination" @@ -56,6 +44,6 @@ class CodeDeliveryDetailsTypeExtensionTest { val authCodeDeliveryDetails = deliveryDetailsType.toAuthCodeDeliveryDetails() // THEN - assertEquals(expectedResult, authCodeDeliveryDetails) + authCodeDeliveryDetails shouldBe expectedResult } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCaseTest.kt new file mode 100644 index 0000000000..2279f999d7 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmResetPasswordUseCaseTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2025 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.usecases + +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.confirmForgotPassword +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordRequest +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ConfirmForgotPasswordResponse +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.cognito.MockAuthHelperRule +import com.amplifyframework.auth.exceptions.ConfigurationException +import com.amplifyframework.statemachine.codegen.states.AuthenticationState +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrowAny +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ConfirmResetPasswordUseCaseTest { + private val client: CognitoIdentityProviderClient = mockk() + private val stateMachine: AuthStateMachine = mockk { + coEvery { getCurrentState().authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + } + private val environment = mockk { + coEvery { getDeviceMetadata("user")?.deviceKey } returns "test deviceKey" + every { getPinpointEndpointId() } returns "pinpointId" + coEvery { getUserContextData(any()) } returns null + every { configuration.userPool } returns mockk { + every { appClient } returns "appClient" + every { appClientSecret } returns "appClientSecret" + } + } + + private val useCase = ConfirmResetPasswordUseCase( + client = client, + environment = environment, + stateMachine = stateMachine + ) + + @get:Rule + val authHelperMock = MockAuthHelperRule() + + @Test + fun `confirmResetPassword fails if authentication state is NotConfigured`() = runTest { + // Given + coEvery { stateMachine.getCurrentState().authNState } returns AuthenticationState.NotConfigured() + + val expectedError = ConfigurationException( + "Confirm Reset Password failed.", + "Cognito User Pool not configured. Please check amplifyconfiguration.json file." + ) + + shouldThrowAny { + useCase.execute("username", "password", "123456") + } shouldBe expectedError + } + + @Test + fun `confirmResetPassword calls confirmForgotPassword API with given arguments`() = runTest { + // GIVEN + coEvery { client.confirmForgotPassword(any()) } returns ConfirmForgotPasswordResponse { } + + val user = "username" + val pass = "passworD" + val code = "007" + + val expectedRequestBuilder: ConfirmForgotPasswordRequest.Builder.() -> Unit = { + username = user + password = pass + confirmationCode = code + clientMetadata = emptyMap() + clientId = "appClient" + secretHash = MockAuthHelperRule.DEFAULT_HASH + userContextData = null + analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = "pinpointId" } + } + + useCase.execute("username", "passworD", "007") + + coVerify { + client.confirmForgotPassword(expectedRequestBuilder) + } + } + + @Test + fun `confirmResetPassword call succeeds`() = runTest { + coEvery { client.confirmForgotPassword(any()) } returns ConfirmForgotPasswordResponse { } + + shouldNotThrowAny { + useCase.execute("username", "passworD", "007") + } + } + + @Test + fun `Exception is thrown when confirmForgotPassword API call fails`() = runTest { + val expectedException = CognitoIdentityProviderException("Some SDK Message") + coEvery { client.confirmForgotPassword(any()) } throws expectedException + + shouldThrowAny { + useCase.execute("username", "passworD", "007") + } shouldBe expectedException + } +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmUserAttributeUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmUserAttributeUseCaseTest.kt index 3ccdb3913f..b95a4abb3b 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmUserAttributeUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ConfirmUserAttributeUseCaseTest.kt @@ -34,7 +34,7 @@ import org.junit.Test class ConfirmUserAttributeUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchDevicesUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchDevicesUseCaseTest.kt index 106c084006..760a8ce0ec 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchDevicesUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchDevicesUseCaseTest.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class FetchDevicesUseCaseTest { - private val client = mockk() + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchUserAttributesUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchUserAttributesUseCaseTest.kt index 5d10e0436e..b08ad5180d 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchUserAttributesUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/FetchUserAttributesUseCaseTest.kt @@ -32,7 +32,7 @@ import org.junit.Test class FetchUserAttributesUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ForgetDeviceUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ForgetDeviceUseCaseTest.kt index 14af9cf696..05817eb532 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ForgetDeviceUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ForgetDeviceUseCaseTest.kt @@ -16,6 +16,7 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgetDeviceResponse import com.amplifyframework.auth.AuthDevice import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.AuthStateMachine @@ -33,7 +34,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class ForgetDeviceUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } @@ -56,6 +57,8 @@ class ForgetDeviceUseCaseTest { @Test fun `forget device invokes API`() = runTest { + coEvery { client.forgetDevice(any()) } returns ForgetDeviceResponse { } + useCase.execute() coVerify { @@ -70,6 +73,8 @@ class ForgetDeviceUseCaseTest { @Test fun `forget device invokes API with device ID`() = runTest { + coEvery { client.forgetDevice(any()) } returns ForgetDeviceResponse { } + val device = AuthDevice.fromId("specified id") useCase.execute(device) diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/RememberDeviceUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/RememberDeviceUseCaseTest.kt index 32ca8a593c..3f2664f528 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/RememberDeviceUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/RememberDeviceUseCaseTest.kt @@ -17,6 +17,7 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceRememberedStatusType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateDeviceStatusResponse import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.AuthStateMachine import com.amplifyframework.auth.exceptions.InvalidStateException @@ -33,7 +34,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class RememberDeviceUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } @@ -56,6 +57,8 @@ class RememberDeviceUseCaseTest { @Test fun `remember device invokes API`() = runTest { + coEvery { client.updateDeviceStatus(any()) } returns UpdateDeviceStatusResponse { } + useCase.execute() coVerify { client.updateDeviceStatus( diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCaseTest.kt index fcba25fe86..3a24a264ca 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResendUserAttributeConfirmationUseCaseTest.kt @@ -40,7 +40,7 @@ import org.junit.Test class ResendUserAttributeConfirmationUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt index 51ad31a395..d7181c6cfb 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/ResetPasswordUseCaseTest.kt @@ -16,41 +16,32 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.forgotPassword import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataType import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeDeliveryDetailsType import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeliveryMediumType import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgotPasswordRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgotPasswordResponse -import com.amplifyframework.auth.AuthException -import com.amplifyframework.auth.cognito.helpers.AuthHelper +import com.amplifyframework.auth.cognito.AuthEnvironment +import com.amplifyframework.auth.cognito.MockAuthHelperRule +import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException +import com.amplifyframework.auth.cognito.util.toAuthCodeDeliveryDetails import com.amplifyframework.auth.options.AuthResetPasswordOptions import com.amplifyframework.auth.result.AuthResetPasswordResult import com.amplifyframework.auth.result.step.AuthNextResetPasswordStep import com.amplifyframework.auth.result.step.AuthResetPasswordStep -import com.amplifyframework.core.Consumer +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.assertions.throwables.shouldThrowAny +import io.kotest.matchers.shouldBe import io.mockk.coEvery -import io.mockk.coJustRun import io.mockk.coVerify -import io.mockk.justRun +import io.mockk.every import io.mockk.mockk -import io.mockk.slot -import kotlin.test.assertEquals -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.newSingleThreadContext -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.resetMain -import kotlinx.coroutines.test.setMain -import org.junit.After -import org.junit.Before +import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) -@RunWith(RobolectricTestRunner::class) class ResetPasswordUseCaseTest { private val dummyClientId = "app client id" @@ -58,69 +49,50 @@ class ResetPasswordUseCaseTest { private val dummyUserName = "username" private val expectedPinpointEndpointId = "abc123" - private val mockCognitoIPClient: CognitoIdentityProviderClient = mockk() - - private lateinit var resetPasswordUseCase: ResetPasswordUseCase - - // Used to execute a test in situations where the platform Main dispatcher is not available - // see [https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/] - private val mainThreadSurrogate = newSingleThreadContext("Main thread") - - @Before - fun setUp() { - Dispatchers.setMain(mainThreadSurrogate) - resetPasswordUseCase = ResetPasswordUseCase(mockCognitoIPClient, dummyClientId, dummyAppClientSecret) + private val client: CognitoIdentityProviderClient = mockk { + coEvery { forgotPassword(any()) } returns ForgotPasswordResponse { + codeDeliveryDetails = mockk(relaxed = true) + } } - @After - fun tearDown() { - Dispatchers.resetMain() + private val environment = mockk { + coEvery { getDeviceMetadata("user")?.deviceKey } returns "test deviceKey" + every { getPinpointEndpointId() } returns expectedPinpointEndpointId + coEvery { getUserContextData(any()) } returns null + every { configuration.userPool } returns mockk { + every { appClient } returns dummyClientId + every { appClientSecret } returns dummyAppClientSecret + } } - @Test - fun `use case calls forgotPassword API with given arguments`() { - // GIVEN - val requestBuilderCaptor = slot() - coJustRun { mockCognitoIPClient.forgotPassword(capture(requestBuilderCaptor)) } + private val useCase = ResetPasswordUseCase( + client = client, + environment = environment + ) + + @get:Rule + val authHelperRule = MockAuthHelperRule() + @Test + fun `use case calls forgotPassword API with given arguments`() = runTest { val expectedRequestBuilder: ForgotPasswordRequest.Builder.() -> Unit = { username = dummyUserName clientMetadata = mapOf() clientId = dummyClientId - secretHash = AuthHelper.getSecretHash( - username, - dummyClientId, - dummyAppClientSecret - ) + secretHash = MockAuthHelperRule.DEFAULT_HASH analyticsMetadata = AnalyticsMetadataType.invoke { analyticsEndpointId = expectedPinpointEndpointId } } - // WHEN - runBlocking { - resetPasswordUseCase.execute( - dummyUserName, - AuthResetPasswordOptions.defaults(), - null, - expectedPinpointEndpointId, - {}, - {} - ) - } + useCase.execute(dummyUserName, AuthResetPasswordOptions.defaults()) - // THEN - assertEquals( - ForgotPasswordRequest.invoke(expectedRequestBuilder), - requestBuilderCaptor.captured - ) + coVerify { + client.forgotPassword(expectedRequestBuilder) + } } @Test - fun `AuthResetPasswordResult object is returned when reset password succeeds`() { - // GIVEN - val onSuccess = mockk>() - val onError = mockk>() - - val dummyCodeDeliveryDetails = CodeDeliveryDetailsType.invoke { + fun `AuthResetPasswordResult object is returned when reset password succeeds`() = runTest { + val dummyCodeDeliveryDetails = CodeDeliveryDetailsType { destination = "dummy destination" deliveryMedium = DeliveryMediumType.Email attributeName = "dummy attribute" @@ -135,64 +107,31 @@ class ResetPasswordUseCaseTest { ) ) - coEvery { mockCognitoIPClient.forgotPassword(any()) } coAnswers { - ForgotPasswordResponse.invoke { codeDeliveryDetails = dummyCodeDeliveryDetails } - } + coEvery { client.forgotPassword(any()) } returns + ForgotPasswordResponse { codeDeliveryDetails = dummyCodeDeliveryDetails } - val resultCaptor = slot() - justRun { onSuccess.accept(capture(resultCaptor)) } - - // WHEN - runBlocking { - resetPasswordUseCase.execute( - dummyUserName, - AuthResetPasswordOptions.defaults(), - null, - expectedPinpointEndpointId, - onSuccess, - onError - ) - } + val result = useCase.execute(dummyUserName) - // THEN - coVerify(exactly = 0) { onError.accept(any()) } - coVerify(exactly = 1) { onSuccess.accept(resultCaptor.captured) } - - assertEquals(expectedResult, resultCaptor.captured) + result shouldBe expectedResult } @Test - fun `AuthException is thrown when forgotPassword API call fails`() { - // GIVEN - val onSuccess = mockk>() - val onError = mockk>() + fun `AuthException is thrown when forgotPassword API call fails`() = runTest { val expectedException = CognitoIdentityProviderException("Some SDK Message") - coEvery { mockCognitoIPClient.forgotPassword(any()) } coAnswers { - throw expectedException - } + coEvery { client.forgotPassword(any()) } throws expectedException - val resultCaptor = slot() - justRun { onError.accept(capture(resultCaptor)) } - - // WHEN - runBlocking { - resetPasswordUseCase.execute( - dummyUserName, - AuthResetPasswordOptions.defaults(), - null, - expectedPinpointEndpointId, - onSuccess, - onError - ) - } + shouldThrowAny { + useCase.execute(dummyUserName) + } shouldBe expectedException + } - // THEN - coVerify(exactly = 0) { onSuccess.accept(any()) } - coVerify { - onError.accept(resultCaptor.captured) - } + @Test + fun `reset password fails if appClientId is not set`() = runTest { + every { environment.configuration.userPool?.appClient } returns null - assertEquals(expectedException, resultCaptor.captured.cause) + shouldThrow { + useCase.execute(dummyUserName) + } } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SetupTotpUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SetupTotpUseCaseTest.kt index d7b996c5fa..ac2c445778 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SetupTotpUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/SetupTotpUseCaseTest.kt @@ -17,7 +17,6 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient import aws.sdk.kotlin.services.cognitoidentityprovider.model.AssociateSoftwareTokenResponse -import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeleteWebAuthnCredentialResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaNotFoundException import com.amplifyframework.auth.cognito.AuthStateMachine import com.amplifyframework.auth.cognito.mockSignedInData @@ -30,9 +29,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class SetupTotpUseCaseTest { - private val client: CognitoIdentityProviderClient = mockk { - coEvery { deleteWebAuthnCredential(any()) } returns DeleteWebAuthnCredentialResponse { } - } + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCaseTest.kt new file mode 100644 index 0000000000..e861e61fe9 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdatePasswordUseCaseTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2025 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.usecases + +import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient +import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordResponse +import com.amplifyframework.auth.cognito.AuthStateMachine +import com.amplifyframework.auth.exceptions.InvalidStateException +import com.amplifyframework.statemachine.codegen.states.AuthenticationState +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.mockk.coEvery +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class UpdatePasswordUseCaseTest { + private val client: CognitoIdentityProviderClient = mockk() + private val fetchAuthSession: FetchAuthSessionUseCase = mockk { + coEvery { execute().accessToken } returns "access token" + } + private val stateMachine: AuthStateMachine = mockk { + coEvery { getCurrentState().authNState } returns AuthenticationState.SignedIn(mockk(), mockk()) + } + + private val useCase = UpdatePasswordUseCase( + client = client, + fetchAuthSession = fetchAuthSession, + stateMachine = stateMachine + ) + + @Test + fun `update password with success`() = runTest { + coEvery { client.changePassword(any()) } returns ChangePasswordResponse { } + + // WHEN + shouldNotThrowAny { + useCase.execute("old", "new") + } + } + + @Test + fun `update password fails when not in SignedIn state`() = runTest { + coEvery { stateMachine.getCurrentState().authNState } returns AuthenticationState.NotConfigured() + + shouldThrow { + useCase.execute("old", "new") + } + } +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCaseTest.kt index 0039ed0639..633351c095 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/UpdateUserAttributesUseCaseTest.kt @@ -45,7 +45,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class UpdateUserAttributesUseCaseTest { - private val client = mockk(relaxed = true) + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/VerifyTotpSetupUseCaseTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/VerifyTotpSetupUseCaseTest.kt index f803a6a520..039c3029c5 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/VerifyTotpSetupUseCaseTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/usecases/VerifyTotpSetupUseCaseTest.kt @@ -17,7 +17,6 @@ package com.amplifyframework.auth.cognito.usecases import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient import aws.sdk.kotlin.services.cognitoidentityprovider.model.CodeMismatchException -import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeleteWebAuthnCredentialResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenRequest import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponse import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType @@ -33,9 +32,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class VerifyTotpSetupUseCaseTest { - private val client: CognitoIdentityProviderClient = mockk { - coEvery { deleteWebAuthnCredential(any()) } returns DeleteWebAuthnCredentialResponse { } - } + private val client: CognitoIdentityProviderClient = mockk() private val fetchAuthSession: FetchAuthSessionUseCase = mockk { coEvery { execute().accessToken } returns "access token" }