diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorConfiguration.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorConfiguration.kt index 6c6a72e..2af20ec 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorConfiguration.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorConfiguration.kt @@ -15,6 +15,7 @@ package com.amplifyframework.ui.authenticator +import com.amplifyframework.ui.authenticator.data.AuthenticationFlow import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.forms.SignUpFormBuilder import com.amplifyframework.ui.authenticator.options.TotpOptions @@ -22,5 +23,6 @@ import com.amplifyframework.ui.authenticator.options.TotpOptions internal data class AuthenticatorConfiguration( val initialStep: AuthenticatorInitialStep, val signUpForm: SignUpFormBuilder.() -> Unit, - val totpOptions: TotpOptions? + val totpOptions: TotpOptions?, + val authenticationFlow: AuthenticationFlow ) diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorState.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorState.kt index 92528be..c01445e 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorState.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorState.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.lifecycle.viewmodel.compose.viewModel +import com.amplifyframework.ui.authenticator.data.AuthenticationFlow import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.SignUpFormBuilder @@ -46,7 +47,8 @@ import kotlinx.coroutines.flow.onEach fun rememberAuthenticatorState( initialStep: AuthenticatorInitialStep = AuthenticatorStep.SignIn, signUpForm: SignUpFormBuilder.() -> Unit = {}, - totpOptions: TotpOptions? = null + totpOptions: TotpOptions? = null, + authenticationFlow: AuthenticationFlow = AuthenticationFlow.Password ): AuthenticatorState { val viewModel = viewModel() val scope = rememberCoroutineScope() @@ -54,7 +56,8 @@ fun rememberAuthenticatorState( val configuration = AuthenticatorConfiguration( initialStep = initialStep, signUpForm = signUpForm, - totpOptions = totpOptions + totpOptions = totpOptions, + authenticationFlow = authenticationFlow ) viewModel.start(configuration) @@ -102,9 +105,7 @@ interface AuthenticatorState { val messages: Flow } -internal class AuthenticatorStateImpl constructor( - private val viewModel: AuthenticatorViewModel -) : AuthenticatorState { +internal class AuthenticatorStateImpl constructor(private val viewModel: AuthenticatorViewModel) : AuthenticatorState { override var stepState by mutableStateOf(LoadingState) override val messages: Flow diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt index 4550851..ea1fc57 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt @@ -24,7 +24,7 @@ import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.MFAType import com.amplifyframework.auth.result.AuthSignOutResult import com.amplifyframework.auth.result.AuthWebAuthnCredential -import com.amplifyframework.ui.authenticator.enums.AuthFactor +import com.amplifyframework.ui.authenticator.data.AuthFactor import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep import com.amplifyframework.ui.authenticator.forms.MutableFormState diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt index 5930db3..d3bef64 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorViewModel.kt @@ -121,6 +121,14 @@ internal class AuthenticatorViewModel(application: Application, private val auth // Is there a current Amplify call in progress that could result in a signed in event? private var expectingSignInEvent: Boolean = false + // The current activity is used for WebAuthn sign-in when using passwordless functionality + private var activityReference: WeakReference = WeakReference(null) + var activity: Activity? + get() = activityReference.get() + set(value) { + activityReference = WeakReference(value) + } + fun start(configuration: AuthenticatorConfiguration) { if (::configuration.isInitialized) { return diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthFactor.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthFactor.kt index f2dca04..1db13d4 100644 --- a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthFactor.kt +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthFactor.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package com.amplifyframework.ui.authenticator.enums +package com.amplifyframework.ui.authenticator.data import com.amplifyframework.auth.AuthFactorType diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthenticationFlow.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthenticationFlow.kt new file mode 100644 index 0000000..a4b0a19 --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/AuthenticationFlow.kt @@ -0,0 +1,73 @@ +/* + * 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.ui.authenticator.data + +import com.amplifyframework.auth.cognito.options.AuthFlowType + +/** + * AuthenticationFlow represents the different styles of authentication supported by the Authenticator component. + */ +sealed interface AuthenticationFlow { + /** + * The standard password-based auth flow. The user will be prompted to enter a username and password on the SignIn + * screen. You can use this with either Password or PasswordSrp sign ins. + */ + data object Password : AuthenticationFlow + + /** + * A choice-based auth flow, where the user may log in via a password, a passkey, or a one-time-password (OTP) sent + * to their email or SMS. The user is first prompted to enter only their sign in attribute (username/email/phone) + * and then may be presented with options for how to log in. You must have ALLOW_USER_AUTH enabled as an + * authentication flow in your Cognito User Pool. + */ + data class UserChoice( + /** + * Specify an [AuthFactor] to use by default, if available to the user. + * + * For example, if you want any user with a registered passkey to sign in with that passkey without being + * prompted, then set this value to `AuthFactor.WebAuthn`. + * + * If this is null or the [AuthFactor] is not available to the user, they may go directly into a different + * [AuthFactor] (if they only have one available) or may be prompted to choose a factor (if they have multiple + * available). + * + * If this is set to [AuthFactor.Password] or [AuthFactor.PasswordSrp] then the user will be prompted for a + * password directly when signing in. Use these values only if you're certain that no users exist who don't + * have passwords. + */ + val preferredAuthFactor: AuthFactor? = null, + + /** + * Control when/if the user is prompted to create a passkey after logging in. + */ + val passkeyPrompts: PasskeyPrompts = PasskeyPrompts() + ) : AuthenticationFlow +} + +internal val AuthenticationFlow.signUpRequiresPassword: Boolean get() = when (this) { + is AuthenticationFlow.Password -> true + is AuthenticationFlow.UserChoice -> false +} + +internal val AuthenticationFlow.signInRequiresPassword: Boolean get() = when (this) { + is AuthenticationFlow.Password -> true + is AuthenticationFlow.UserChoice -> this.preferredAuthFactor is AuthFactor.Password +} + +internal fun AuthenticationFlow.toAuthFlowType() = when (this) { + is AuthenticationFlow.Password -> AuthFlowType.USER_SRP_AUTH + is AuthenticationFlow.UserChoice -> AuthFlowType.USER_AUTH +} diff --git a/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/PasskeyPrompt.kt b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/PasskeyPrompt.kt new file mode 100644 index 0000000..8cf753e --- /dev/null +++ b/authenticator/src/main/java/com/amplifyframework/ui/authenticator/data/PasskeyPrompt.kt @@ -0,0 +1,46 @@ +/* + * 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.ui.authenticator.data + +/** + * Class that contains configuration values for when/if to show prompts to create passkeys to the user. + */ +data class PasskeyPrompts( + /** + * Show a prompt after a user who does not have a passkey registered signs in to the application. + */ + val afterSignIn: PasskeyPrompt = PasskeyPrompt.Always, + /** + * Show a prompt to create a passkey after the automatic sign in following a new user signing up. + */ + val afterSignUp: PasskeyPrompt = PasskeyPrompt.Always +) + +/** + * Possible selections for controlling passkey prompts. + */ +sealed interface PasskeyPrompt { + /** + * Never prompt users to create a passkey after signing in. + */ + data object Never : PasskeyPrompt + + /** + * Always prompt users to create a passkey after signing in if they don't already have an existing registered + * passkey. + */ + data object Always : PasskeyPrompt +}