Skip to content
Open
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
16 changes: 16 additions & 0 deletions auth/src/main/java/com/firebase/ui/auth/AuthException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,22 @@ abstract class AuthException(
cause: Throwable? = null
) : AuthException(message, cause)

/**
* Phone verification is in cooldown period for the same phone number.
*
* This exception is thrown when attempting to verify the same phone number
* again before the cooldown period (timeout) has expired.
*
* @property message The detailed error message
* @property cooldownSeconds The number of seconds remaining in the cooldown period
* @property cause The underlying [Throwable] that caused this exception
*/
class PhoneVerificationCooldownException(
message: String,
val cooldownSeconds: Long,
cause: Throwable? = null
) : AuthException(message, cause)

/**
* Multi-Factor Authentication is required to proceed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ private fun getRecoveryMessage(
}

is AuthException.TooManyRequestsException -> stringProvider.tooManyRequestsRecoveryMessage
is AuthException.PhoneVerificationCooldownException -> {
// Use the custom message which includes remaining cooldown time
error.message ?: stringProvider.unknownErrorRecoveryMessage
}
is AuthException.MfaRequiredException -> stringProvider.mfaRequiredRecoveryMessage
is AuthException.AccountLinkingRequiredException -> {
// Use the custom message which includes email and provider details
Expand Down Expand Up @@ -194,6 +198,7 @@ private fun getRecoveryActionText(
is AuthException.InvalidCredentialsException,
is AuthException.WeakPasswordException,
is AuthException.TooManyRequestsException,
is AuthException.PhoneVerificationCooldownException -> stringProvider.retryAction
is AuthException.UnknownException -> stringProvider.retryAction

else -> stringProvider.retryAction
Expand All @@ -214,6 +219,7 @@ private fun isRecoverable(error: AuthException): Boolean {
is AuthException.WeakPasswordException -> true
is AuthException.EmailAlreadyInUseException -> true
is AuthException.TooManyRequestsException -> false // User must wait
is AuthException.PhoneVerificationCooldownException -> false // User must wait for cooldown
is AuthException.MfaRequiredException -> true
is AuthException.AccountLinkingRequiredException -> true
is AuthException.AuthCancelledException -> true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ fun PhoneAuthScreen(
val forceResendingToken =
rememberSaveable { mutableStateOf<PhoneAuthProvider.ForceResendingToken?>(null) }
val resendTimerSeconds = rememberSaveable { mutableIntStateOf(0) }
val pendingVerificationPhoneNumber = remember { mutableStateOf<String?>(null) }
val verificationStartTime = remember { mutableStateOf<Long?>(null) }

val authState by authUI.authStateFlow().collectAsState(AuthState.Idle)
val isLoading = authState is AuthState.Loading
Expand Down Expand Up @@ -189,6 +191,9 @@ fun PhoneAuthScreen(
}

is AuthState.SMSAutoVerified -> {
// Auto-verification succeeded, clear pending verification tracking
pendingVerificationPhoneNumber.value = null
verificationStartTime.value = null
// Auto-verification succeeded, sign in with the credential
coroutineScope.launch {
try {
Expand Down Expand Up @@ -246,6 +251,33 @@ fun PhoneAuthScreen(
onSendCodeClick = {
coroutineScope.launch {
try {
val currentTime = System.currentTimeMillis()
val timeoutMs = provider.timeout * 1000
val timeSinceLastVerification = verificationStartTime.value?.let {
currentTime - it
} ?: Long.MAX_VALUE

// Check if the same phone number is being verified again within the cooldown period
val storedNumber = pendingVerificationPhoneNumber.value
val isSameNumber = storedNumber != null && fullPhoneNumber == storedNumber

// Check cooldown: same number and still within timeout period
if (isSameNumber && timeSinceLastVerification < timeoutMs) {
// Calculate remaining cooldown time in seconds
val remainingCooldownSeconds = ((timeoutMs - timeSinceLastVerification) / 1000).coerceAtLeast(1)
val cooldownException = AuthException.PhoneVerificationCooldownException(
message = "Please wait ${remainingCooldownSeconds} second${if (remainingCooldownSeconds != 1L) "s" else ""} before verifying the same phone number again. The cooldown period is ${provider.timeout} seconds.",
cooldownSeconds = remainingCooldownSeconds
)
// Update auth state to show the error
authUI.updateAuthState(AuthState.Error(cooldownException))
throw cooldownException
}

// Track the phone number and start time for cooldown checking
pendingVerificationPhoneNumber.value = fullPhoneNumber
verificationStartTime.value = currentTime

authUI.verifyPhoneNumber(
provider = provider,
activity = activity,
Expand Down
Loading