Skip to content

Commit ed47c1d

Browse files
authored
feat: Email & Password Validator and PasswordRule validations (#2218)
* feat(AuthUIConfiguration): implement configuration model, DSL builder and tests * refactor: use OAuthProvider base class for common properties * feat: add Provider enum class for provider ids * feat: setup default provider styles for each provider * test: added builder validation logic from old auth library and tests * refactor: changes in API design docs replaced sealed with abstract class, data with regular class use isXX prefix for booleans * test: fix AuthUIConfiguration constructor test * wip: email validator and password validator * feat: added password rules validations, FieldValidator interface added email and password validator * test: EmailValidator, PasswordValidator and PasswordRule * docs: update PasswordRule comments * docs: update PasswordRule comments * fix: remove mock annotation
1 parent ed436cf commit ed47c1d

File tree

11 files changed

+1064
-41
lines changed

11 files changed

+1064
-41
lines changed

auth/src/main/java/com/firebase/ui/auth/compose/configuration/AuthUIStringProvider.kt

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,59 @@ import com.firebase.ui.auth.R
2323
* passwordsDoNotMatch(), etc., allowing for complete localization of the UI.
2424
*/
2525
interface AuthUIStringProvider {
26-
fun initializing(): String
27-
fun signInWithGoogle(): String
28-
fun invalidEmail(): String
29-
fun passwordsDoNotMatch(): String
30-
}
26+
/** Loading text displayed during initialization or processing states */
27+
val initializing: String
28+
29+
/** Button text for Google sign-in option */
30+
val signInWithGoogle: String
31+
32+
/** Error message when email address field is empty */
33+
val missingEmailAddress: String
34+
35+
/** Error message when email address format is invalid */
36+
val invalidEmailAddress: String
37+
38+
/** Generic error message for incorrect password during sign-in */
39+
val invalidPassword: String
3140

32-
class DefaultAuthUIStringProvider(private val context: Context) : AuthUIStringProvider {
33-
override fun initializing(): String = ""
41+
/** Error message when password confirmation doesn't match the original password */
42+
val passwordsDoNotMatch: String
3443

35-
override fun signInWithGoogle(): String =
36-
context.getString(R.string.fui_sign_in_with_google)
44+
/** Error message when password doesn't meet minimum length requirement. Should support string formatting with minimum length parameter. */
45+
val passwordTooShort: String
3746

38-
override fun invalidEmail(): String = ""
47+
/** Error message when password is missing at least one uppercase letter (A-Z) */
48+
val passwordMissingUppercase: String
49+
50+
/** Error message when password is missing at least one lowercase letter (a-z) */
51+
val passwordMissingLowercase: String
52+
53+
/** Error message when password is missing at least one numeric digit (0-9) */
54+
val passwordMissingDigit: String
55+
56+
/** Error message when password is missing at least one special character */
57+
val passwordMissingSpecialCharacter: String
58+
}
3959

40-
override fun passwordsDoNotMatch(): String = ""
60+
internal class DefaultAuthUIStringProvider(private val context: Context) : AuthUIStringProvider {
61+
override val initializing: String get() = ""
62+
override val signInWithGoogle: String
63+
get() = context.getString(R.string.fui_sign_in_with_google)
64+
override val missingEmailAddress: String
65+
get() = context.getString(R.string.fui_missing_email_address)
66+
override val invalidEmailAddress: String
67+
get() = context.getString(R.string.fui_invalid_email_address)
68+
override val invalidPassword: String
69+
get() = context.getString(R.string.fui_error_invalid_password)
70+
override val passwordsDoNotMatch: String get() = ""
71+
override val passwordTooShort: String
72+
get() = context.getString(R.string.fui_error_password_too_short)
73+
override val passwordMissingUppercase: String
74+
get() = context.getString(R.string.fui_error_password_missing_uppercase)
75+
override val passwordMissingLowercase: String
76+
get() = context.getString(R.string.fui_error_password_missing_lowercase)
77+
override val passwordMissingDigit: String
78+
get() = context.getString(R.string.fui_error_password_missing_digit)
79+
override val passwordMissingSpecialCharacter: String
80+
get() = context.getString(R.string.fui_error_password_missing_special_character)
4181
}

auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,100 @@ abstract class PasswordRule {
2222
/**
2323
* Requires the password to have at least a certain number of characters.
2424
*/
25-
class MinimumLength(val value: Int) : PasswordRule()
25+
class MinimumLength(val value: Int) : PasswordRule() {
26+
override fun isValid(password: String): Boolean {
27+
return password.length >= this@MinimumLength.value
28+
}
29+
30+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
31+
return stringProvider.passwordTooShort.format(value)
32+
}
33+
}
2634

2735
/**
2836
* Requires the password to contain at least one uppercase letter (A-Z).
2937
*/
30-
object RequireUppercase : PasswordRule()
38+
object RequireUppercase : PasswordRule() {
39+
override fun isValid(password: String): Boolean {
40+
return password.any { it.isUpperCase() }
41+
}
42+
43+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
44+
return stringProvider.passwordMissingUppercase
45+
}
46+
}
3147

3248
/**
3349
* Requires the password to contain at least one lowercase letter (a-z).
3450
*/
35-
object RequireLowercase: PasswordRule()
51+
object RequireLowercase : PasswordRule() {
52+
override fun isValid(password: String): Boolean {
53+
return password.any { it.isLowerCase() }
54+
}
55+
56+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
57+
return stringProvider.passwordMissingLowercase
58+
}
59+
}
3660

3761
/**
3862
* Requires the password to contain at least one numeric digit (0-9).
3963
*/
40-
object RequireDigit: PasswordRule()
64+
object RequireDigit : PasswordRule() {
65+
override fun isValid(password: String): Boolean {
66+
return password.any { it.isDigit() }
67+
}
68+
69+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
70+
return stringProvider.passwordMissingDigit
71+
}
72+
}
4173

4274
/**
4375
* Requires the password to contain at least one special character (e.g., !@#$%^&*).
4476
*/
45-
object RequireSpecialCharacter: PasswordRule()
77+
object RequireSpecialCharacter : PasswordRule() {
78+
private val specialCharacters = "!@#$%^&*()_+-=[]{}|;:,.<>?".toSet()
79+
80+
override fun isValid(password: String): Boolean {
81+
return password.any { it in specialCharacters }
82+
}
83+
84+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
85+
return stringProvider.passwordMissingSpecialCharacter
86+
}
87+
}
4688

4789
/**
4890
* Defines a custom validation rule using a regular expression and provides a specific error
4991
* message on failure.
5092
*/
51-
class Custom(val regex: Regex, val errorMessage: String)
93+
class Custom(
94+
val regex: Regex,
95+
val errorMessage: String
96+
) : PasswordRule() {
97+
override fun isValid(password: String): Boolean {
98+
return regex.matches(password)
99+
}
100+
101+
override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
102+
return errorMessage
103+
}
104+
}
105+
106+
/**
107+
* Validates whether the given password meets this rule's requirements.
108+
*
109+
* @param password The password to validate
110+
* @return true if the password meets this rule's requirements, false otherwise
111+
*/
112+
internal abstract fun isValid(password: String): Boolean
113+
114+
/**
115+
* Returns the appropriate error message for this rule when validation fails.
116+
*
117+
* @param stringProvider The string provider for localized error messages
118+
* @return The localized error message for this rule
119+
*/
120+
internal abstract fun getErrorMessage(stringProvider: AuthUIStringProvider): String
52121
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.validators
16+
17+
import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider
18+
19+
internal class EmailValidator(override val stringProvider: AuthUIStringProvider) : FieldValidator {
20+
private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
21+
22+
override val hasError: Boolean
23+
get() = _validationStatus.hasError
24+
25+
override val errorMessage: String
26+
get() = _validationStatus.errorMessage ?: ""
27+
28+
override fun validate(value: String): Boolean {
29+
if (value.isEmpty()) {
30+
_validationStatus = FieldValidationStatus(
31+
hasError = true,
32+
errorMessage = stringProvider.missingEmailAddress
33+
)
34+
return false
35+
}
36+
37+
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(value).matches()) {
38+
_validationStatus = FieldValidationStatus(
39+
hasError = true,
40+
errorMessage = stringProvider.invalidEmailAddress
41+
)
42+
return false
43+
}
44+
45+
_validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
46+
return true
47+
}
48+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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.validators
16+
17+
/**
18+
* Class for encapsulating [hasError] and [errorMessage] properties in
19+
* internal FieldValidator subclasses.
20+
*/
21+
internal class FieldValidationStatus(
22+
val hasError: Boolean,
23+
val errorMessage: String? = null,
24+
)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.validators
16+
17+
import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider
18+
19+
/**
20+
* An interface for validating input fields.
21+
*/
22+
interface FieldValidator {
23+
val stringProvider: AuthUIStringProvider
24+
25+
/**
26+
* Returns true if the last validation failed.
27+
*/
28+
val hasError: Boolean
29+
30+
/**
31+
* The error message for the current state.
32+
*/
33+
val errorMessage: String
34+
35+
/**
36+
* Runs validation on a value and returns true if valid.
37+
*/
38+
fun validate(value: String): Boolean
39+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.validators
16+
17+
import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider
18+
import com.firebase.ui.auth.compose.configuration.PasswordRule
19+
20+
internal class PasswordValidator(
21+
override val stringProvider: AuthUIStringProvider,
22+
private val rules: List<PasswordRule>
23+
) : FieldValidator {
24+
private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
25+
26+
override val hasError: Boolean
27+
get() = _validationStatus.hasError
28+
29+
override val errorMessage: String
30+
get() = _validationStatus.errorMessage ?: ""
31+
32+
override fun validate(value: String): Boolean {
33+
if (value.isEmpty()) {
34+
_validationStatus = FieldValidationStatus(
35+
hasError = true,
36+
errorMessage = stringProvider.invalidPassword
37+
)
38+
return false
39+
}
40+
41+
for (rule in rules) {
42+
if (!rule.isValid(value)) {
43+
_validationStatus = FieldValidationStatus(
44+
hasError = true,
45+
errorMessage = rule.getErrorMessage(stringProvider)
46+
)
47+
return false
48+
}
49+
}
50+
51+
_validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
52+
return true
53+
}
54+
}

auth/src/main/res/values/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@
9393
<string name="fui_error_unknown" translation_description="General error messages for an unknown failure.">An unknown error occurred.</string>
9494
<string name="fui_error_invalid_password" translation_description="Error message for when the user enters an invalid or wrong password.">Incorrect password.</string>
9595

96+
<!-- Password validation messages -->
97+
<string name="fui_error_password_too_short" translation_description="Error message when password is too short">Password must be at least %1$d characters long</string>
98+
<string name="fui_error_password_missing_uppercase" translation_description="Error message when password is missing uppercase letter">Password must contain at least one uppercase letter</string>
99+
<string name="fui_error_password_missing_lowercase" translation_description="Error message when password is missing lowercase letter">Password must contain at least one lowercase letter</string>
100+
<string name="fui_error_password_missing_digit" translation_description="Error message when password is missing digit">Password must contain at least one number</string>
101+
<string name="fui_error_password_missing_special_character" translation_description="Error message when password is missing special character">Password must contain at least one special character</string>
102+
96103
<!-- Accessibility -->
97104
<string name="fui_accessibility_logo" translation_description="Content description for app logo">App logo</string>
98105

0 commit comments

Comments
 (0)