Skip to content

Commit 478ef63

Browse files
committed
feat: added preconditions for specific auth provider configurations
1 parent 59325de commit 478ef63

File tree

2 files changed

+113
-11
lines changed

2 files changed

+113
-11
lines changed

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

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@
1414

1515
package com.firebase.ui.auth.compose.configuration
1616

17+
import android.content.Context
1718
import android.graphics.Color
19+
import android.util.Log
1820
import androidx.compose.ui.graphics.vector.ImageVector
21+
import com.firebase.ui.auth.AuthUI
22+
import com.firebase.ui.auth.R
23+
import com.firebase.ui.auth.util.Preconditions
24+
import com.firebase.ui.auth.util.data.PhoneNumberUtils
25+
import com.firebase.ui.auth.util.data.ProviderAvailability
1926
import com.google.firebase.auth.ActionCodeSettings
2027
import com.google.firebase.auth.EmailAuthProvider
2128
import com.google.firebase.auth.FacebookAuthProvider
@@ -78,6 +85,15 @@ abstract class AuthProvider(open val providerId: String) {
7885
*/
7986
val isEmailLinkSignInEnabled: Boolean = false,
8087

88+
/**
89+
* Forces email link sign-in to complete on the same device that initiated it.
90+
*
91+
* When enabled, prevents email links from being opened on different devices,
92+
* which is required for security when upgrading anonymous users. Defaults to true.
93+
*/
94+
95+
val isEmailLinkForceSameDeviceEnabled: Boolean = true,
96+
8197
/**
8298
* Settings for email link actions.
8399
*/
@@ -118,6 +134,11 @@ abstract class AuthProvider(open val providerId: String) {
118134
* Phone number authentication provider configuration.
119135
*/
120136
class Phone(
137+
/**
138+
* The phone number in international format.
139+
*/
140+
val defaultNumber: String?,
141+
121142
/**
122143
* The default country code to pre-select.
123144
*/
@@ -147,7 +168,29 @@ abstract class AuthProvider(open val providerId: String) {
147168
* Enables automatic retrieval of the SMS code. Defaults to true.
148169
*/
149170
val isAutoRetrievalEnabled: Boolean = true
150-
) : AuthProvider(providerId = Provider.PHONE.id)
171+
) : AuthProvider(providerId = Provider.PHONE.id) {
172+
fun validate() {
173+
defaultNumber?.let {
174+
check(PhoneNumberUtils.isValid(it)) {
175+
"Invalid phone number: $it"
176+
}
177+
}
178+
179+
check(PhoneNumberUtils.isValidIso(defaultCountryCode)) {
180+
"Invalid country iso: $defaultCountryCode"
181+
}
182+
183+
allowedCountries?.forEach { code ->
184+
check(
185+
PhoneNumberUtils.isValidIso(code) ||
186+
PhoneNumberUtils.isValid(code)
187+
) {
188+
"Invalid input: You must provide a valid country iso (alpha-2) " +
189+
"or code (e-164). e.g. 'us' or '+1'. Invalid code: $code"
190+
}
191+
}
192+
}
193+
}
151194

152195
/**
153196
* Google Sign-In provider configuration.
@@ -186,7 +229,30 @@ abstract class AuthProvider(open val providerId: String) {
186229
providerId = Provider.GOOGLE.id,
187230
scopes = scopes,
188231
customParameters = customParameters
189-
)
232+
) {
233+
fun validate(context: Context) {
234+
// TODO(demolaf): do we need this? since we are requesting this in AuthProvider.Google?
235+
// if serverClientId is nullable do we still need to throw an IllegalStateException?
236+
Preconditions.checkConfigured(
237+
context,
238+
"Check your google-services plugin configuration, the" +
239+
" default_web_client_id string wasn't populated.",
240+
R.string.default_web_client_id
241+
)
242+
243+
for (scope in scopes) {
244+
if ("email" == scope) {
245+
Log.w(
246+
"AuthProvider.Google",
247+
"The GoogleSignInOptions passed to setSignInOptions does not " +
248+
"request the 'email' scope. In most cases this is a mistake! " +
249+
"Call requestEmail() on the GoogleSignInOptions object."
250+
)
251+
break
252+
}
253+
}
254+
}
255+
}
190256

191257
/**
192258
* Facebook Login provider configuration.
@@ -210,7 +276,26 @@ abstract class AuthProvider(open val providerId: String) {
210276
providerId = Provider.FACEBOOK.id,
211277
scopes = scopes,
212278
customParameters = customParameters
213-
)
279+
) {
280+
fun validate(context: Context) {
281+
if (ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
282+
throw RuntimeException(
283+
"Facebook provider cannot be configured " +
284+
"without dependency. Did you forget to add " +
285+
"'com.facebook.android:facebook-login:VERSION' dependency?"
286+
)
287+
}
288+
289+
// TODO(demolaf): is this required? or should we add appId to AuthProvider.Facebook
290+
// parameters above?
291+
Preconditions.checkConfigured(
292+
context,
293+
"Facebook provider unconfigured. Make sure to " +
294+
"add a `facebook_application_id` string.",
295+
R.string.facebook_application_id
296+
)
297+
}
298+
}
214299

215300
/**
216301
* Twitter/X authentication provider configuration.
@@ -314,7 +399,16 @@ abstract class AuthProvider(open val providerId: String) {
314399
/**
315400
* Anonymous authentication provider. It has no configurable properties.
316401
*/
317-
object Anonymous : AuthProvider(providerId = Provider.ANONYMOUS.id)
402+
object Anonymous : AuthProvider(providerId = Provider.ANONYMOUS.id) {
403+
fun validate(providers: List<AuthProvider>) {
404+
if (providers.size == 1 && providers.first() is Anonymous) {
405+
throw IllegalStateException(
406+
"Sign in as guest cannot be the only sign in method. " +
407+
"In this case, sign the user in anonymously your self; no UI is needed."
408+
)
409+
}
410+
}
411+
}
318412

319413
/**
320414
* A generic OAuth provider for any unsupported provider.

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,7 @@ class AuthUIConfigurationBuilder {
6868
}
6969

7070
// Cannot have only anonymous provider
71-
if (providers.size == 1 && providers.first() is AuthProvider.Anonymous) {
72-
throw IllegalStateException(
73-
"Sign in as guest cannot be the only sign in method. " +
74-
"In this case, sign the user in anonymously your self; no UI is needed."
75-
)
76-
}
71+
AuthProvider.Anonymous.validate(providers)
7772

7873
// Check for duplicate providers
7974
val providerIds = providers.map { it.providerId }
@@ -89,7 +84,20 @@ class AuthUIConfigurationBuilder {
8984
// Provider specific validations
9085
providers.forEach { provider ->
9186
when (provider) {
92-
is AuthProvider.Email -> provider.validate()
87+
is AuthProvider.Email -> {
88+
provider.validate()
89+
90+
if (isAnonymousUpgradeEnabled && provider.isEmailLinkSignInEnabled) {
91+
check(provider.isEmailLinkForceSameDeviceEnabled) {
92+
"You must force the same device flow when using email link sign in " +
93+
"with anonymous user upgrade"
94+
}
95+
}
96+
}
97+
98+
is AuthProvider.Phone -> provider.validate()
99+
is AuthProvider.Google -> provider.validate(context)
100+
is AuthProvider.Facebook -> provider.validate(context)
93101
else -> null
94102
}
95103
}

0 commit comments

Comments
 (0)