Skip to content

Commit 45b6a40

Browse files
committed
Merge branch 'version-10.0.0-dev' of https://github.com/firebase/FirebaseUI-Android into feat/T3
2 parents eb38a85 + 4d1aefd commit 45b6a40

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.configuration
16+
17+
/**
18+
* Configuration class for Multi-Factor Authentication (MFA) enrollment and verification behavior.
19+
*
20+
* This class controls which MFA factors are available to users, whether enrollment is mandatory,
21+
* and whether recovery codes are generated.
22+
*
23+
* @property allowedFactors List of MFA factors that users are permitted to enroll in.
24+
* Defaults to [MfaFactor.Sms, MfaFactor.Totp].
25+
* @property requireEnrollment Whether MFA enrollment is mandatory for all users.
26+
* When true, users must enroll in at least one MFA factor.
27+
* Defaults to false.
28+
* @property enableRecoveryCodes Whether to generate and provide recovery codes to users
29+
* after successful MFA enrollment. These codes can be used
30+
* as a backup authentication method. Defaults to true.
31+
*/
32+
class MfaConfiguration(
33+
val allowedFactors: List<MfaFactor> = listOf(MfaFactor.Sms, MfaFactor.Totp),
34+
val requireEnrollment: Boolean = false,
35+
val enableRecoveryCodes: Boolean = true
36+
) {
37+
init {
38+
require(allowedFactors.isNotEmpty()) {
39+
"At least one MFA factor must be allowed"
40+
}
41+
}
42+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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.configuration
16+
17+
/**
18+
* Represents the different Multi-Factor Authentication (MFA) factors that can be used
19+
* for enrollment and verification.
20+
*/
21+
enum class MfaFactor {
22+
/**
23+
* SMS-based authentication factor.
24+
* Users receive a verification code via text message to their registered phone number.
25+
*/
26+
Sms,
27+
28+
/**
29+
* Time-based One-Time Password (TOTP) authentication factor.
30+
* Users generate verification codes using an authenticator app.
31+
*/
32+
Totp
33+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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.configuration
16+
17+
import com.google.common.truth.Truth.assertThat
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
import org.robolectric.RobolectricTestRunner
21+
import org.robolectric.annotation.Config
22+
23+
/**
24+
* Unit tests for [MfaConfiguration] covering default values, custom configurations,
25+
* and validation rules.
26+
*
27+
* @suppress Internal test class
28+
*/
29+
@RunWith(RobolectricTestRunner::class)
30+
@Config(manifest = Config.NONE)
31+
class MfaConfigurationTest {
32+
33+
// =============================================================================================
34+
// Default Configuration Tests
35+
// =============================================================================================
36+
37+
@Test
38+
fun `MfaConfiguration with defaults uses correct values`() {
39+
val config = MfaConfiguration()
40+
41+
assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
42+
assertThat(config.requireEnrollment).isFalse()
43+
assertThat(config.enableRecoveryCodes).isTrue()
44+
}
45+
46+
@Test
47+
fun `MfaConfiguration default allowedFactors includes both Sms and Totp`() {
48+
val config = MfaConfiguration()
49+
50+
assertThat(config.allowedFactors).hasSize(2)
51+
assertThat(config.allowedFactors).contains(MfaFactor.Sms)
52+
assertThat(config.allowedFactors).contains(MfaFactor.Totp)
53+
}
54+
55+
// =============================================================================================
56+
// Custom Configuration Tests
57+
// =============================================================================================
58+
59+
@Test
60+
fun `MfaConfiguration with custom allowedFactors only Sms`() {
61+
val config = MfaConfiguration(
62+
allowedFactors = listOf(MfaFactor.Sms)
63+
)
64+
65+
assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms)
66+
assertThat(config.allowedFactors).hasSize(1)
67+
}
68+
69+
@Test
70+
fun `MfaConfiguration with custom allowedFactors only Totp`() {
71+
val config = MfaConfiguration(
72+
allowedFactors = listOf(MfaFactor.Totp)
73+
)
74+
75+
assertThat(config.allowedFactors).containsExactly(MfaFactor.Totp)
76+
assertThat(config.allowedFactors).hasSize(1)
77+
}
78+
79+
@Test
80+
fun `MfaConfiguration with requireEnrollment enabled`() {
81+
val config = MfaConfiguration(
82+
requireEnrollment = true
83+
)
84+
85+
assertThat(config.requireEnrollment).isTrue()
86+
}
87+
88+
@Test
89+
fun `MfaConfiguration with enableRecoveryCodes disabled`() {
90+
val config = MfaConfiguration(
91+
enableRecoveryCodes = false
92+
)
93+
94+
assertThat(config.enableRecoveryCodes).isFalse()
95+
}
96+
97+
@Test
98+
fun `MfaConfiguration with all custom values`() {
99+
val config = MfaConfiguration(
100+
allowedFactors = listOf(MfaFactor.Sms),
101+
requireEnrollment = true,
102+
enableRecoveryCodes = false
103+
)
104+
105+
assertThat(config.allowedFactors).containsExactly(MfaFactor.Sms)
106+
assertThat(config.requireEnrollment).isTrue()
107+
assertThat(config.enableRecoveryCodes).isFalse()
108+
}
109+
110+
// =============================================================================================
111+
// Validation Tests
112+
// =============================================================================================
113+
114+
@Test
115+
fun `MfaConfiguration throws when allowedFactors is empty`() {
116+
try {
117+
MfaConfiguration(
118+
allowedFactors = emptyList()
119+
)
120+
} catch (e: Exception) {
121+
assertThat(e).isInstanceOf(IllegalArgumentException::class.java)
122+
assertThat(e.message).isEqualTo("At least one MFA factor must be allowed")
123+
}
124+
}
125+
126+
@Test
127+
fun `MfaConfiguration allows both factors in any order`() {
128+
val config1 = MfaConfiguration(
129+
allowedFactors = listOf(MfaFactor.Sms, MfaFactor.Totp)
130+
)
131+
val config2 = MfaConfiguration(
132+
allowedFactors = listOf(MfaFactor.Totp, MfaFactor.Sms)
133+
)
134+
135+
assertThat(config1.allowedFactors).hasSize(2)
136+
assertThat(config2.allowedFactors).hasSize(2)
137+
assertThat(config1.allowedFactors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
138+
assertThat(config2.allowedFactors).containsExactly(MfaFactor.Totp, MfaFactor.Sms)
139+
}
140+
141+
// =============================================================================================
142+
// MfaFactor Enum Tests
143+
// =============================================================================================
144+
145+
@Test
146+
fun `MfaFactor enum has exactly two values`() {
147+
val factors = MfaFactor.entries
148+
149+
assertThat(factors).hasSize(2)
150+
assertThat(factors).containsExactly(MfaFactor.Sms, MfaFactor.Totp)
151+
}
152+
153+
@Test
154+
fun `MfaFactor Sms has correct name`() {
155+
assertThat(MfaFactor.Sms.name).isEqualTo("Sms")
156+
}
157+
158+
@Test
159+
fun `MfaFactor Totp has correct name`() {
160+
assertThat(MfaFactor.Totp.name).isEqualTo("Totp")
161+
}
162+
}

0 commit comments

Comments
 (0)