Skip to content

Commit 9be1f5d

Browse files
authored
feat(authenticator): Add passwordless sign in flows (#287)
1 parent 2363d52 commit 9be1f5d

File tree

84 files changed

+3020
-410
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+3020
-410
lines changed

authenticator/api/authenticator.api

Lines changed: 281 additions & 32 deletions
Large diffs are not rendered by default.

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorConfiguration.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515

1616
package com.amplifyframework.ui.authenticator
1717

18+
import com.amplifyframework.ui.authenticator.data.AuthenticationFlow
1819
import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep
1920
import com.amplifyframework.ui.authenticator.forms.SignUpFormBuilder
2021
import com.amplifyframework.ui.authenticator.options.TotpOptions
2122

2223
internal data class AuthenticatorConfiguration(
2324
val initialStep: AuthenticatorInitialStep,
2425
val signUpForm: SignUpFormBuilder.() -> Unit,
25-
val totpOptions: TotpOptions?
26+
val totpOptions: TotpOptions?,
27+
val authenticationFlow: AuthenticationFlow
2628
)

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorState.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ import androidx.compose.runtime.mutableStateOf
2222
import androidx.compose.runtime.remember
2323
import androidx.compose.runtime.rememberCoroutineScope
2424
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.platform.LocalContext
2526
import androidx.lifecycle.viewmodel.compose.viewModel
27+
import com.amplifyframework.ui.authenticator.data.AuthenticationFlow
2628
import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep
2729
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
2830
import com.amplifyframework.ui.authenticator.forms.SignUpFormBuilder
2931
import com.amplifyframework.ui.authenticator.options.TotpOptions
3032
import com.amplifyframework.ui.authenticator.util.AuthenticatorMessage
33+
import com.amplifyframework.ui.authenticator.util.findActivity
3134
import kotlinx.coroutines.flow.Flow
3235
import kotlinx.coroutines.flow.launchIn
3336
import kotlinx.coroutines.flow.onEach
@@ -46,18 +49,22 @@ import kotlinx.coroutines.flow.onEach
4649
fun rememberAuthenticatorState(
4750
initialStep: AuthenticatorInitialStep = AuthenticatorStep.SignIn,
4851
signUpForm: SignUpFormBuilder.() -> Unit = {},
49-
totpOptions: TotpOptions? = null
52+
totpOptions: TotpOptions? = null,
53+
authenticationFlow: AuthenticationFlow = AuthenticationFlow.Password
5054
): AuthenticatorState {
5155
val viewModel = viewModel<AuthenticatorViewModel>()
5256
val scope = rememberCoroutineScope()
57+
val context = LocalContext.current
58+
5359
return remember {
5460
val configuration = AuthenticatorConfiguration(
5561
initialStep = initialStep,
5662
signUpForm = signUpForm,
57-
totpOptions = totpOptions
63+
totpOptions = totpOptions,
64+
authenticationFlow = authenticationFlow
5865
)
5966

60-
viewModel.start(configuration)
67+
viewModel.start(configuration, context.findActivity())
6168
AuthenticatorStateImpl(viewModel).also { state ->
6269
viewModel.stepState.onEach { state.stepState = it }.launchIn(scope)
6370
}
@@ -102,9 +109,7 @@ interface AuthenticatorState {
102109
val messages: Flow<AuthenticatorMessage>
103110
}
104111

105-
internal class AuthenticatorStateImpl constructor(
106-
private val viewModel: AuthenticatorViewModel
107-
) : AuthenticatorState {
112+
internal class AuthenticatorStateImpl constructor(private val viewModel: AuthenticatorViewModel) : AuthenticatorState {
108113
override var stepState by mutableStateOf<AuthenticatorStepState>(LoadingState)
109114

110115
override val messages: Flow<AuthenticatorMessage>

authenticator/src/main/java/com/amplifyframework/ui/authenticator/AuthenticatorStepState.kt

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import com.amplifyframework.auth.AuthUser
2323
import com.amplifyframework.auth.AuthUserAttribute
2424
import com.amplifyframework.auth.MFAType
2525
import com.amplifyframework.auth.result.AuthSignOutResult
26+
import com.amplifyframework.auth.result.AuthWebAuthnCredential
27+
import com.amplifyframework.ui.authenticator.data.AuthFactor
2628
import com.amplifyframework.ui.authenticator.enums.AuthenticatorInitialStep
2729
import com.amplifyframework.ui.authenticator.enums.AuthenticatorStep
2830
import com.amplifyframework.ui.authenticator.forms.MutableFormState
@@ -38,6 +40,17 @@ interface AuthenticatorStepState {
3840
val step: AuthenticatorStep
3941
}
4042

43+
/**
44+
* A state holder for the UI that has multiple possible actions that may be in progress.
45+
*/
46+
@Stable
47+
interface AuthenticatorActionState<T> {
48+
/**
49+
* The action in progress, or null if state is idle
50+
*/
51+
val action: T?
52+
}
53+
4154
/**
4255
* The Authenticator is loading the current state of the user's Auth session.
4356
*/
@@ -93,6 +106,73 @@ interface SignInState : AuthenticatorStepState {
93106
suspend fun signIn()
94107
}
95108

109+
/**
110+
* The user has entered their username and must select the authentication factor they'd like to use to sign in
111+
*/
112+
@Stable
113+
interface SignInSelectAuthFactorState :
114+
AuthenticatorStepState,
115+
AuthenticatorActionState<SignInSelectAuthFactorState.Action> {
116+
117+
sealed interface Action {
118+
/**
119+
* User has selected an auth factor
120+
*/
121+
data class SelectAuthFactor(val factor: AuthFactor) : Action
122+
}
123+
124+
/**
125+
* The input form state holder for this step.
126+
*/
127+
val form: MutableFormState
128+
129+
/**
130+
* The username entered in the SignIn step
131+
*/
132+
val username: String
133+
134+
/**
135+
* The available types to select how to sign in.
136+
*/
137+
val availableAuthFactors: Set<AuthFactor>
138+
139+
/**
140+
* Move the user to a different [AuthenticatorInitialStep].
141+
*/
142+
fun moveTo(step: AuthenticatorInitialStep)
143+
144+
/**
145+
* Initiate a sign in with one of the available sign in types
146+
*/
147+
suspend fun select(authFactor: AuthFactor)
148+
}
149+
150+
/**
151+
* A user has entered their username and must enter their password to continue signing in
152+
*/
153+
@Stable
154+
interface SignInConfirmPasswordState : AuthenticatorStepState {
155+
/**
156+
* The input form state holder for this step.
157+
*/
158+
val form: MutableFormState
159+
160+
/**
161+
* The username entered in the SignIn step
162+
*/
163+
val username: String
164+
165+
/**
166+
* Move the user to a different [AuthenticatorInitialStep].
167+
*/
168+
fun moveTo(step: AuthenticatorInitialStep)
169+
170+
/**
171+
* Initiate a sign in with the information entered into the [form].
172+
*/
173+
suspend fun signIn()
174+
}
175+
96176
/**
97177
* The user has completed the initial Sign In step, and needs to enter the confirmation code from an MFA
98178
* message to complete the sign in process.
@@ -460,3 +540,59 @@ interface VerifyUserConfirmState : AuthenticatorStepState {
460540
*/
461541
fun skip()
462542
}
543+
544+
/**
545+
* The user is being shown a prompt to create a passkey, encouraging them to use this as a way to sign in quickly
546+
* via biometrics
547+
*/
548+
@Stable
549+
interface PromptToCreatePasskeyState :
550+
AuthenticatorStepState,
551+
AuthenticatorActionState<PromptToCreatePasskeyState.Action> {
552+
sealed interface Action {
553+
/**
554+
* User is creating a passkey
555+
*/
556+
class CreatePasskey : Action
557+
558+
/**
559+
* User has selected the Skip button
560+
*/
561+
class Skip : Action
562+
}
563+
564+
/**
565+
* Create a passkey
566+
*/
567+
suspend fun createPasskey()
568+
569+
/**
570+
* Skip passkey creation and continue to the next step
571+
*/
572+
suspend fun skip()
573+
}
574+
575+
/**
576+
* The user is being shown a confirmation screen after creating a passkey
577+
*/
578+
@Stable
579+
interface PasskeyCreatedState :
580+
AuthenticatorStepState,
581+
AuthenticatorActionState<PasskeyCreatedState.Action> {
582+
sealed interface Action {
583+
/**
584+
* User has selected the Done button
585+
*/
586+
class ContinueSignIn : Action
587+
}
588+
589+
/**
590+
* A list of existing passkeys for this user, including the one they've just created
591+
*/
592+
val passkeys: List<AuthWebAuthnCredential>
593+
594+
/**
595+
* Continue to the next step
596+
*/
597+
suspend fun continueSignIn()
598+
}

0 commit comments

Comments
 (0)