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 @@ -23,19 +23,59 @@ import com.firebase.ui.auth.R
* passwordsDoNotMatch(), etc., allowing for complete localization of the UI.
*/
interface AuthUIStringProvider {
fun initializing(): String
fun signInWithGoogle(): String
fun invalidEmail(): String
fun passwordsDoNotMatch(): String
}
/** Loading text displayed during initialization or processing states */
val initializing: String

/** Button text for Google sign-in option */
val signInWithGoogle: String

/** Error message when email address field is empty */
val missingEmailAddress: String

/** Error message when email address format is invalid */
val invalidEmailAddress: String

/** Generic error message for incorrect password during sign-in */
val invalidPassword: String

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

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

override fun invalidEmail(): String = ""
/** Error message when password is missing at least one uppercase letter (A-Z) */
val passwordMissingUppercase: String

/** Error message when password is missing at least one lowercase letter (a-z) */
val passwordMissingLowercase: String

/** Error message when password is missing at least one numeric digit (0-9) */
val passwordMissingDigit: String

/** Error message when password is missing at least one special character */
val passwordMissingSpecialCharacter: String
}

override fun passwordsDoNotMatch(): String = ""
internal class DefaultAuthUIStringProvider(private val context: Context) : AuthUIStringProvider {
override val initializing: String get() = ""
override val signInWithGoogle: String
get() = context.getString(R.string.fui_sign_in_with_google)
override val missingEmailAddress: String
get() = context.getString(R.string.fui_missing_email_address)
override val invalidEmailAddress: String
get() = context.getString(R.string.fui_invalid_email_address)
override val invalidPassword: String
get() = context.getString(R.string.fui_error_invalid_password)
override val passwordsDoNotMatch: String get() = ""
override val passwordTooShort: String
get() = context.getString(R.string.fui_error_password_too_short)
override val passwordMissingUppercase: String
get() = context.getString(R.string.fui_error_password_missing_uppercase)
override val passwordMissingLowercase: String
get() = context.getString(R.string.fui_error_password_missing_lowercase)
override val passwordMissingDigit: String
get() = context.getString(R.string.fui_error_password_missing_digit)
override val passwordMissingSpecialCharacter: String
get() = context.getString(R.string.fui_error_password_missing_special_character)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,100 @@ abstract class PasswordRule {
/**
* Requires the password to have at least a certain number of characters.
*/
class MinimumLength(val value: Int) : PasswordRule()
class MinimumLength(val value: Int) : PasswordRule() {
override fun isValid(password: String): Boolean {
return password.length >= this@MinimumLength.value
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return stringProvider.passwordTooShort.format(value)
}
}

/**
* Requires the password to contain at least one uppercase letter (A-Z).
*/
object RequireUppercase : PasswordRule()
object RequireUppercase : PasswordRule() {
override fun isValid(password: String): Boolean {
return password.any { it.isUpperCase() }
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return stringProvider.passwordMissingUppercase
}
}

/**
* Requires the password to contain at least one lowercase letter (a-z).
*/
object RequireLowercase: PasswordRule()
object RequireLowercase : PasswordRule() {
override fun isValid(password: String): Boolean {
return password.any { it.isLowerCase() }
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return stringProvider.passwordMissingLowercase
}
}

/**
* Requires the password to contain at least one numeric digit (0-9).
*/
object RequireDigit: PasswordRule()
object RequireDigit : PasswordRule() {
override fun isValid(password: String): Boolean {
return password.any { it.isDigit() }
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return stringProvider.passwordMissingDigit
}
}

/**
* Requires the password to contain at least one special character (e.g., !@#$%^&*).
*/
object RequireSpecialCharacter: PasswordRule()
object RequireSpecialCharacter : PasswordRule() {
private val specialCharacters = "!@#$%^&*()_+-=[]{}|;:,.<>?".toSet()

override fun isValid(password: String): Boolean {
return password.any { it in specialCharacters }
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return stringProvider.passwordMissingSpecialCharacter
}
}

/**
* Defines a custom validation rule using a regular expression and provides a specific error
* message on failure.
*/
class Custom(val regex: Regex, val errorMessage: String)
class Custom(
val regex: Regex,
val errorMessage: String
) : PasswordRule() {
override fun isValid(password: String): Boolean {
return regex.matches(password)
}

override fun getErrorMessage(stringProvider: AuthUIStringProvider): String {
return errorMessage
}
}

/**
* Validates whether the given password meets this rule's requirements.
*
* @param password The password to validate
* @return true if the password meets this rule's requirements, false otherwise
*/
internal abstract fun isValid(password: String): Boolean

/**
* Returns the appropriate error message for this rule when validation fails.
*
* @param stringProvider The string provider for localized error messages
* @return The localized error message for this rule
*/
internal abstract fun getErrorMessage(stringProvider: AuthUIStringProvider): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.validators

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

internal class EmailValidator(override val stringProvider: AuthUIStringProvider) : FieldValidator {
private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)

override val hasError: Boolean
get() = _validationStatus.hasError

override val errorMessage: String
get() = _validationStatus.errorMessage ?: ""

override fun validate(value: String): Boolean {
if (value.isEmpty()) {
_validationStatus = FieldValidationStatus(
hasError = true,
errorMessage = stringProvider.missingEmailAddress
)
return false
}

if (!android.util.Patterns.EMAIL_ADDRESS.matcher(value).matches()) {
_validationStatus = FieldValidationStatus(
hasError = true,
errorMessage = stringProvider.invalidEmailAddress
)
return false
}

_validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.validators

/**
* Class for encapsulating [hasError] and [errorMessage] properties in
* internal FieldValidator subclasses.
*/
internal class FieldValidationStatus(
val hasError: Boolean,
val errorMessage: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.validators

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

/**
* An interface for validating input fields.
*/
interface FieldValidator {
val stringProvider: AuthUIStringProvider

/**
* Returns true if the last validation failed.
*/
val hasError: Boolean

/**
* The error message for the current state.
*/
val errorMessage: String

/**
* Runs validation on a value and returns true if valid.
*/
fun validate(value: String): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.validators

import com.firebase.ui.auth.compose.configuration.AuthUIStringProvider
import com.firebase.ui.auth.compose.configuration.PasswordRule

internal class PasswordValidator(
override val stringProvider: AuthUIStringProvider,
private val rules: List<PasswordRule>
) : FieldValidator {
private var _validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)

override val hasError: Boolean
get() = _validationStatus.hasError

override val errorMessage: String
get() = _validationStatus.errorMessage ?: ""

override fun validate(value: String): Boolean {
if (value.isEmpty()) {
_validationStatus = FieldValidationStatus(
hasError = true,
errorMessage = stringProvider.invalidPassword
)
return false
}

for (rule in rules) {
if (!rule.isValid(value)) {
_validationStatus = FieldValidationStatus(
hasError = true,
errorMessage = rule.getErrorMessage(stringProvider)
)
return false
}
}

_validationStatus = FieldValidationStatus(hasError = false, errorMessage = null)
return true
}
}
7 changes: 7 additions & 0 deletions auth/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@
<string name="fui_error_unknown" translation_description="General error messages for an unknown failure.">An unknown error occurred.</string>
<string name="fui_error_invalid_password" translation_description="Error message for when the user enters an invalid or wrong password.">Incorrect password.</string>

<!-- Password validation messages -->
<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>
<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>
<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>
<string name="fui_error_password_missing_digit" translation_description="Error message when password is missing digit">Password must contain at least one number</string>
<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>

<!-- Accessibility -->
<string name="fui_accessibility_logo" translation_description="Content description for app logo">App logo</string>

Expand Down
Loading