Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,42 @@ interface AuthUIStringProvider {

/** Unknown error recovery message */
val unknownErrorRecoveryMessage: String

// MFA Enrollment Step Titles
/** Title for MFA factor selection step */
val mfaStepSelectFactorTitle: String

/** Title for SMS MFA configuration step */
val mfaStepConfigureSmsTitle: String

/** Title for TOTP MFA configuration step */
val mfaStepConfigureTotpTitle: String

/** Title for MFA verification step */
val mfaStepVerifyFactorTitle: String

/** Title for recovery codes step */
val mfaStepShowRecoveryCodesTitle: String

// MFA Enrollment Helper Text
/** Helper text for selecting MFA factor */
val mfaStepSelectFactorHelper: String

/** Helper text for SMS configuration */
val mfaStepConfigureSmsHelper: String

/** Helper text for TOTP configuration */
val mfaStepConfigureTotpHelper: String

/** Helper text for SMS verification */
val mfaStepVerifyFactorSmsHelper: String

/** Helper text for TOTP verification */
val mfaStepVerifyFactorTotpHelper: String

/** Generic helper text for factor verification */
val mfaStepVerifyFactorGenericHelper: String

/** Helper text for recovery codes */
val mfaStepShowRecoveryCodesHelper: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,36 @@ class DefaultAuthUIStringProvider(
get() = localizedContext.getString(R.string.fui_error_auth_cancelled_message)
override val unknownErrorRecoveryMessage: String
get() = localizedContext.getString(R.string.fui_error_unknown)

/**
* MFA Enrollment Step Titles
*/
override val mfaStepSelectFactorTitle: String
get() = localizedContext.getString(R.string.fui_mfa_step_select_factor_title)
override val mfaStepConfigureSmsTitle: String
get() = localizedContext.getString(R.string.fui_mfa_step_configure_sms_title)
override val mfaStepConfigureTotpTitle: String
get() = localizedContext.getString(R.string.fui_mfa_step_configure_totp_title)
override val mfaStepVerifyFactorTitle: String
get() = localizedContext.getString(R.string.fui_mfa_step_verify_factor_title)
override val mfaStepShowRecoveryCodesTitle: String
get() = localizedContext.getString(R.string.fui_mfa_step_show_recovery_codes_title)

/**
* MFA Enrollment Helper Text
*/
override val mfaStepSelectFactorHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_select_factor_helper)
override val mfaStepConfigureSmsHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_configure_sms_helper)
override val mfaStepConfigureTotpHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_configure_totp_helper)
override val mfaStepVerifyFactorSmsHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_verify_factor_sms_helper)
override val mfaStepVerifyFactorTotpHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_verify_factor_totp_helper)
override val mfaStepVerifyFactorGenericHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_verify_factor_generic_helper)
override val mfaStepShowRecoveryCodesHelper: String
get() = localizedContext.getString(R.string.fui_mfa_step_show_recovery_codes_helper)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright 2025 Google Inc. 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. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License 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.firebase.ui.auth.compose.mfa

import com.firebase.ui.auth.compose.configuration.MfaFactor
import com.firebase.ui.auth.compose.data.CountryData

/**
* State class containing all the necessary information to render a custom UI for the
* Multi-Factor Authentication (MFA) enrollment flow.
*
* This class is passed to the content slot of the MfaEnrollmentScreen composable, providing
* access to the current step, user input values, callbacks for actions, and loading/error states.
*
* Use a `when` expression on [step] to determine which UI to render:
*
* ```kotlin
* MfaEnrollmentScreen(user, config, onComplete, onSkip) { state ->
* when (state.step) {
* MfaEnrollmentStep.SelectFactor -> {
* // Render factor selection UI using state.availableFactors
* }
* MfaEnrollmentStep.ConfigureTotp -> {
* // Render TOTP setup UI using state.totpSecret and state.totpQrCodeUrl
* }
* MfaEnrollmentStep.VerifyFactor -> {
* // Render verification UI using state.verificationCode
* }
* // ... other steps
* }
* }
* ```
*
* @property step The current step in the enrollment flow. Use this to determine which UI to display.
* @property isLoading `true` when an asynchronous operation (like generating a secret or verifying a code) is in progress. Use this to show loading indicators.
* @property error An optional error message to display to the user. Will be `null` if there's no error.
* @property onBackClick Callback to navigate to the previous step in the flow. Invoked when the user clicks a back button.
*
* @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].
* @property onFactorSelected (Step: [MfaEnrollmentStep.SelectFactor]) Callback invoked when the user selects an MFA factor. Receives the selected [MfaFactor].
* @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].
*
* @property phoneNumber (Step: [MfaEnrollmentStep.ConfigureSms]) The current value of the phone number input field. Does not include country code prefix.
* @property onPhoneNumberChange (Step: [MfaEnrollmentStep.ConfigureSms]) Callback invoked when the phone number input changes. Receives the new phone number string.
* @property selectedCountry (Step: [MfaEnrollmentStep.ConfigureSms]) The currently selected country for phone number formatting. Contains dial code, country code, and flag.
* @property onCountrySelected (Step: [MfaEnrollmentStep.ConfigureSms]) Callback invoked when the user selects a different country. Receives the new [CountryData].
* @property onSendSmsCodeClick (Step: [MfaEnrollmentStep.ConfigureSms]) Callback to send the SMS verification code to the entered phone number.
*
* @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.
* @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].
* @property onContinueToVerifyClick (Step: [MfaEnrollmentStep.ConfigureTotp]) Callback to proceed to the verification step after the user has scanned the QR code or entered the secret.
*
* @property verificationCode (Step: [MfaEnrollmentStep.VerifyFactor]) The current value of the verification code input field. Should be a 6-digit string.
* @property onVerificationCodeChange (Step: [MfaEnrollmentStep.VerifyFactor]) Callback invoked when the verification code input changes. Receives the new code string.
* @property onVerifyClick (Step: [MfaEnrollmentStep.VerifyFactor]) Callback to verify the entered code and finalize MFA enrollment.
* @property selectedFactor (Step: [MfaEnrollmentStep.VerifyFactor]) The MFA factor being verified (SMS or TOTP). Use this to customize UI messages.
* @property onResendCodeClick (Step: [MfaEnrollmentStep.VerifyFactor], SMS only) Callback to resend the SMS verification code. Will be `null` for TOTP verification.
*
* @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`.
* @property onCodesSavedClick (Step: [MfaEnrollmentStep.ShowRecoveryCodes]) Callback invoked when the user confirms they have saved their recovery codes. Completes the enrollment flow.
*
* @since 10.0.0
*/
data class MfaEnrollmentContentState(
/** The current step in the enrollment flow. Use this to determine which UI to display. */
val step: MfaEnrollmentStep,

/** `true` when an async operation is in progress. Use to show loading indicators. */
val isLoading: Boolean = false,

/** Optional error message to display. `null` if no error. */
val error: String? = null,

/** Callback to navigate to the previous step. */
val onBackClick: () -> Unit = {},

// SelectFactor step
val availableFactors: List<MfaFactor> = emptyList(),

val onFactorSelected: (MfaFactor) -> Unit = {},

val onSkipClick: (() -> Unit)? = null,

// ConfigureSms step
val phoneNumber: String = "",

val onPhoneNumberChange: (String) -> Unit = {},

val selectedCountry: CountryData? = null,

val onCountrySelected: (CountryData) -> Unit = {},

val onSendSmsCodeClick: () -> Unit = {},

// ConfigureTotp step
val totpSecret: TotpSecret? = null,

val totpQrCodeUrl: String? = null,

val onContinueToVerifyClick: () -> Unit = {},

// VerifyFactor step
val verificationCode: String = "",

val onVerificationCodeChange: (String) -> Unit = {},

val onVerifyClick: () -> Unit = {},

val selectedFactor: MfaFactor? = null,

val onResendCodeClick: (() -> Unit)? = null,

// ShowRecoveryCodes step
val recoveryCodes: List<String>? = null,

val onCodesSavedClick: () -> Unit = {}
) {
/**
* Returns true if the current state is valid for the current step.
*
* This can be used to enable/disable action buttons in the UI.
*/
val isValid: Boolean
get() = when (step) {
MfaEnrollmentStep.SelectFactor -> availableFactors.isNotEmpty()
MfaEnrollmentStep.ConfigureSms -> phoneNumber.isNotBlank()
MfaEnrollmentStep.ConfigureTotp -> totpSecret != null && totpQrCodeUrl != null
MfaEnrollmentStep.VerifyFactor -> verificationCode.length == 6
MfaEnrollmentStep.ShowRecoveryCodes -> !recoveryCodes.isNullOrEmpty()
}

/**
* Returns true if there is an error in the current state.
*/
val hasError: Boolean
get() = !error.isNullOrBlank()

/**
* Returns true if the skip action is available (only for SelectFactor step when not required).
*/
val canSkip: Boolean
get() = step == MfaEnrollmentStep.SelectFactor && onSkipClick != null

/**
* Returns true if the back action is available (for all steps except SelectFactor).
*/
val canGoBack: Boolean
get() = step != MfaEnrollmentStep.SelectFactor
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2025 Google Inc. 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. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License 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.firebase.ui.auth.compose.mfa

import com.firebase.ui.auth.compose.configuration.MfaFactor
import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider

/**
* Represents the different steps in the Multi-Factor Authentication (MFA) enrollment flow.
*
* This enum defines the sequence of UI states that users progress through when enrolling
* in MFA, from selecting a factor to completing the setup with recovery codes.
*
* @since 10.0.0
*/
enum class MfaEnrollmentStep {
/**
* The user is presented with a selection of available MFA factors to enroll in.
* The available factors are determined by the [com.firebase.ui.auth.compose.configuration.MfaConfiguration].
*/
SelectFactor,

/**
* The user is configuring SMS-based MFA by entering their phone number.
* This step prepares to send an SMS verification code to the provided number.
*/
ConfigureSms,

/**
* The user is configuring TOTP (Time-based One-Time Password) MFA.
* This step presents the TOTP secret (as both text and QR code) for the user
* to scan into their authenticator app.
*/
ConfigureTotp,

/**
* The user is verifying their chosen MFA factor by entering a verification code.
* For SMS, this is the code received via text message.
* For TOTP, this is the code generated by their authenticator app.
*/
VerifyFactor,

/**
* The enrollment is complete and recovery codes are displayed to the user.
* These backup codes can be used to sign in if the primary MFA method is unavailable.
* This step only appears if recovery codes are enabled in the configuration.
*/
ShowRecoveryCodes
}

/**
* Returns the localized title text for this enrollment step.
*
* @param stringProvider The string provider for localized strings
* @return The localized title for this step
*/
fun MfaEnrollmentStep.getTitle(stringProvider: AuthUIStringProvider): String = when (this) {
MfaEnrollmentStep.SelectFactor -> stringProvider.mfaStepSelectFactorTitle
MfaEnrollmentStep.ConfigureSms -> stringProvider.mfaStepConfigureSmsTitle
MfaEnrollmentStep.ConfigureTotp -> stringProvider.mfaStepConfigureTotpTitle
MfaEnrollmentStep.VerifyFactor -> stringProvider.mfaStepVerifyFactorTitle
MfaEnrollmentStep.ShowRecoveryCodes -> stringProvider.mfaStepShowRecoveryCodesTitle
}

/**
* Returns localized helper text providing instructions for this step.
*
* @param stringProvider The string provider for localized strings
* @param selectedFactor The MFA factor being configured or verified. Used for [MfaEnrollmentStep.VerifyFactor]
* to provide factor-specific instructions. Ignored for other steps.
* @return Localized instructional text appropriate for this step
*/
fun MfaEnrollmentStep.getHelperText(
stringProvider: AuthUIStringProvider,
selectedFactor: MfaFactor? = null
): String = when (this) {
MfaEnrollmentStep.SelectFactor -> stringProvider.mfaStepSelectFactorHelper
MfaEnrollmentStep.ConfigureSms -> stringProvider.mfaStepConfigureSmsHelper
MfaEnrollmentStep.ConfigureTotp -> stringProvider.mfaStepConfigureTotpHelper
MfaEnrollmentStep.VerifyFactor -> when (selectedFactor) {
MfaFactor.Sms -> stringProvider.mfaStepVerifyFactorSmsHelper
MfaFactor.Totp -> stringProvider.mfaStepVerifyFactorTotpHelper
null -> stringProvider.mfaStepVerifyFactorGenericHelper
}
MfaEnrollmentStep.ShowRecoveryCodes -> stringProvider.mfaStepShowRecoveryCodesHelper
}
Loading