Skip to content

Commit f1dfd0c

Browse files
authored
feat: Provider Models (AuthProvider + concrete types) (#2223)
* feat: added preconditions for specific auth provider configurations * test: covers auth providers with config validations * fix: auth provider validation and tests - validate serverClientId empty string - validate applicationId empty string - remove @test(expected=) not descriptive - tests covering AuthProviders Google and Facebook config validation
1 parent 59325de commit f1dfd0c

File tree

4 files changed

+577
-90
lines changed

4 files changed

+577
-90
lines changed

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

Lines changed: 123 additions & 10 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,14 @@ 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+
val isEmailLinkForceSameDeviceEnabled: Boolean = true,
95+
8196
/**
8297
* Settings for email link actions.
8398
*/
@@ -100,11 +115,10 @@ abstract class AuthProvider(open val providerId: String) {
100115
) : AuthProvider(providerId = Provider.EMAIL.id) {
101116
fun validate() {
102117
if (isEmailLinkSignInEnabled) {
103-
val actionCodeSettings = actionCodeSettings
104-
?: requireNotNull(actionCodeSettings) {
105-
"ActionCodeSettings cannot be null when using " +
106-
"email link sign in."
107-
}
118+
val actionCodeSettings = requireNotNull(actionCodeSettings) {
119+
"ActionCodeSettings cannot be null when using " +
120+
"email link sign in."
121+
}
108122

109123
check(actionCodeSettings.canHandleCodeInApp()) {
110124
"You must set canHandleCodeInApp in your " +
@@ -118,6 +132,11 @@ abstract class AuthProvider(open val providerId: String) {
118132
* Phone number authentication provider configuration.
119133
*/
120134
class Phone(
135+
/**
136+
* The phone number in international format.
137+
*/
138+
val defaultNumber: String?,
139+
121140
/**
122141
* The default country code to pre-select.
123142
*/
@@ -147,7 +166,31 @@ abstract class AuthProvider(open val providerId: String) {
147166
* Enables automatic retrieval of the SMS code. Defaults to true.
148167
*/
149168
val isAutoRetrievalEnabled: Boolean = true
150-
) : AuthProvider(providerId = Provider.PHONE.id)
169+
) : AuthProvider(providerId = Provider.PHONE.id) {
170+
fun validate() {
171+
defaultNumber?.let {
172+
check(PhoneNumberUtils.isValid(it)) {
173+
"Invalid phone number: $it"
174+
}
175+
}
176+
177+
defaultCountryCode?.let {
178+
check(PhoneNumberUtils.isValidIso(it)) {
179+
"Invalid country iso: $it"
180+
}
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,12 +229,40 @@ 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+
if (serverClientId == null) {
235+
Preconditions.checkConfigured(
236+
context,
237+
"Check your google-services plugin configuration, the" +
238+
" default_web_client_id string wasn't populated.",
239+
R.string.default_web_client_id
240+
)
241+
} else {
242+
require(serverClientId.isNotBlank()) {
243+
"Server client ID cannot be blank."
244+
}
245+
}
246+
247+
val hasEmailScope = scopes.contains("email")
248+
if (!hasEmailScope) {
249+
Log.w(
250+
"AuthProvider.Google",
251+
"The scopes do not include 'email'. In most cases this is a mistake!"
252+
)
253+
}
254+
}
255+
}
190256

191257
/**
192258
* Facebook Login provider configuration.
193259
*/
194260
class Facebook(
261+
/**
262+
* The Facebook application ID.
263+
*/
264+
val applicationId: String? = null,
265+
195266
/**
196267
* The list of scopes (permissions) to request. Defaults to email and public_profile.
197268
*/
@@ -210,7 +281,30 @@ abstract class AuthProvider(open val providerId: String) {
210281
providerId = Provider.FACEBOOK.id,
211282
scopes = scopes,
212283
customParameters = customParameters
213-
)
284+
) {
285+
fun validate(context: Context) {
286+
if (!ProviderAvailability.IS_FACEBOOK_AVAILABLE) {
287+
throw RuntimeException(
288+
"Facebook provider cannot be configured " +
289+
"without dependency. Did you forget to add " +
290+
"'com.facebook.android:facebook-login:VERSION' dependency?"
291+
)
292+
}
293+
294+
if (applicationId == null) {
295+
Preconditions.checkConfigured(
296+
context,
297+
"Facebook provider unconfigured. Make sure to " +
298+
"add a `facebook_application_id` string or provide applicationId parameter.",
299+
R.string.facebook_application_id
300+
)
301+
} else {
302+
require(applicationId.isNotBlank()) {
303+
"Facebook application ID cannot be blank"
304+
}
305+
}
306+
}
307+
}
214308

215309
/**
216310
* Twitter/X authentication provider configuration.
@@ -314,7 +408,16 @@ abstract class AuthProvider(open val providerId: String) {
314408
/**
315409
* Anonymous authentication provider. It has no configurable properties.
316410
*/
317-
object Anonymous : AuthProvider(providerId = Provider.ANONYMOUS.id)
411+
object Anonymous : AuthProvider(providerId = Provider.ANONYMOUS.id) {
412+
fun validate(providers: List<AuthProvider>) {
413+
if (providers.size == 1 && providers.first() is Anonymous) {
414+
throw IllegalStateException(
415+
"Sign in as guest cannot be the only sign in method. " +
416+
"In this case, sign the user in anonymously your self; no UI is needed."
417+
)
418+
}
419+
}
420+
}
318421

319422
/**
320423
* A generic OAuth provider for any unsupported provider.
@@ -353,5 +456,15 @@ abstract class AuthProvider(open val providerId: String) {
353456
providerId = providerId,
354457
scopes = scopes,
355458
customParameters = customParameters
356-
)
459+
) {
460+
fun validate() {
461+
require(providerId.isNotBlank()) {
462+
"Provider ID cannot be null or empty"
463+
}
464+
465+
require(buttonLabel.isNotBlank()) {
466+
"Button label cannot be null or empty"
467+
}
468+
}
469+
}
357470
}

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

Lines changed: 16 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,21 @@ 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)
101+
is AuthProvider.GenericOAuth -> provider.validate()
93102
else -> null
94103
}
95104
}

0 commit comments

Comments
 (0)