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
@@ -0,0 +1,42 @@
/*
* 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.configuration

/**
* Configuration class for Multi-Factor Authentication (MFA) enrollment and verification behavior.
*
* This class controls which MFA factors are available to users, whether enrollment is mandatory,
* and whether recovery codes are generated.
*
* @property allowedFactors List of MFA factors that users are permitted to enroll in.
* Defaults to [MfaFactor.Sms, MfaFactor.Totp].
* @property requireEnrollment Whether MFA enrollment is mandatory for all users.
* When true, users must enroll in at least one MFA factor.
* Defaults to false.
* @property enableRecoveryCodes Whether to generate and provide recovery codes to users
* after successful MFA enrollment. These codes can be used
* as a backup authentication method. Defaults to true.
*/
class MfaConfiguration(
val allowedFactors: List<MfaFactor> = listOf(MfaFactor.Sms, MfaFactor.Totp),
val requireEnrollment: Boolean = false,
val enableRecoveryCodes: Boolean = true
) {
init {
require(allowedFactors.isNotEmpty()) {
"At least one MFA factor must be allowed"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.configuration

/**
* Represents the different Multi-Factor Authentication (MFA) factors that can be used
* for enrollment and verification.
*/
enum class MfaFactor {
/**
* SMS-based authentication factor.
* Users receive a verification code via text message to their registered phone number.
*/
Sms,

/**
* Time-based One-Time Password (TOTP) authentication factor.
* Users generate verification codes using an authenticator app.
*/
Totp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* 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.configuration

import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

/**
* Unit tests for [MfaConfiguration] covering default values, custom configurations,
* and validation rules.
*
* @suppress Internal test class
*/
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class MfaConfigurationTest {

// =============================================================================================
// Default Configuration Tests
// =============================================================================================

@Test
fun `MfaConfiguration with defaults uses correct values`() {
val config = MfaConfiguration()

assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
assertThat(config.requireEnrollment).isFalse()
assertThat(config.enableRecoveryCodes).isTrue()
}

@Test
fun `MfaConfiguration default allowedFactors includes both Sms and Totp`() {
val config = MfaConfiguration()

assertThat(config.allowedFactors).hasSize(2)
assertThat(config.allowedFactors).contains(MfaFactor.Sms)
assertThat(config.allowedFactors).contains(MfaFactor.Totp)
}

// =============================================================================================
// Custom Configuration Tests
// =============================================================================================

@Test
fun `MfaConfiguration with custom allowedFactors only Sms`() {
val config = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms)
)

assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms)
assertThat(config.allowedFactors).hasSize(1)
}

@Test
fun `MfaConfiguration with custom allowedFactors only Totp`() {
val config = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Totp)
)

assertThat(config.allowedFactors).containsExactly(MfaFactor.Totp)
assertThat(config.allowedFactors).hasSize(1)
}

@Test
fun `MfaConfiguration with requireEnrollment enabled`() {
val config = MfaConfiguration(
requireEnrollment = true
)

assertThat(config.requireEnrollment).isTrue()
}

@Test
fun `MfaConfiguration with enableRecoveryCodes disabled`() {
val config = MfaConfiguration(
enableRecoveryCodes = false
)

assertThat(config.enableRecoveryCodes).isFalse()
}

@Test
fun `MfaConfiguration with all custom values`() {
val config = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms),
requireEnrollment = true,
enableRecoveryCodes = false
)

assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms)
assertThat(config.requireEnrollment).isTrue()
assertThat(config.enableRecoveryCodes).isFalse()
}

// =============================================================================================
// Validation Tests
// =============================================================================================

@Test
fun `MfaConfiguration throws when allowedFactors is empty`() {
try {
MfaConfiguration(
allowedFactors = emptyList()
)
} catch (e: Exception) {
assertThat(e).isInstanceOf(IllegalArgumentException::class.java)
assertThat(e.message).isEqualTo("At least one MFA factor must be allowed")
}
}

@Test
fun `MfaConfiguration allows both factors in any order`() {
val config1 = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp)
)
val config2 = MfaConfiguration(
allowedFactors = listOf(MfaFactor.Totp, MfaFactor.Sms)
)

assertThat(config1.allowedFactors).hasSize(2)
assertThat(config2.allowedFactors).hasSize(2)
assertThat(config1.allowedFactors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
assertThat(config2.allowedFactors).containsExactly(MfaFactor.Totp, MfaFactor.Sms)
}

// =============================================================================================
// MfaFactor Enum Tests
// =============================================================================================

@Test
fun `MfaFactor enum has exactly two values`() {
val factors = MfaFactor.entries

assertThat(factors).hasSize(2)
assertThat(factors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
}

@Test
fun `MfaFactor Sms has correct name`() {
assertThat(MfaFactor.Sms.name).isEqualTo("Sms")
}

@Test
fun `MfaFactor Totp has correct name`() {
assertThat(MfaFactor.Totp.name).isEqualTo("Totp")
}
}