Skip to content

Commit 46c756f

Browse files
committed
feat: MFA Enrollment (TOTP)
1 parent 10b6dc7 commit 46c756f

8 files changed

Lines changed: 1188 additions & 0 deletions

File tree

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.ui.auth.compose.mfa
16+
17+
import com.firebase.ui.auth.compose.configuration.MfaFactor
18+
import com.firebase.ui.auth.compose.data.CountryData
19+
20+
/**
21+
* State class containing all the necessary information to render a custom UI for the
22+
* Multi-Factor Authentication (MFA) enrollment flow.
23+
*
24+
* This class is passed to the content slot of the MfaEnrollmentScreen composable, providing
25+
* access to the current step, user input values, callbacks for actions, and loading/error states.
26+
*
27+
* Use a `when` expression on [step] to determine which UI to render:
28+
*
29+
* ```kotlin
30+
* MfaEnrollmentScreen(user, config, onComplete, onSkip) { state ->
31+
* when (state.step) {
32+
* MfaEnrollmentStep.SelectFactor -> {
33+
* // Render factor selection UI using state.availableFactors
34+
* }
35+
* MfaEnrollmentStep.ConfigureTotp -> {
36+
* // Render TOTP setup UI using state.totpSecret and state.totpQrCodeUrl
37+
* }
38+
* MfaEnrollmentStep.VerifyFactor -> {
39+
* // Render verification UI using state.verificationCode
40+
* }
41+
* // ... other steps
42+
* }
43+
* }
44+
* ```
45+
*
46+
* @property step The current step in the enrollment flow. Use this to determine which UI to display.
47+
* @property isLoading `true` when an asynchronous operation (like generating a secret or verifying a code) is in progress. Use this to show loading indicators.
48+
* @property error An optional error message to display to the user. Will be `null` if there's no error.
49+
* @property onBackClick Callback to navigate to the previous step in the flow. Invoked when the user clicks a back button.
50+
*
51+
* @property availableFactors (Step: [MfaEnrollmentStep.SelectFactor]) A list of MFA factors the user can choose from (e.g., SMS, TOTP). Determined by [com.firebase.ui.auth.compose.configuration.MfaConfiguration.allowedFactors].
52+
* @property onFactorSelected (Step: [MfaEnrollmentStep.SelectFactor]) Callback invoked when the user selects an MFA factor. Receives the selected [MfaFactor].
53+
* @property onSkipClick (Step: [MfaEnrollmentStep.SelectFactor]) Callback for the "Skip" action. Will be `null` if MFA enrollment is required via [com.firebase.ui.auth.compose.configuration.MfaConfiguration.requireEnrollment].
54+
*
55+
* @property phoneNumber (Step: [MfaEnrollmentStep.ConfigureSms]) The current value of the phone number input field. Does not include country code prefix.
56+
* @property onPhoneNumberChange (Step: [MfaEnrollmentStep.ConfigureSms]) Callback invoked when the phone number input changes. Receives the new phone number string.
57+
* @property selectedCountry (Step: [MfaEnrollmentStep.ConfigureSms]) The currently selected country for phone number formatting. Contains dial code, country code, and flag.
58+
* @property onCountrySelected (Step: [MfaEnrollmentStep.ConfigureSms]) Callback invoked when the user selects a different country. Receives the new [CountryData].
59+
* @property onSendSmsCodeClick (Step: [MfaEnrollmentStep.ConfigureSms]) Callback to send the SMS verification code to the entered phone number.
60+
*
61+
* @property totpSecret (Step: [MfaEnrollmentStep.ConfigureTotp]) The TOTP secret containing the shared key and configuration. Use this to display the secret key or access the underlying Firebase TOTP secret.
62+
* @property totpQrCodeUrl (Step: [MfaEnrollmentStep.ConfigureTotp]) A URI that can be rendered as a QR code or used as a deep link to open authenticator apps. Generated via [TotpSecret.generateQrCodeUrl].
63+
* @property onContinueToVerifyClick (Step: [MfaEnrollmentStep.ConfigureTotp]) Callback to proceed to the verification step after the user has scanned the QR code or entered the secret.
64+
*
65+
* @property verificationCode (Step: [MfaEnrollmentStep.VerifyFactor]) The current value of the verification code input field. Should be a 6-digit string.
66+
* @property onVerificationCodeChange (Step: [MfaEnrollmentStep.VerifyFactor]) Callback invoked when the verification code input changes. Receives the new code string.
67+
* @property onVerifyClick (Step: [MfaEnrollmentStep.VerifyFactor]) Callback to verify the entered code and finalize MFA enrollment.
68+
* @property selectedFactor (Step: [MfaEnrollmentStep.VerifyFactor]) The MFA factor being verified (SMS or TOTP). Use this to customize UI messages.
69+
* @property onResendCodeClick (Step: [MfaEnrollmentStep.VerifyFactor], SMS only) Callback to resend the SMS verification code. Will be `null` for TOTP verification.
70+
*
71+
* @property recoveryCodes (Step: [MfaEnrollmentStep.ShowRecoveryCodes]) A list of one-time backup codes the user should save. Only present if [com.firebase.ui.auth.compose.configuration.MfaConfiguration.enableRecoveryCodes] is `true`.
72+
* @property onCodesSavedClick (Step: [MfaEnrollmentStep.ShowRecoveryCodes]) Callback invoked when the user confirms they have saved their recovery codes. Completes the enrollment flow.
73+
*
74+
* @since 10.0.0
75+
*/
76+
data class MfaEnrollmentContentState(
77+
/** The current step in the enrollment flow. Use this to determine which UI to display. */
78+
val step: MfaEnrollmentStep,
79+
80+
/** `true` when an async operation is in progress. Use to show loading indicators. */
81+
val isLoading: Boolean = false,
82+
83+
/** Optional error message to display. `null` if no error. */
84+
val error: String? = null,
85+
86+
/** Callback to navigate to the previous step. */
87+
val onBackClick: () -> Unit = {},
88+
89+
// SelectFactor step
90+
/** (SelectFactor) List of MFA factors the user can choose from. */
91+
val availableFactors: List<MfaFactor> = emptyList(),
92+
93+
/** (SelectFactor) Callback when user selects an MFA factor. */
94+
val onFactorSelected: (MfaFactor) -> Unit = {},
95+
96+
/** (SelectFactor) Skip callback. `null` if enrollment is required. */
97+
val onSkipClick: (() -> Unit)? = null,
98+
99+
// ConfigureSms step
100+
/** (ConfigureSms) Current phone number value (without country code prefix). */
101+
val phoneNumber: String = "",
102+
103+
/** (ConfigureSms) Callback when phone number changes. */
104+
val onPhoneNumberChange: (String) -> Unit = {},
105+
106+
/** (ConfigureSms) Currently selected country (dial code, country code, flag). */
107+
val selectedCountry: CountryData? = null,
108+
109+
/** (ConfigureSms) Callback when user selects a different country. */
110+
val onCountrySelected: (CountryData) -> Unit = {},
111+
112+
/** (ConfigureSms) Callback to send SMS verification code. */
113+
val onSendSmsCodeClick: () -> Unit = {},
114+
115+
// ConfigureTotp step
116+
/** (ConfigureTotp) TOTP secret containing shared key. Use to display or access Firebase secret. */
117+
val totpSecret: TotpSecret? = null,
118+
119+
/** (ConfigureTotp) QR code URI for authenticator apps. */
120+
val totpQrCodeUrl: String? = null,
121+
122+
/** (ConfigureTotp) Callback to proceed to verification after QR scan. */
123+
val onContinueToVerifyClick: () -> Unit = {},
124+
125+
// VerifyFactor step
126+
/** (VerifyFactor) Current verification code value (6 digits). */
127+
val verificationCode: String = "",
128+
129+
/** (VerifyFactor) Callback when verification code changes. */
130+
val onVerificationCodeChange: (String) -> Unit = {},
131+
132+
/** (VerifyFactor) Callback to verify code and finalize enrollment. */
133+
val onVerifyClick: () -> Unit = {},
134+
135+
/** (VerifyFactor) The factor being verified (SMS or TOTP). Use to customize messages. */
136+
val selectedFactor: MfaFactor? = null,
137+
138+
/** (VerifyFactor, SMS only) Callback to resend SMS code. `null` for TOTP. */
139+
val onResendCodeClick: (() -> Unit)? = null,
140+
141+
// ShowRecoveryCodes step
142+
/** (ShowRecoveryCodes) One-time backup codes to save. Only if recovery codes are enabled. */
143+
val recoveryCodes: List<String>? = null,
144+
145+
/** (ShowRecoveryCodes) Callback when user confirms codes are saved. Completes enrollment. */
146+
val onCodesSavedClick: () -> Unit = {}
147+
) {
148+
/**
149+
* Returns true if the current state is valid for the current step.
150+
*
151+
* This can be used to enable/disable action buttons in the UI.
152+
*/
153+
val isValid: Boolean
154+
get() = when (step) {
155+
MfaEnrollmentStep.SelectFactor -> availableFactors.isNotEmpty()
156+
MfaEnrollmentStep.ConfigureSms -> phoneNumber.isNotBlank()
157+
MfaEnrollmentStep.ConfigureTotp -> totpSecret != null && totpQrCodeUrl != null
158+
MfaEnrollmentStep.VerifyFactor -> verificationCode.length == 6
159+
MfaEnrollmentStep.ShowRecoveryCodes -> !recoveryCodes.isNullOrEmpty()
160+
}
161+
162+
/**
163+
* Returns true if there is an error in the current state.
164+
*/
165+
val hasError: Boolean
166+
get() = !error.isNullOrBlank()
167+
168+
/**
169+
* Returns true if the skip action is available (only for SelectFactor step when not required).
170+
*/
171+
val canSkip: Boolean
172+
get() = step == MfaEnrollmentStep.SelectFactor && onSkipClick != null
173+
174+
/**
175+
* Returns true if the back action is available (for all steps except SelectFactor).
176+
*/
177+
val canGoBack: Boolean
178+
get() = step != MfaEnrollmentStep.SelectFactor
179+
180+
/**
181+
* Returns the title text for the current step.
182+
*
183+
* This is a convenience method for providing default titles in custom UIs.
184+
*/
185+
fun getStepTitle(): String = when (step) {
186+
MfaEnrollmentStep.SelectFactor -> "Choose Authentication Method"
187+
MfaEnrollmentStep.ConfigureSms -> "Set Up SMS Verification"
188+
MfaEnrollmentStep.ConfigureTotp -> "Set Up Authenticator App"
189+
MfaEnrollmentStep.VerifyFactor -> "Verify Your Code"
190+
MfaEnrollmentStep.ShowRecoveryCodes -> "Save Your Recovery Codes"
191+
}
192+
193+
/**
194+
* Returns helper text for the current step.
195+
*
196+
* This is a convenience method for providing default instructions in custom UIs.
197+
*/
198+
fun getStepHelperText(): String = when (step) {
199+
MfaEnrollmentStep.SelectFactor -> "Select a second authentication method to secure your account"
200+
MfaEnrollmentStep.ConfigureSms -> "Enter your phone number to receive verification codes"
201+
MfaEnrollmentStep.ConfigureTotp -> "Scan the QR code with your authenticator app"
202+
MfaEnrollmentStep.VerifyFactor -> when (selectedFactor) {
203+
MfaFactor.Sms -> "Enter the code sent to your phone"
204+
MfaFactor.Totp -> "Enter the code from your authenticator app"
205+
null -> "Enter your verification code"
206+
}
207+
MfaEnrollmentStep.ShowRecoveryCodes -> "Store these codes in a safe place. You can use them to sign in if you lose access to your authentication method."
208+
}
209+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2025 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package com.firebase.ui.auth.compose.mfa
16+
17+
/**
18+
* Represents the different steps in the Multi-Factor Authentication (MFA) enrollment flow.
19+
*
20+
* This enum defines the sequence of UI states that users progress through when enrolling
21+
* in MFA, from selecting a factor to completing the setup with recovery codes.
22+
*
23+
* @since 10.0.0
24+
*/
25+
enum class MfaEnrollmentStep {
26+
/**
27+
* The user is presented with a selection of available MFA factors to enroll in.
28+
* The available factors are determined by the [com.firebase.ui.auth.compose.configuration.MfaConfiguration].
29+
*/
30+
SelectFactor,
31+
32+
/**
33+
* The user is configuring SMS-based MFA by entering their phone number.
34+
* This step prepares to send an SMS verification code to the provided number.
35+
*/
36+
ConfigureSms,
37+
38+
/**
39+
* The user is configuring TOTP (Time-based One-Time Password) MFA.
40+
* This step presents the TOTP secret (as both text and QR code) for the user
41+
* to scan into their authenticator app.
42+
*/
43+
ConfigureTotp,
44+
45+
/**
46+
* The user is verifying their chosen MFA factor by entering a verification code.
47+
* For SMS, this is the code received via text message.
48+
* For TOTP, this is the code generated by their authenticator app.
49+
*/
50+
VerifyFactor,
51+
52+
/**
53+
* The enrollment is complete and recovery codes are displayed to the user.
54+
* These backup codes can be used to sign in if the primary MFA method is unavailable.
55+
* This step only appears if recovery codes are enabled in the configuration.
56+
*/
57+
ShowRecoveryCodes
58+
}

0 commit comments

Comments
 (0)