Skip to content

Commit 96a6dfa

Browse files
authored
chore(auth): Move MFA Preferences to use cases (#3007)
1 parent 469258f commit 96a6dfa

File tree

11 files changed

+701
-1335
lines changed

11 files changed

+701
-1335
lines changed

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/AWSCognitoAuthPlugin.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,23 +526,35 @@ class AWSCognitoAuthPlugin : AuthPlugin<AWSCognitoAuthService>() {
526526
enqueue(onSuccess, onError) { queueFacade.clearFederationToIdentityPool() }
527527

528528
fun fetchMFAPreference(onSuccess: Consumer<UserMFAPreference>, onError: Consumer<AuthException>) =
529-
enqueue(onSuccess, onError) { queueFacade.fetchMFAPreference() }
529+
enqueue(onSuccess, onError) { useCaseFactory.fetchMfaPreference().execute() }
530530

531531
@Deprecated("Use updateMFAPreference(sms, totp, email, onSuccess, onError) instead")
532532
fun updateMFAPreference(
533533
sms: MFAPreference?,
534534
totp: MFAPreference?,
535535
onSuccess: Action,
536536
onError: Consumer<AuthException>
537-
) = enqueue(onSuccess, onError) { queueFacade.updateMFAPreference(sms, totp, null) }
537+
) = enqueue(onSuccess, onError) {
538+
useCaseFactory.updateMfaPreference().execute(
539+
sms = sms,
540+
totp = totp,
541+
email = null
542+
)
543+
}
538544

539545
fun updateMFAPreference(
540546
sms: MFAPreference? = null,
541547
totp: MFAPreference? = null,
542548
email: MFAPreference? = null,
543549
onSuccess: Action,
544550
onError: Consumer<AuthException>
545-
) = enqueue(onSuccess, onError) { queueFacade.updateMFAPreference(sms, totp, email) }
551+
) = enqueue(onSuccess, onError) {
552+
useCaseFactory.updateMfaPreference().execute(
553+
sms = sms,
554+
totp = totp,
555+
email = email
556+
)
557+
}
546558

547559
private fun enqueue(onSuccess: Action, onError: Consumer<AuthException>, block: suspend () -> Unit) =
548560
enqueue({ onSuccess.call() }, onError::accept, block)

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/KotlinAuthFacadeInternal.kt

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -169,24 +169,6 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth
169169
)
170170
}
171171

172-
suspend fun fetchMFAPreference(): UserMFAPreference = suspendCoroutine { continuation ->
173-
delegate.fetchMFAPreference(
174-
{ continuation.resume(it) },
175-
{ continuation.resumeWithException(it) }
176-
)
177-
}
178-
179-
suspend fun updateMFAPreference(sms: MFAPreference?, totp: MFAPreference?, email: MFAPreference?) =
180-
suspendCoroutine { continuation ->
181-
delegate.updateMFAPreference(
182-
sms,
183-
totp,
184-
email,
185-
{ continuation.resume(Unit) },
186-
{ continuation.resumeWithException(it) }
187-
)
188-
}
189-
190172
suspend fun autoSignIn(): AuthSignInResult = suspendCoroutine { continuation ->
191173
delegate.autoSignIn(
192174
{ continuation.resume(it) },

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/RealAWSCognitoAuthPlugin.kt

Lines changed: 0 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@ package com.amplifyframework.auth.cognito
1818
import android.app.Activity
1919
import android.content.Intent
2020
import androidx.annotation.WorkerThread
21-
import aws.sdk.kotlin.services.cognitoidentityprovider.getUser
2221
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType
23-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType
24-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType
25-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType
26-
import aws.sdk.kotlin.services.cognitoidentityprovider.setUserMfaPreference
2722
import com.amplifyframework.AmplifyException
2823
import com.amplifyframework.annotations.InternalAmplifyApi
2924
import com.amplifyframework.auth.AWSCognitoAuthMetadataType
@@ -47,7 +42,6 @@ import com.amplifyframework.auth.cognito.helpers.HostedUIHelper
4742
import com.amplifyframework.auth.cognito.helpers.SignInChallengeHelper
4843
import com.amplifyframework.auth.cognito.helpers.getAllowedMFATypesFromChallengeParameters
4944
import com.amplifyframework.auth.cognito.helpers.getMFASetupTypeOrNull
50-
import com.amplifyframework.auth.cognito.helpers.getMFAType
5145
import com.amplifyframework.auth.cognito.helpers.getMFATypeOrNull
5246
import com.amplifyframework.auth.cognito.helpers.identityProviderName
5347
import com.amplifyframework.auth.cognito.helpers.isMfaSetupSelectionChallenge
@@ -1566,139 +1560,6 @@ internal class RealAWSCognitoAuthPlugin(
15661560
}
15671561
}
15681562

1569-
fun fetchMFAPreference(onSuccess: Consumer<UserMFAPreference>, onError: Consumer<AuthException>) {
1570-
authStateMachine.getCurrentState { authState ->
1571-
when (authState.authNState) {
1572-
is AuthenticationState.SignedIn -> {
1573-
GlobalScope.launch {
1574-
try {
1575-
val accessToken = getSession().userPoolTokensResult.value?.accessToken
1576-
accessToken?.let { token ->
1577-
authEnvironment.cognitoAuthService
1578-
.cognitoIdentityProviderClient?.getUser {
1579-
this.accessToken = token
1580-
}?.also { response ->
1581-
var enabledSet: MutableSet<MFAType>? = null
1582-
var preferred: MFAType? = null
1583-
if (!response.userMfaSettingList.isNullOrEmpty()) {
1584-
enabledSet = mutableSetOf()
1585-
response.userMfaSettingList?.forEach { mfaType ->
1586-
enabledSet.add(getMFAType(mfaType))
1587-
}
1588-
}
1589-
response.preferredMfaSetting?.let { preferredMFA ->
1590-
preferred = getMFAType(preferredMFA)
1591-
}
1592-
onSuccess.accept(UserMFAPreference(enabledSet, preferred))
1593-
}
1594-
} ?: onError.accept(SignedOutException())
1595-
} catch (error: Exception) {
1596-
onError.accept(
1597-
CognitoAuthExceptionConverter.lookup(
1598-
error,
1599-
"Cannot update the MFA preferences"
1600-
)
1601-
)
1602-
}
1603-
}
1604-
}
1605-
1606-
else -> onError.accept(InvalidStateException())
1607-
}
1608-
}
1609-
}
1610-
1611-
fun updateMFAPreference(
1612-
sms: MFAPreference?,
1613-
totp: MFAPreference?,
1614-
email: MFAPreference?,
1615-
onSuccess: Action,
1616-
onError: Consumer<AuthException>
1617-
) {
1618-
if (sms == null && totp == null && email == null) {
1619-
onError.accept(InvalidParameterException("No mfa settings given"))
1620-
return
1621-
}
1622-
// If either of the params have preferred setting set then ignore fetched preference preferred property
1623-
val overridePreferredSetting: Boolean = !(sms?.mfaPreferred == true || totp?.mfaPreferred == true)
1624-
fetchMFAPreference({ userPreference ->
1625-
authStateMachine.getCurrentState { authState ->
1626-
when (authState.authNState) {
1627-
is AuthenticationState.SignedIn -> {
1628-
GlobalScope.launch {
1629-
try {
1630-
val accessToken = getSession().userPoolTokensResult.value?.accessToken
1631-
accessToken?.let { token ->
1632-
authEnvironment
1633-
.cognitoAuthService
1634-
.cognitoIdentityProviderClient
1635-
?.setUserMfaPreference {
1636-
this.accessToken = token
1637-
this.smsMfaSettings = sms?.let {
1638-
val preferredMFASetting = it.mfaPreferred
1639-
?: (
1640-
overridePreferredSetting &&
1641-
userPreference.preferred == MFAType.SMS &&
1642-
it.mfaEnabled
1643-
)
1644-
SmsMfaSettingsType.invoke {
1645-
enabled = it.mfaEnabled
1646-
preferredMfa = preferredMFASetting
1647-
}
1648-
}
1649-
this.softwareTokenMfaSettings = totp?.let {
1650-
val preferredMFASetting = it.mfaPreferred
1651-
?: (
1652-
overridePreferredSetting &&
1653-
userPreference.preferred == MFAType.TOTP &&
1654-
it.mfaEnabled
1655-
)
1656-
SoftwareTokenMfaSettingsType.invoke {
1657-
enabled = it.mfaEnabled
1658-
preferredMfa = preferredMFASetting
1659-
}
1660-
}
1661-
this.emailMfaSettings = email?.let {
1662-
val preferredMFASetting = it.mfaPreferred
1663-
?: (
1664-
overridePreferredSetting &&
1665-
userPreference.preferred == MFAType.EMAIL &&
1666-
it.mfaEnabled
1667-
)
1668-
EmailMfaSettingsType.invoke {
1669-
enabled = it.mfaEnabled
1670-
preferredMfa = preferredMFASetting
1671-
}
1672-
}
1673-
}?.also {
1674-
onSuccess.call()
1675-
}
1676-
} ?: onError.accept(SignedOutException())
1677-
} catch (error: Exception) {
1678-
onError.accept(
1679-
CognitoAuthExceptionConverter.lookup(
1680-
error,
1681-
"Amazon Cognito cannot update the MFA preferences"
1682-
)
1683-
)
1684-
}
1685-
}
1686-
}
1687-
else -> onError.accept(InvalidStateException())
1688-
}
1689-
}
1690-
}, {
1691-
onError.accept(
1692-
AuthException(
1693-
message = "Failed to fetch current MFA preferences " +
1694-
"which is a pre-requisite to update MFA preferences",
1695-
recoverySuggestion = AmplifyException.TODO_RECOVERY_SUGGESTION,
1696-
cause = it
1697-
)
1698-
)
1699-
})
1700-
}
1701-
17021563
private fun _clearFederationToIdentityPool(onSuccess: Action, onError: Consumer<AuthException>) {
17031564
_signOut(sendHubEvent = false) {
17041565
when (it) {

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/UserMFAPreference.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,12 @@ import com.amplifyframework.auth.MFAType
2222
* @param enabled MFA types
2323
* @param preferred MFA type. null if not set
2424
*/
25-
data class UserMFAPreference(
26-
val enabled: Set<MFAType>?,
27-
val preferred: MFAType?
28-
)
25+
data class UserMFAPreference(val enabled: Set<MFAType>?, val preferred: MFAType?)
2926

3027
/**
3128
* Input for updating the MFA preference for a MFA Type
3229
*/
33-
enum class MFAPreference(
34-
internal val mfaEnabled: Boolean,
35-
internal val mfaPreferred: Boolean? = null
36-
) {
30+
enum class MFAPreference(internal val mfaEnabled: Boolean, internal val mfaPreferred: Boolean? = null) {
3731
/**
3832
* MFA not enabled
3933
*/

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/usecases/AuthUseCaseFactory.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,17 @@ internal class AuthUseCaseFactory(
139139
environment = authEnvironment,
140140
stateMachine = stateMachine
141141
)
142+
143+
fun fetchMfaPreference() = FetchMfaPreferenceUseCase(
144+
client = authEnvironment.requireIdentityProviderClient(),
145+
fetchAuthSession = fetchAuthSession(),
146+
stateMachine = stateMachine
147+
)
148+
149+
fun updateMfaPreference() = UpdateMfaPreferenceUseCase(
150+
client = authEnvironment.requireIdentityProviderClient(),
151+
fetchAuthSession = fetchAuthSession(),
152+
fetchMfaPreference = fetchMfaPreference(),
153+
stateMachine = stateMachine
154+
)
142155
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.auth.cognito.usecases
17+
18+
import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient
19+
import aws.sdk.kotlin.services.cognitoidentityprovider.getUser
20+
import com.amplifyframework.auth.cognito.AuthStateMachine
21+
import com.amplifyframework.auth.cognito.UserMFAPreference
22+
import com.amplifyframework.auth.cognito.helpers.getMFAType
23+
import com.amplifyframework.auth.cognito.requireAccessToken
24+
import com.amplifyframework.auth.cognito.requireSignedInState
25+
26+
internal class FetchMfaPreferenceUseCase(
27+
private val client: CognitoIdentityProviderClient,
28+
private val fetchAuthSession: FetchAuthSessionUseCase,
29+
private val stateMachine: AuthStateMachine
30+
) {
31+
suspend fun execute(): UserMFAPreference {
32+
stateMachine.requireSignedInState()
33+
val token = fetchAuthSession.execute().requireAccessToken()
34+
35+
val response = client.getUser { accessToken = token }
36+
37+
val enabled = response.userMfaSettingList?.map { getMFAType(it) }?.toSet()
38+
val preferred = response.preferredMfaSetting?.let { getMFAType(it) }
39+
40+
return UserMFAPreference(enabled = enabled, preferred = preferred)
41+
}
42+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.auth.cognito.usecases
17+
18+
import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient
19+
import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType
20+
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType
21+
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType
22+
import aws.sdk.kotlin.services.cognitoidentityprovider.setUserMfaPreference
23+
import com.amplifyframework.auth.MFAType
24+
import com.amplifyframework.auth.cognito.AuthStateMachine
25+
import com.amplifyframework.auth.cognito.MFAPreference
26+
import com.amplifyframework.auth.cognito.UserMFAPreference
27+
import com.amplifyframework.auth.cognito.exceptions.service.InvalidParameterException
28+
import com.amplifyframework.auth.cognito.requireAccessToken
29+
import com.amplifyframework.auth.cognito.requireSignedInState
30+
31+
internal class UpdateMfaPreferenceUseCase(
32+
private val client: CognitoIdentityProviderClient,
33+
private val fetchAuthSession: FetchAuthSessionUseCase,
34+
private val fetchMfaPreference: FetchMfaPreferenceUseCase,
35+
private val stateMachine: AuthStateMachine
36+
) {
37+
suspend fun execute(sms: MFAPreference?, totp: MFAPreference?, email: MFAPreference?) {
38+
if (sms == null && totp == null && email == null) {
39+
throw InvalidParameterException("No mfa settings given")
40+
}
41+
42+
stateMachine.requireSignedInState()
43+
val token = fetchAuthSession.execute().requireAccessToken()
44+
45+
// If none of the params are marked as preferred then keep the existing preferred property
46+
val keepExistingPreference = !(
47+
sms?.mfaPreferred == true ||
48+
totp?.mfaPreferred == true ||
49+
email?.mfaPreferred == true
50+
)
51+
val existingPreference = fetchMfaPreference.execute()
52+
53+
client.setUserMfaPreference {
54+
accessToken = token
55+
smsMfaSettings = sms?.let {
56+
val preferred = isPreferred(MFAType.SMS, sms, keepExistingPreference, existingPreference)
57+
SmsMfaSettingsType {
58+
enabled = it.mfaEnabled
59+
preferredMfa = preferred
60+
}
61+
}
62+
softwareTokenMfaSettings = totp?.let {
63+
val preferred = isPreferred(MFAType.TOTP, totp, keepExistingPreference, existingPreference)
64+
SoftwareTokenMfaSettingsType {
65+
enabled = it.mfaEnabled
66+
preferredMfa = preferred
67+
}
68+
}
69+
emailMfaSettings = email?.let {
70+
val preferred = isPreferred(MFAType.EMAIL, email, keepExistingPreference, existingPreference)
71+
EmailMfaSettingsType {
72+
enabled = it.mfaEnabled
73+
preferredMfa = preferred
74+
}
75+
}
76+
}
77+
}
78+
79+
// If preference.mfaPreferred is set then we use that. Otherwise, we will keep the existing preference if
80+
// keepExistingPreference is true and the preference is enabled
81+
private fun isPreferred(
82+
type: MFAType,
83+
preference: MFAPreference,
84+
keepExistingPreference: Boolean,
85+
existingPreference: UserMFAPreference
86+
): Boolean = preference.mfaPreferred ?: (
87+
keepExistingPreference &&
88+
existingPreference.preferred == type &&
89+
preference.mfaEnabled
90+
)
91+
}

0 commit comments

Comments
 (0)