diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt index 30018e2b3..cde7a4018 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthService.kt @@ -22,6 +22,7 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.endpoints.CognitoIdentity import aws.smithy.kotlin.runtime.client.RequestInterceptorContext import aws.smithy.kotlin.runtime.client.endpoints.Endpoint import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor +import com.amplifyframework.auth.cognito.exceptions.configuration.InvalidUserPoolConfigurationException import com.amplifyframework.util.setHttpEngine interface AWSCognitoAuthService { @@ -75,3 +76,6 @@ interface AWSCognitoAuthService { } } } + +internal fun AWSCognitoAuthService.requireIdentityClient() = + cognitoIdentityProviderClient ?: throw InvalidUserPoolConfigurationException() diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActions.kt index dfb19bf8c..f49caff08 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActions.kt @@ -18,14 +18,12 @@ package com.amplifyframework.auth.cognito.actions import aws.sdk.kotlin.services.cognitoidentityprovider.initiateAuth import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType import aws.sdk.kotlin.services.cognitoidentityprovider.respondToAuthChallenge -import aws.smithy.kotlin.runtime.util.type -import com.amplifyframework.AmplifyException import com.amplifyframework.auth.cognito.AuthEnvironment import com.amplifyframework.auth.cognito.helpers.AuthHelper import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper import com.amplifyframework.auth.cognito.helpers.toCognitoType import com.amplifyframework.auth.cognito.options.AuthFlowType -import com.amplifyframework.auth.exceptions.ServiceException +import com.amplifyframework.auth.cognito.requireIdentityClient import com.amplifyframework.statemachine.Action import com.amplifyframework.statemachine.codegen.actions.MigrateAuthActions import com.amplifyframework.statemachine.codegen.data.SignInMethod @@ -50,6 +48,8 @@ internal object MigrateAuthCognitoActions : MigrateAuthActions { configuration.userPool?.appClientSecret ) + val client = cognitoAuthService.requireIdentityClient() + val authParams = mutableMapOf(KEY_USERNAME to event.username, KEY_PASSWORD to event.password) secretHash?.let { authParams[KEY_SECRET_HASH] = it } @@ -59,7 +59,7 @@ internal object MigrateAuthCognitoActions : MigrateAuthActions { if (event.respondToAuthChallenge?.session != null) { authParams[KEY_ANSWER] = ChallengeNameType.Password.value - val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { + val response = client.respondToAuthChallenge { clientId = configuration.userPool?.appClient challengeName = ChallengeNameType.SelectChallenge this.challengeResponses = authParams @@ -69,21 +69,20 @@ internal object MigrateAuthCognitoActions : MigrateAuthActions { encodedContextData?.let { this.userContextData { encodedData = it } } } - response?.let { - SignInChallengeHelper.evaluateNextStep( - username = event.username, - challengeNameType = response.challengeName, - session = response.session, - challengeParameters = response.challengeParameters, - authenticationResult = response.authenticationResult, - signInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_AUTH) - ) - } ?: throw ServiceException("Sign in failed", AmplifyException.TODO_RECOVERY_SUGGESTION) + val username = AuthHelper.getActiveUsername(event.username, response) + SignInChallengeHelper.evaluateNextStep( + username = username, + challengeNameType = response.challengeName, + session = response.session, + challengeParameters = response.challengeParameters, + authenticationResult = response.authenticationResult, + signInMethod = SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_AUTH) + ) } else { if (event.authFlowType == AuthFlowType.USER_AUTH) { authParams[KEY_PREFERRED_CHALLENGE] = KEY_PASSWORD } - val response = cognitoAuthService.cognitoIdentityProviderClient?.initiateAuth { + val response = client.initiateAuth { authFlow = event.authFlowType.toCognitoType() clientId = configuration.userPool?.appClient authParameters = authParams @@ -92,28 +91,20 @@ internal object MigrateAuthCognitoActions : MigrateAuthActions { encodedContextData?.let { userContextData { encodedData = it } } } - response?.let { - val username = AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = response.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = response.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) - val signInMethod = if (event.authFlowType == AuthFlowType.USER_AUTH) { - SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_AUTH) - } else { - SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH) - } - SignInChallengeHelper.evaluateNextStep( - username = username, - challengeNameType = response.challengeName, - session = response.session, - challengeParameters = response.challengeParameters, - authenticationResult = response.authenticationResult, - signInMethod = signInMethod - ) - } ?: throw ServiceException("Sign in failed", AmplifyException.TODO_RECOVERY_SUGGESTION) + val username = AuthHelper.getActiveUsername(event.username, response) + val signInMethod = if (event.authFlowType == AuthFlowType.USER_AUTH) { + SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_AUTH) + } else { + SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH) + } + SignInChallengeHelper.evaluateNextStep( + username = username, + challengeNameType = response.challengeName, + session = response.session, + challengeParameters = response.challengeParameters, + authenticationResult = response.authenticationResult, + signInMethod = signInMethod + ) } } catch (e: Exception) { val errorEvent = SignInEvent(SignInEvent.EventType.ThrowError(e)) 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 317360ed9..d2c5e208d 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 @@ -26,6 +26,7 @@ import com.amplifyframework.auth.cognito.helpers.SRPHelper import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper import com.amplifyframework.auth.cognito.helpers.toCognitoType import com.amplifyframework.auth.cognito.options.AuthFlowType +import com.amplifyframework.auth.cognito.requireIdentityClient import com.amplifyframework.auth.exceptions.ServiceException import com.amplifyframework.auth.exceptions.UnknownException import com.amplifyframework.statemachine.Action @@ -62,6 +63,8 @@ internal object SRPCognitoActions : SRPActions { val evt = try { srpHelper = SRPHelper(event.password) + val client = cognitoAuthService.requireIdentityClient() + val secretHash = AuthHelper.getSecretHash( event.username, configuration.userPool?.appClient, @@ -79,7 +82,7 @@ internal object SRPCognitoActions : SRPActions { if (event.respondToAuthChallenge?.session != null) { authParams[KEY_ANSWER] = ChallengeNameType.PasswordSrp.value - val response = cognitoAuthService.cognitoIdentityProviderClient?.respondToAuthChallenge { + val response = client.respondToAuthChallenge { clientId = configuration.userPool?.appClient challengeName = ChallengeNameType.SelectChallenge this.challengeResponses = authParams @@ -90,19 +93,13 @@ internal object SRPCognitoActions : SRPActions { } val updatedDeviceMetadata = getDeviceMetadata( - AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = response?.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = response?.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) + AuthHelper.getActiveUsername(event.username, response) ) parseResponseChallenge( - challengeNameType = response?.challengeName, - challengeParams = response?.challengeParameters, - session = response?.session, + challengeNameType = response.challengeName, + challengeParams = response.challengeParameters, + session = response.session, updatedDeviceMetadata = updatedDeviceMetadata, metadata = event.metadata ) @@ -110,7 +107,7 @@ internal object SRPCognitoActions : SRPActions { if (event.authFlowType == AuthFlowType.USER_AUTH) { authParams[KEY_PREFERRED_CHALLENGE] = KEY_PASSWORD_SRP } - val initiateAuthResponse = cognitoAuthService.cognitoIdentityProviderClient?.initiateAuth { + val response = client.initiateAuth { authFlow = event.authFlowType.toCognitoType() clientId = configuration.userPool?.appClient authParameters = authParams @@ -118,21 +115,14 @@ internal object SRPCognitoActions : SRPActions { pinpointEndpointId?.let { analyticsMetadata { analyticsEndpointId = it } } encodedContextData?.let { userContextData { encodedData = it } } } - val updatedDeviceMetadata = getDeviceMetadata( - AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = initiateAuthResponse?.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = initiateAuthResponse?.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) + AuthHelper.getActiveUsername(event.username, response) ) parseResponseChallenge( - challengeNameType = initiateAuthResponse?.challengeName, - challengeParams = initiateAuthResponse?.challengeParameters, - session = initiateAuthResponse?.session, + challengeNameType = response.challengeName, + challengeParams = response.challengeParameters, + session = response.session, updatedDeviceMetadata = updatedDeviceMetadata, metadata = event.metadata ) diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCustomCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCustomCognitoActions.kt index 4e0d28dfd..f21001699 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCustomCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/SignInCustomCognitoActions.kt @@ -64,13 +64,7 @@ internal object SignInCustomCognitoActions : CustomSignInActions { if (initiateAuthResponse?.challengeName == ChallengeNameType.CustomChallenge && initiateAuthResponse.challengeParameters != null ) { - val activeUserName = AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = initiateAuthResponse.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = initiateAuthResponse.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) + val activeUserName = AuthHelper.getActiveUsername(event.username, initiateAuthResponse) SignInChallengeHelper.evaluateNextStep( username = activeUserName, challengeNameType = initiateAuthResponse.challengeName, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/UserAuthSignInCognitoActions.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/UserAuthSignInCognitoActions.kt index 303f7dd8e..333f6e7d9 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/UserAuthSignInCognitoActions.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/actions/UserAuthSignInCognitoActions.kt @@ -72,13 +72,7 @@ internal object UserAuthSignInCognitoActions : UserAuthSignInActions { resolvedSession != null && resolvedChallenges != null ) { - val activeUserName = AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = initiateAuthResponse.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = initiateAuthResponse.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) + val activeUserName = AuthHelper.getActiveUsername(event.username, initiateAuthResponse) val listOfChallenges = resolvedChallenges.map { it.value } @@ -95,13 +89,7 @@ internal object UserAuthSignInCognitoActions : UserAuthSignInActions { initiateAuthResponse?.challengeParameters != null && resolvedSession != null ) { - val activeUserName = AuthHelper.getActiveUsername( - username = event.username, - alternateUsername = initiateAuthResponse.challengeParameters?.get(KEY_USERNAME), - userIDForSRP = initiateAuthResponse.challengeParameters?.get( - KEY_USERID_FOR_SRP - ) - ) + val activeUserName = AuthHelper.getActiveUsername(event.username, initiateAuthResponse) SignInChallengeHelper.evaluateNextStep( username = activeUserName, diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt index 0f0128a07..88f13f281 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/AuthHelper.kt @@ -15,11 +15,19 @@ package com.amplifyframework.auth.cognito.helpers +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResultType +import aws.sdk.kotlin.services.cognitoidentityprovider.model.InitiateAuthResponse +import aws.sdk.kotlin.services.cognitoidentityprovider.model.RespondToAuthChallengeResponse import com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException import com.amplifyframework.auth.exceptions.UnknownException +import com.amplifyframework.statemachine.codegen.data.ChallengeParameter +import com.amplifyframework.statemachine.codegen.data.asAccessToken +import com.amplifyframework.statemachine.codegen.data.asIdToken import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec +private typealias ChallengeParameters = Map + internal open class AuthHelper { companion object { @@ -55,13 +63,30 @@ internal open class AuthHelper { } /** - * This method is used to capture the activeUsername we should be using to store and retreive device Metadata. + * This method is used to capture the activeUsername we should be using to store and retrieve device Metadata. * This becomes more important when an alias is used to signin instead of a username where cognito then * generates the username on the customers behalf and returns it. We need to then use that username to store * and retrieve device information as the username that the customer entered will no longer be available when * respondToAuthChallenge/confirmSignIn calls are made. * */ - fun getActiveUsername(username: String, alternateUsername: String?, userIDForSRP: String?): String = - alternateUsername ?: (userIDForSRP ?: username) + fun getActiveUsername(usernameSentInRequest: String, response: InitiateAuthResponse): String = + getActiveUsername(usernameSentInRequest, response.authenticationResult, response.challengeParameters) + fun getActiveUsername(usernameSentInRequest: String, response: RespondToAuthChallengeResponse): String = + getActiveUsername(usernameSentInRequest, response.authenticationResult, response.challengeParameters) + + private fun getActiveUsername( + usernameSentInRequest: String, + authenticationResult: AuthenticationResultType?, + challengeParameters: ChallengeParameters? + ): String = authenticationResult?.getUsername() ?: challengeParameters?.getUsername() ?: usernameSentInRequest + + private fun AuthenticationResultType.getUsername(): String? { + val idToken = idToken?.asIdToken() + val accessToken = accessToken?.asAccessToken() + return idToken?.username ?: accessToken?.username + } + + private fun ChallengeParameters.getUsername(): String? = + get(ChallengeParameter.Username.key) ?: get(ChallengeParameter.UserIdForSrp.key) } } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/ChallengeParameter.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/ChallengeParameter.kt index a9b1a8ce6..83ebeb754 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/ChallengeParameter.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/ChallengeParameter.kt @@ -23,5 +23,7 @@ internal enum class ChallengeParameter(val key: String) { CodeDeliveryMedium("CODE_DELIVERY_DELIVERY_MEDIUM"), CredentialRequestOptions("CREDENTIAL_REQUEST_OPTIONS"), MfasCanChoose("MFAS_CAN_CHOOSE"), - MfasCanSetup("MFAS_CAN_SETUP") + MfasCanSetup("MFAS_CAN_SETUP"), + Username("USERNAME"), + UserIdForSrp("USER_ID_FOR_SRP") } diff --git a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/Tokens.kt b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/Tokens.kt index 60d556111..d9236374d 100644 --- a/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/Tokens.kt +++ b/aws-auth-cognito/src/main/java/com/amplifyframework/statemachine/codegen/data/Tokens.kt @@ -65,14 +65,19 @@ internal abstract class Jwt { enum class Claim(val key: String) { Expiration("exp"), UserSub("sub"), - Username("username"), - TokenRevocationId("origin_jti") + Username("username"), // username claim in the access token + TokenRevocationId("origin_jti"), + CognitoUsername("cognito:username") // username claim in the identity token } } // See https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-id-token.html @Serializable internal class IdToken(override val tokenValue: String) : Jwt() { + val userSub: String? + get() = getClaim(Claim.UserSub) + val username: String? + get() = getClaim(Claim.CognitoUsername) val expiration: Instant? by lazy { getClaim(Claim.Expiration)?.let { Instant.ofEpochSecond(it.toLong()) } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt index 17f9f72d1..219c18320 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/AuthValidationTest.kt @@ -544,6 +544,7 @@ class AuthValidationTest { } private fun mockInitiateAuthSuccessResponse(username: String) = mockk { + every { authenticationResult } returns null every { challengeName } returns ChallengeNameType.PasswordVerifier every { challengeParameters } returns mapOf( "SALT" to "abc", @@ -564,36 +565,4 @@ class AuthValidationTest { every { newDeviceMetadata } returns null } } - - @Test - fun `test getActiveUsername returns correct username when active and userIDforSRP is null`() { - val username = AuthHelper.getActiveUsername("username", null, null) - assertEquals(username, "username") - } - - @Test - fun `getActiveUsername returns correct username when userIDforSRP is null & alternate is same as username`() { - val username = AuthHelper.getActiveUsername("username", "username", null) - assertEquals(username, "username") - } - - @Test - fun `getActiveUsername returns correct username when userIDforSRP is null & alternate is different as username`() { - val username = AuthHelper.getActiveUsername( - "username", - "userID12322", - null - ) - assertEquals(username, "userID12322") - } - - @Test - fun `test getActiveUsername returns correct username when userIDforSRP is not null null and alternate is null`() { - val username = AuthHelper.getActiveUsername( - "username", - null, - "userID12322" - ) - assertEquals(username, "userID12322") - } } diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActionsTest.kt index eff10369f..e793fde61 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActionsTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/MigrateAuthCognitoActionsTest.kt @@ -81,7 +81,8 @@ class MigrateAuthCognitoActionsTest { private val userId = "1234567890" private val dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g" + "RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o" - private val dummyIdToken = "id-token" + private val dummyIdToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g" + + "RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o" private val dummyRefreshToken = "refreshToken" private val bearerToken = "Bearer" private val dummySession = "session" diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActionsTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActionsTest.kt index b5a00e14e..40b43300c 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActionsTest.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/actions/SRPCognitoActionsTest.kt @@ -55,7 +55,8 @@ class SRPCognitoActionsTest { private val password = "password" private val dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g" + "RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o" - private val dummyIdToken = "id-token" + private val dummyIdToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g" + + "RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o" private val dummyRefreshToken = "refreshToken" private val bearerToken = "Bearer" private val dummySession = "session" diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/AuthHelperTest.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/AuthHelperTest.kt new file mode 100644 index 000000000..3f1fc6e74 --- /dev/null +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/AuthHelperTest.kt @@ -0,0 +1,113 @@ +/* + * 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.helpers + +import aws.sdk.kotlin.services.cognitoidentityprovider.model.InitiateAuthResponse +import com.amplifyframework.auth.cognito.testUtil.authenticationResult +import com.amplifyframework.statemachine.codegen.data.ChallengeParameter +import io.kotest.matchers.shouldBe +import org.junit.Test + +class AuthHelperTest { + // JWT with no username claims + private val tokenWithoutUsernames = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I" + + "kpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30" + + // JWT with both username and cognito:username claims + private val tokenWithUsernames = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG" + + "9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMiwidXNlcm5hbWUiOiJqd3RVc2VyIiwiY29nbml0bzp1c2VybmFtZSI6ImNv" + + "Z25pdG9Vc2VyIn0.TDAPYzxW2Vxp0pfhGyUDkwN8d0x55cWBzNqluQc1P-0" + + @Test + fun `getActiveUsername returns fallback username for empty initiateAuthResponse`() { + val response = initiateAuthResponse() + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "fallback" + } + + @Test + fun `getActiveUsername returns fallback username for response with no username claims`() { + val response = initiateAuthResponse( + idToken = tokenWithoutUsernames, + accessToken = tokenWithoutUsernames + ) + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "fallback" + } + + @Test + fun `getActiveUsername prefers username from idToken`() { + val response = initiateAuthResponse( + idToken = tokenWithUsernames, + accessToken = tokenWithUsernames, + usernameParameter = "challengeUsername", + userIdForSrpParameter = "userIdForSrp" + ) + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "cognitoUser" + } + + @Test + fun `getActiveUsername returns username from accessToken if idToken is missing`() { + val response = initiateAuthResponse( + accessToken = tokenWithUsernames, + usernameParameter = "challengeUsername", + userIdForSrpParameter = "userIdForSrp" + ) + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "jwtUser" + } + + @Test + fun `getActiveUsername returns username from username parameter if both tokens are missing`() { + val response = initiateAuthResponse( + usernameParameter = "challengeUsername", + userIdForSrpParameter = "userIdForSrp" + ) + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "challengeUsername" + } + + @Test + fun `getActiveUsername returns username from username srp id`() { + val response = initiateAuthResponse( + userIdForSrpParameter = "userIdForSrp" + ) + val username = AuthHelper.getActiveUsername("fallback", response) + username shouldBe "userIdForSrp" + } + + private fun initiateAuthResponse( + idToken: String? = null, + accessToken: String? = null, + usernameParameter: String? = null, + userIdForSrpParameter: String? = null + ) = InitiateAuthResponse { + if (idToken != null || accessToken != null) { + this.authenticationResult = authenticationResult( + idToken = idToken, + accessToken = accessToken + ) + } + if (usernameParameter != null || userIdForSrpParameter != null) { + this.challengeParameters = buildMap { + usernameParameter?.let { put(ChallengeParameter.Username.key, it) } + userIdForSrpParameter?.let { put(ChallengeParameter.UserIdForSrp.key, it) } + } + } + } +} diff --git a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/MockData.kt b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/MockData.kt index e9c98c790..93503506a 100644 --- a/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/MockData.kt +++ b/aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/testUtil/MockData.kt @@ -15,10 +15,12 @@ package com.amplifyframework.auth.cognito.testUtil +import aws.sdk.kotlin.services.cognitoidentityprovider.model.AuthenticationResultType import com.amplifyframework.statemachine.codegen.states.AuthState import com.amplifyframework.statemachine.codegen.states.AuthenticationState import com.amplifyframework.statemachine.codegen.states.AuthorizationState import com.amplifyframework.statemachine.codegen.states.SignUpState +import kotlin.time.Duration.Companion.minutes internal fun authState( authNState: AuthenticationState? = null, @@ -29,3 +31,15 @@ internal fun authState( authZState = authZState, authSignUpState = authSignUpState ) + +internal fun authenticationResult( + accessToken: String? = "accessToken", + idToken: String? = "idToken", + refreshToken: String? = "refreshToken", + expiresIn: Int = 60.minutes.inWholeSeconds.toInt() +) = AuthenticationResultType { + this.accessToken = accessToken + this.idToken = idToken + this.refreshToken = refreshToken + this.expiresIn = expiresIn +}