Skip to content

Commit 9dbd9f4

Browse files
chore(auth): Create use cases for device management APIs (#2979)
Co-authored-by: Tyler Roach <[email protected]>
1 parent de59bc4 commit 9dbd9f4

19 files changed

+489
-314
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,16 +297,16 @@ class AWSCognitoAuthPlugin : AuthPlugin<AWSCognitoAuthService>() {
297297
enqueue(onSuccess, onError) { queueFacade.fetchAuthSession() }
298298

299299
override fun rememberDevice(onSuccess: Action, onError: Consumer<AuthException>) =
300-
enqueue(onSuccess, onError) { queueFacade.rememberDevice() }
300+
enqueue(onSuccess, onError) { useCaseFactory.rememberDevice().execute() }
301301

302302
override fun forgetDevice(onSuccess: Action, onError: Consumer<AuthException>) =
303-
enqueue(onSuccess, onError) { queueFacade.forgetDevice() }
303+
enqueue(onSuccess, onError) { useCaseFactory.forgetDevice().execute() }
304304

305305
override fun forgetDevice(device: AuthDevice, onSuccess: Action, onError: Consumer<AuthException>) =
306-
enqueue(onSuccess, onError) { queueFacade.forgetDevice(device) }
306+
enqueue(onSuccess, onError) { useCaseFactory.forgetDevice().execute(device) }
307307

308308
override fun fetchDevices(onSuccess: Consumer<List<AuthDevice>>, onError: Consumer<AuthException>) =
309-
enqueue(onSuccess, onError) { queueFacade.fetchDevices() }
309+
enqueue(onSuccess, onError) { useCaseFactory.fetchDevices().execute() }
310310

311311
override fun resetPassword(
312312
username: String,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.amplifyframework.auth.cognito.actions.SignUpCognitoActions
3333
import com.amplifyframework.auth.cognito.actions.UserAuthSignInCognitoActions
3434
import com.amplifyframework.auth.cognito.actions.WebAuthnSignInCognitoActions
3535
import com.amplifyframework.auth.exceptions.InvalidStateException
36+
import com.amplifyframework.auth.exceptions.SignedOutException
3637
import com.amplifyframework.statemachine.Environment
3738
import com.amplifyframework.statemachine.StateMachine
3839
import com.amplifyframework.statemachine.StateMachineResolver
@@ -137,3 +138,12 @@ internal suspend inline fun <reified T : AuthenticationState> AuthStateMachine.r
137138
)
138139
}
139140
}
141+
142+
// Returns the SignedInState or throws SignedOutException or InvalidStateException
143+
internal suspend fun AuthStateMachine.requireSignedInState(): AuthenticationState.SignedIn {
144+
return when (val state = getCurrentState().authNState) {
145+
is AuthenticationState.SignedIn -> state
146+
is AuthenticationState.SignedOut -> throw SignedOutException()
147+
else -> throw InvalidStateException()
148+
}
149+
}

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

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package com.amplifyframework.auth.cognito
1818
import android.app.Activity
1919
import android.content.Intent
2020
import com.amplifyframework.auth.AuthCodeDeliveryDetails
21-
import com.amplifyframework.auth.AuthDevice
2221
import com.amplifyframework.auth.AuthProvider
2322
import com.amplifyframework.auth.AuthSession
2423
import com.amplifyframework.auth.AuthUser
@@ -259,43 +258,6 @@ internal class KotlinAuthFacadeInternal(private val delegate: RealAWSCognitoAuth
259258
}
260259
}
261260

262-
suspend fun rememberDevice() {
263-
return suspendCoroutine { continuation ->
264-
delegate.rememberDevice(
265-
{ continuation.resume(Unit) },
266-
{ continuation.resumeWithException(it) }
267-
)
268-
}
269-
}
270-
271-
suspend fun forgetDevice() {
272-
return suspendCoroutine { continuation ->
273-
delegate.forgetDevice(
274-
{ continuation.resume(Unit) },
275-
{ continuation.resumeWithException(it) }
276-
)
277-
}
278-
}
279-
280-
suspend fun forgetDevice(device: AuthDevice) {
281-
return suspendCoroutine { continuation ->
282-
delegate.forgetDevice(
283-
device,
284-
{ continuation.resume(Unit) },
285-
{ continuation.resumeWithException(it) }
286-
)
287-
}
288-
}
289-
290-
suspend fun fetchDevices(): List<AuthDevice> {
291-
return suspendCoroutine { continuation ->
292-
delegate.fetchDevices(
293-
{ continuation.resume(it) },
294-
{ continuation.resumeWithException(it) }
295-
)
296-
}
297-
}
298-
299261
suspend fun resetPassword(
300262
username: String
301263
): AuthResetPasswordResult {

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

Lines changed: 0 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,11 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.model.AnalyticsMetadataTy
2525
import aws.sdk.kotlin.services.cognitoidentityprovider.model.AttributeType
2626
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChallengeNameType
2727
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ChangePasswordRequest
28-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.DeviceRememberedStatusType
2928
import aws.sdk.kotlin.services.cognitoidentityprovider.model.EmailMfaSettingsType
30-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ForgetDeviceRequest
3129
import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserAttributeVerificationCodeRequest
3230
import aws.sdk.kotlin.services.cognitoidentityprovider.model.GetUserRequest
33-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.ListDevicesRequest
3431
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SmsMfaSettingsType
3532
import aws.sdk.kotlin.services.cognitoidentityprovider.model.SoftwareTokenMfaSettingsType
36-
import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateDeviceStatusRequest
3733
import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesRequest
3834
import aws.sdk.kotlin.services.cognitoidentityprovider.model.UpdateUserAttributesResponse
3935
import aws.sdk.kotlin.services.cognitoidentityprovider.model.VerifySoftwareTokenResponseType
@@ -48,7 +44,6 @@ import com.amplifyframework.auth.AWSCredentials
4844
import com.amplifyframework.auth.AWSTemporaryCredentials
4945
import com.amplifyframework.auth.AuthChannelEventName
5046
import com.amplifyframework.auth.AuthCodeDeliveryDetails
51-
import com.amplifyframework.auth.AuthDevice
5247
import com.amplifyframework.auth.AuthException
5348
import com.amplifyframework.auth.AuthFactorType
5449
import com.amplifyframework.auth.AuthProvider
@@ -1497,135 +1492,6 @@ internal class RealAWSCognitoAuthPlugin(
14971492
)
14981493
}
14991494

1500-
fun rememberDevice(onSuccess: Action, onError: Consumer<AuthException>) {
1501-
authStateMachine.getCurrentState { authState ->
1502-
when (val state = authState.authNState) {
1503-
is AuthenticationState.SignedIn -> {
1504-
GlobalScope.launch {
1505-
updateDevice(
1506-
authEnvironment.getDeviceMetadata(state.signedInData.username)?.deviceKey,
1507-
DeviceRememberedStatusType.Remembered,
1508-
onSuccess,
1509-
onError
1510-
)
1511-
}
1512-
}
1513-
is AuthenticationState.SignedOut -> {
1514-
onError.accept(SignedOutException())
1515-
}
1516-
else -> {
1517-
onError.accept(InvalidStateException())
1518-
}
1519-
}
1520-
}
1521-
}
1522-
1523-
fun forgetDevice(onSuccess: Action, onError: Consumer<AuthException>) {
1524-
forgetDevice(AuthDevice.fromId(""), onSuccess, onError)
1525-
}
1526-
1527-
private fun updateDevice(
1528-
alternateDeviceId: String?,
1529-
rememberedStatusType: DeviceRememberedStatusType,
1530-
onSuccess: Action,
1531-
onError: Consumer<AuthException>
1532-
) {
1533-
GlobalScope.async {
1534-
try {
1535-
val tokens = getSession().userPoolTokensResult
1536-
authEnvironment.cognitoAuthService.cognitoIdentityProviderClient?.updateDeviceStatus(
1537-
UpdateDeviceStatusRequest.invoke {
1538-
accessToken = tokens.value?.accessToken
1539-
deviceKey = alternateDeviceId
1540-
deviceRememberedStatus = rememberedStatusType
1541-
}
1542-
)
1543-
onSuccess.call()
1544-
} catch (e: Exception) {
1545-
onError.accept(CognitoAuthExceptionConverter.lookup(e, "Update device ID failed."))
1546-
}
1547-
}
1548-
}
1549-
1550-
fun forgetDevice(device: AuthDevice, onSuccess: Action, onError: Consumer<AuthException>) {
1551-
authStateMachine.getCurrentState { authState ->
1552-
when (val authNState = authState.authNState) {
1553-
is AuthenticationState.SignedIn -> {
1554-
GlobalScope.launch {
1555-
try {
1556-
if (device.id.isEmpty()) {
1557-
val deviceKey = authEnvironment.getDeviceMetadata(authNState.signedInData.username)
1558-
?.deviceKey
1559-
forgetDevice(deviceKey)
1560-
} else {
1561-
forgetDevice(device.id)
1562-
}
1563-
onSuccess.call()
1564-
} catch (e: Exception) {
1565-
onError.accept(CognitoAuthExceptionConverter.lookup(e, "Failed to forget device."))
1566-
}
1567-
}
1568-
}
1569-
is AuthenticationState.SignedOut -> {
1570-
onError.accept(SignedOutException())
1571-
}
1572-
else -> {
1573-
onError.accept(InvalidStateException())
1574-
}
1575-
}
1576-
}
1577-
}
1578-
1579-
private suspend fun forgetDevice(alternateDeviceId: String?) {
1580-
val tokens = getSession().userPoolTokensResult
1581-
authEnvironment.cognitoAuthService.cognitoIdentityProviderClient?.forgetDevice(
1582-
ForgetDeviceRequest.invoke {
1583-
accessToken = tokens.value?.accessToken
1584-
deviceKey = alternateDeviceId
1585-
}
1586-
)
1587-
}
1588-
1589-
fun fetchDevices(onSuccess: Consumer<List<AuthDevice>>, onError: Consumer<AuthException>) {
1590-
authStateMachine.getCurrentState { authState ->
1591-
when (authState.authNState) {
1592-
is AuthenticationState.SignedIn -> {
1593-
_fetchDevices(onSuccess, onError)
1594-
}
1595-
is AuthenticationState.SignedOut -> {
1596-
onError.accept(SignedOutException())
1597-
}
1598-
else -> {
1599-
onError.accept(InvalidStateException())
1600-
}
1601-
}
1602-
}
1603-
}
1604-
1605-
private fun _fetchDevices(onSuccess: Consumer<List<AuthDevice>>, onError: Consumer<AuthException>) {
1606-
GlobalScope.async {
1607-
try {
1608-
val tokens = getSession().userPoolTokensResult
1609-
val response =
1610-
authEnvironment.cognitoAuthService.cognitoIdentityProviderClient?.listDevices(
1611-
ListDevicesRequest.invoke {
1612-
accessToken = tokens.value?.accessToken
1613-
}
1614-
)
1615-
1616-
val devices = response?.devices?.map { device ->
1617-
val id = device.deviceKey ?: ""
1618-
val name = device.deviceAttributes?.find { it.name == "device_name" }?.value
1619-
AuthDevice.fromId(id, name)
1620-
} ?: emptyList()
1621-
1622-
onSuccess.accept(devices)
1623-
} catch (e: Exception) {
1624-
onError.accept(CognitoAuthExceptionConverter.lookup(e, "Fetch devices failed."))
1625-
}
1626-
}
1627-
}
1628-
16291495
@OptIn(DelicateCoroutinesApi::class)
16301496
fun resetPassword(
16311497
username: String,

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ import aws.sdk.kotlin.services.cognitoidentityprovider.startWebAuthnRegistration
2222
import com.amplifyframework.auth.cognito.AuthStateMachine
2323
import com.amplifyframework.auth.cognito.helpers.WebAuthnHelper
2424
import com.amplifyframework.auth.cognito.helpers.authLogger
25-
import com.amplifyframework.auth.cognito.requireAuthenticationState
25+
import com.amplifyframework.auth.cognito.requireSignedInState
2626
import com.amplifyframework.auth.options.AuthAssociateWebAuthnCredentialsOptions
27-
import com.amplifyframework.statemachine.codegen.states.AuthenticationState.SignedIn
2827
import com.amplifyframework.statemachine.util.mask
2928
import com.amplifyframework.util.JsonDocument
3029
import com.amplifyframework.util.toJsonString
@@ -40,7 +39,7 @@ internal class AssociateWebAuthnCredentialUseCase(
4039
@Suppress("UNUSED_PARAMETER")
4140
suspend fun execute(callingActivity: Activity, options: AuthAssociateWebAuthnCredentialsOptions) {
4241
// User must be signed in to call this API
43-
stateMachine.requireAuthenticationState<SignedIn>()
42+
stateMachine.requireSignedInState()
4443

4544
val accessToken = fetchAuthSession.execute().accessToken
4645

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,24 @@ internal class AuthUseCaseFactory(
4747
fetchAuthSession = fetchAuthSession(),
4848
stateMachine = stateMachine
4949
)
50+
51+
fun rememberDevice() = RememberDeviceUseCase(
52+
client = authEnvironment.requireIdentityProviderClient(),
53+
fetchAuthSession = fetchAuthSession(),
54+
stateMachine = stateMachine,
55+
environment = authEnvironment
56+
)
57+
58+
fun forgetDevice() = ForgetDeviceUseCase(
59+
client = authEnvironment.requireIdentityProviderClient(),
60+
fetchAuthSession = fetchAuthSession(),
61+
stateMachine = stateMachine,
62+
environment = authEnvironment
63+
)
64+
65+
fun fetchDevices() = FetchDevicesUseCase(
66+
client = authEnvironment.requireIdentityProviderClient(),
67+
fetchAuthSession = fetchAuthSession(),
68+
stateMachine = stateMachine
69+
)
5070
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ package com.amplifyframework.auth.cognito.usecases
1818
import aws.sdk.kotlin.services.cognitoidentityprovider.CognitoIdentityProviderClient
1919
import aws.sdk.kotlin.services.cognitoidentityprovider.deleteWebAuthnCredential
2020
import com.amplifyframework.auth.cognito.AuthStateMachine
21-
import com.amplifyframework.auth.cognito.requireAuthenticationState
21+
import com.amplifyframework.auth.cognito.requireSignedInState
2222
import com.amplifyframework.auth.options.AuthDeleteWebAuthnCredentialOptions
23-
import com.amplifyframework.statemachine.codegen.states.AuthenticationState.SignedIn
2423

2524
internal class DeleteWebAuthnCredentialUseCase(
2625
private val client: CognitoIdentityProviderClient,
@@ -30,7 +29,7 @@ internal class DeleteWebAuthnCredentialUseCase(
3029
@Suppress("UNUSED_PARAMETER")
3130
suspend fun execute(credentialId: String, options: AuthDeleteWebAuthnCredentialOptions) {
3231
// User must be signed in to call this API
33-
stateMachine.requireAuthenticationState<SignedIn>()
32+
stateMachine.requireSignedInState()
3433

3534
val accessToken = fetchAuthSession.execute().accessToken
3635
client.deleteWebAuthnCredential {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.listDevices
20+
import com.amplifyframework.auth.AuthDevice
21+
import com.amplifyframework.auth.cognito.AuthStateMachine
22+
import com.amplifyframework.auth.cognito.requireSignedInState
23+
24+
internal class FetchDevicesUseCase(
25+
private val client: CognitoIdentityProviderClient,
26+
private val fetchAuthSession: FetchAuthSessionUseCase,
27+
private val stateMachine: AuthStateMachine
28+
) {
29+
suspend fun execute(): List<AuthDevice> {
30+
stateMachine.requireSignedInState()
31+
val token = fetchAuthSession.execute().accessToken
32+
val response = client.listDevices { accessToken = token }
33+
return response.devices?.map { device ->
34+
val id = device.deviceKey ?: ""
35+
val name = device.deviceAttributes?.find { it.name == "device_name" }?.value
36+
AuthDevice.fromId(id, name)
37+
} ?: emptyList()
38+
}
39+
}

0 commit comments

Comments
 (0)