From 85e4ff9d2af0572c24d2fd27ee9a1feb26ac6545 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 30 May 2021 18:11:27 +0300 Subject: [PATCH 1/8] Implement siginInByEmailLink --- build.gradle.kts | 3 + firebase-auth/build.gradle.kts | 1 + .../kotlin/dev/gitlive/firebase/auth/auth.kt | 5 ++ .../kotlin/dev/gitlive/firebase/auth/auth.kt | 8 +- .../gitlive/firebase/auth/EmulatorRestApi.kt | 28 +++++++ .../kotlin/dev/gitlive/firebase/auth/auth.kt | 79 ++++++++++++++++--- .../kotlin/dev/gitlive/firebase/auth/auth.kt | 5 ++ .../kotlin/dev/gitlive/firebase/auth/auth.kt | 6 ++ .../kotlin/dev/gitlive/firebase/externals.kt | 2 + 9 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5e9a8d57d..2d7a241c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -214,12 +214,15 @@ subprojects { "commonTestImplementation"(kotlin("test-common")) "commonTestImplementation"(kotlin("test-annotations-common")) "commonTestImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0") + "commonTestImplementation"("io.ktor:ktor-client-core:1.6.0") + "commonTestImplementation"("io.ktor:ktor-client-serialization:1.6.0") "jsTestImplementation"(kotlin("test-js")) "androidAndroidTestImplementation"(kotlin("test-junit")) "androidAndroidTestImplementation"("junit:junit:4.13.2") "androidAndroidTestImplementation"("androidx.test:core:1.3.0") "androidAndroidTestImplementation"("androidx.test.ext:junit:1.1.2") "androidAndroidTestImplementation"("androidx.test:runner:1.3.0") + "androidAndroidTestImplementation"("io.ktor:ktor-client-android:1.6.0") } } diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index d0e582f22..1ad6de368 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -9,6 +9,7 @@ version = project.property("firebase-auth.version") as String plugins { id("com.android.library") kotlin("multiplatform") + kotlin("plugin.serialization") version "1.5.0" //id("com.quittle.android-emulator") version "0.2.0" } diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt index d23af170b..6f7c09ab2 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -66,6 +66,8 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.toAndroid()).await().run { Unit } + actual fun isSignInWithEmailLink(link: String) = android.isSignInWithEmailLink(link) + actual suspend fun signInWithEmailAndPassword(email: String, password: String) = AuthResult(android.signInWithEmailAndPassword(email, password).await()) @@ -77,6 +79,9 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase. actual suspend fun signInWithCredential(authCredential: AuthCredential) = AuthResult(android.signInWithCredential(authCredential.android).await()) + actual suspend fun signInWithEmailLink(email: String, link: String) = + AuthResult(android.signInWithEmailLink(email, link).await()) + actual suspend fun signOut() = android.signOut() actual suspend fun updateCurrentUser(user: FirebaseUser) = android.updateCurrentUser(user.android).await().run { Unit } diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt index ea9981b40..c27b137ac 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -27,10 +27,12 @@ expect class FirebaseAuth { suspend fun fetchSignInMethodsForEmail(email: String): List suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings? = null) suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) + fun isSignInWithEmailLink(link: String): Boolean suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult suspend fun signInWithCustomToken(token: String): AuthResult suspend fun signInAnonymously(): AuthResult suspend fun signInWithCredential(authCredential: AuthCredential): AuthResult + suspend fun signInWithEmailLink(email: String, link: String): AuthResult suspend fun signOut() suspend fun updateCurrentUser(user: FirebaseUser) suspend fun verifyPasswordResetCode(code: String): String @@ -67,7 +69,11 @@ data class ActionCodeSettings( val iOSBundleId: String? = null ) -data class AndroidPackageName(val packageName: String, val installIfNotAvailable: Boolean, val minimumVersion: String?) +data class AndroidPackageName( + val packageName: String, + val installIfNotAvailable: Boolean = true, + val minimumVersion: String? = null +) expect open class FirebaseAuthException : FirebaseException expect class FirebaseAuthActionCodeException : FirebaseAuthException diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt new file mode 100644 index 000000000..213182f9b --- /dev/null +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt @@ -0,0 +1,28 @@ +package dev.gitlive.firebase.auth + +import io.ktor.client.HttpClient +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.features.json.serializer.KotlinxSerializer +import io.ktor.client.request.get +import kotlinx.serialization.Serializable + +suspend fun fetchOobCodes(projectId: String): List { + val client = HttpClient { + install(JsonFeature) { + serializer = KotlinxSerializer() + } + } + + return client.get("http://$emulatorHost:9099/emulator/v1/projects/${projectId}/oobCodes").oobCodes +} + +@Serializable +data class OobCode( + val email: String, + val requestType: String, + val oobCode: String, + val oobLink: String, +) + +@Serializable +data class OobCodesResponse(val oobCodes: List) diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index e14161147..a65ef0e8c 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -4,14 +4,25 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.* +import dev.gitlive.firebase.Firebase +import dev.gitlive.firebase.FirebaseOptions +import dev.gitlive.firebase.apps +import dev.gitlive.firebase.initialize import kotlin.random.Random -import kotlin.test.* +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue expect val emulatorHost: String expect val context: Any expect fun runTest(test: suspend () -> Unit) +private const val PROJECT_ID = "fir-kotlin-sdk" + class FirebaseAuthTest { @BeforeTest @@ -26,7 +37,7 @@ class FirebaseAuthTest { apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = "fir-kotlin-sdk", + projectId = PROJECT_ID, gcmSenderId = "846484016111" ) ) @@ -36,14 +47,15 @@ class FirebaseAuthTest { @Test fun testSignInWithUsernameAndPassword() = runTest { - val uid = getTestUid("test@test.com", "test123") - val result = Firebase.auth.signInWithEmailAndPassword("test@test.com", "test123") + val email = getRandomEmail() + val uid = getTestUid(email, "test123") + val result = Firebase.auth.signInWithEmailAndPassword(email, "test123") assertEquals(uid, result.user!!.uid) } @Test fun testCreateUserWithEmailAndPassword() = runTest { - val email = "test+${Random.nextInt(100000)}@test.com" + val email = getRandomEmail() val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) assertEquals(null, createResult.user?.displayName) @@ -58,7 +70,7 @@ class FirebaseAuthTest { @Test fun testFetchSignInMethods() = runTest { - val email = "test+${Random.nextInt(100000)}@test.com" + val email = getRandomEmail() var signInMethodResult = Firebase.auth.fetchSignInMethodsForEmail(email) assertEquals(emptyList(), signInMethodResult) Firebase.auth.createUserWithEmailAndPassword(email, "test123") @@ -70,33 +82,74 @@ class FirebaseAuthTest { @Test fun testSendEmailVerification() = runTest { - val email = "test+${Random.nextInt(100000)}@test.com" + val email = getRandomEmail() val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) createResult.user!!.sendEmailVerification() + val oobCodes = fetchOobCodes(PROJECT_ID) + assertTrue(oobCodes.any { it.email == email && it.requestType == "VERIFY_EMAIL" }) + createResult.user!!.delete() } @Test - fun sendPasswordResetEmail() = runTest { - val email = "test+${Random.nextInt(100000)}@test.com" + fun testSendPasswordResetEmail() = runTest { + val email = getRandomEmail() val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) Firebase.auth.sendPasswordResetEmail(email) + val oobCodes = fetchOobCodes(PROJECT_ID) + assertTrue(oobCodes.any { it.email == email && it.requestType == "PASSWORD_RESET" }) + createResult.user!!.delete() } @Test fun testSignInWithCredential() = runTest { - val uid = getTestUid("test@test.com", "test123") - val credential = EmailAuthProvider.credential("test@test.com", "test123") + val email = getRandomEmail() + val uid = getTestUid(email, "test123") + val credential = EmailAuthProvider.credential(email, "test123") val result = Firebase.auth.signInWithCredential(credential) assertEquals(uid, result.user!!.uid) } + @Test + fun testSendSignInEmailLink() = runTest { + val email = getRandomEmail() + sendSgnInLink(email) + val oobCodes = fetchOobCodes(PROJECT_ID) + assertTrue(oobCodes.any { it.email == email && it.requestType == "EMAIL_SIGNIN" }) + } + + private suspend fun sendSgnInLink(email: String) { + val actionCodeSettings = ActionCodeSettings( + url = "https://example.com/signin", + canHandleCodeInApp = true, + ) + Firebase.auth.sendSignInLinkToEmail(email, actionCodeSettings) + } + + @Test + fun testIsSignInWithEmailLink() { + val validLink = "http://localhost:9099/emulator/action?mode=signIn&lang=en&oobCode=_vr0QcFcxcVeLZbrcU-GpTaZiuxlHquqdC8MSy0YM_vzWCTAQgV9Jq&apiKey=fake-api-key&continueUrl=https%3A%2F%2Fexample.com%2Fsignin" + val invalidLink = "http://localhost:9099/emulator/action?mode=signIn&lang=en&&apiKey=fake-api-key&continueUrl=https%3A%2F%2Fexample.com%2Fsignin" + assertTrue(Firebase.auth.isSignInWithEmailLink(validLink)) + assertFalse(Firebase.auth.isSignInWithEmailLink(invalidLink)) + } + + @Test + fun testSignInWithEmailLink() = runTest { + val email = getRandomEmail() + sendSgnInLink(email) + val oobCodes = fetchOobCodes(PROJECT_ID) + val oobCode = oobCodes.first { it.email == email && it.requestType == "EMAIL_SIGNIN" } + val authResult = Firebase.auth.signInWithEmailLink(oobCode.email, oobCode.oobLink) + assertNotNull(authResult.user) + } + private suspend fun getTestUid(email: String, password: String): String { val uid = Firebase.auth.let { val user = try { @@ -114,4 +167,6 @@ class FirebaseAuthTest { return uid } + + private fun getRandomEmail(): String = "test+${Random.nextInt(100000)}@test.com" } diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 9c83e881e..96e5a152e 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -58,6 +58,8 @@ actual class FirebaseAuth internal constructor(val ios: FIRAuth) { actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = ios.await { sendSignInLinkToEmail(email, actionCodeSettings.toIos(), it) }.run { Unit } + actual fun isSignInWithEmailLink(link: String) = ios.isSignInWithEmailLink(link) + actual suspend fun signInWithEmailAndPassword(email: String, password: String) = AuthResult(ios.awaitResult { signInWithEmail(email = email, password = password, completion = it) }) @@ -70,6 +72,9 @@ actual class FirebaseAuth internal constructor(val ios: FIRAuth) { actual suspend fun signInWithCredential(authCredential: AuthCredential) = AuthResult(ios.awaitResult { signInWithCredential(authCredential.ios, it) }) + actual suspend fun signInWithEmailLink(email: String, link: String) = + AuthResult(ios.awaitResult { signInWithEmail(email = email, link = link, completion = it) }) + actual suspend fun signOut() = ios.throwError { signOut(it) }.run { Unit } actual suspend fun updateCurrentUser(user: FirebaseUser) = ios.await { updateCurrentUser(user.ios, it) }.run { Unit } diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt index 8f270e351..45ec4fc2d 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -53,6 +53,8 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = rethrow { js.sendSignInLinkToEmail(email, actionCodeSettings.toJson()).await() } + actual fun isSignInWithEmailLink(link: String) = rethrow { js.isSignInWithEmailLink(link) } + actual suspend fun signInWithEmailAndPassword(email: String, password: String) = rethrow { AuthResult(js.signInWithEmailAndPassword(email, password).await()) } @@ -65,6 +67,9 @@ actual class FirebaseAuth internal constructor(val js: firebase.auth.Auth) { actual suspend fun signInWithCredential(authCredential: AuthCredential) = rethrow { AuthResult(js.signInWithCredential(authCredential.js).await()) } + actual suspend fun signInWithEmailLink(email: String, link: String) = + rethrow { AuthResult(js.signInWithEmailLink(email, link).await()) } + actual suspend fun signOut() = rethrow { js.signOut().await() } actual suspend fun updateCurrentUser(user: FirebaseUser) = @@ -119,6 +124,7 @@ actual class AuthTokenResult(val js: firebase.auth.IdTokenResult) { } internal fun ActionCodeSettings.toJson() = json( + "url" to url, "android" to (androidPackageName?.run { json("installApp" to installIfNotAvailable, "minimumVersion" to minimumVersion, "packageName" to packageName) } ?: undefined), "dynamicLinkDomain" to (dynamicLinkDomain ?: undefined), "handleCodeInApp" to canHandleCodeInApp, diff --git a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt index c828184e9..9dc004585 100644 --- a/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt +++ b/firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt @@ -56,12 +56,14 @@ external object firebase { fun fetchSignInMethodsForEmail(email: String): Promise> fun sendPasswordResetEmail(email: String, actionCodeSettings: Any?): Promise fun sendSignInLinkToEmail(email: String, actionCodeSettings: Any?): Promise + fun isSignInWithEmailLink(link: String): Boolean fun signInWithEmailAndPassword(email: String, password: String): Promise fun signInWithCustomToken(token: String): Promise fun signInAnonymously(): Promise fun signInWithCredential(authCredential: AuthCredential): Promise fun signInWithPopup(provider: AuthProvider): Promise fun signInWithRedirect(provider: AuthProvider): Promise + fun signInWithEmailLink(email: String, link: String): Promise fun getRedirectResult(): Promise fun signOut(): Promise fun updateCurrentUser(user: user.User?): Promise From 7f8397f35723919eaecf82b73707a100aeed5a7f Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 30 Jun 2021 09:56:45 +0300 Subject: [PATCH 2/8] Add JS ktor engine --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index 2d7a241c6..9b0f67399 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -217,6 +217,7 @@ subprojects { "commonTestImplementation"("io.ktor:ktor-client-core:1.6.0") "commonTestImplementation"("io.ktor:ktor-client-serialization:1.6.0") "jsTestImplementation"(kotlin("test-js")) + "jsTestImplementation"("io.ktor:ktor-client-js:1.6.0") "androidAndroidTestImplementation"(kotlin("test-junit")) "androidAndroidTestImplementation"("junit:junit:4.13.2") "androidAndroidTestImplementation"("androidx.test:core:1.3.0") From c01d670a29a0ce273a5fb784e36918c1a4f54a23 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 19 Aug 2021 20:09:32 +0300 Subject: [PATCH 3/8] Fix Firebase Auth tests --- build.gradle.kts | 5 ---- firebase-auth/build.gradle.kts | 30 +++++++++++++++++++ .../kotlin/dev/gitlive/firebase/auth/auth.kt | 2 +- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a47c0f096..daf42f00a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -212,17 +212,12 @@ subprojects { "androidMainImplementation"(platform("com.google.firebase:firebase-bom:28.3.1")) "commonTestImplementation"(kotlin("test-common")) "commonTestImplementation"(kotlin("test-annotations-common")) - "commonTestImplementation"("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1") - "commonTestImplementation"("io.ktor:ktor-client-core:1.6.0") - "commonTestImplementation"("io.ktor:ktor-client-serialization:1.6.0") "jsTestImplementation"(kotlin("test-js")) - "jsTestImplementation"("io.ktor:ktor-client-js:1.6.0") "androidAndroidTestImplementation"(kotlin("test-junit")) "androidAndroidTestImplementation"("junit:junit:4.13.2") "androidAndroidTestImplementation"("androidx.test:core:1.4.0") "androidAndroidTestImplementation"("androidx.test.ext:junit:1.1.3") "androidAndroidTestImplementation"("androidx.test:runner:1.4.0") - "androidAndroidTestImplementation"("io.ktor:ktor-client-android:1.6.0") } } diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index ed1b922e1..2d392950f 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -160,15 +160,45 @@ kotlin { } } + val commonTest by getting { + dependencies { + // use Ktor for fetching Firebase emulator REST API in tests + implementation("io.ktor:ktor-client-core:1.6.2") + implementation("io.ktor:ktor-client-serialization:1.6.2") + } + } + val androidMain by getting { dependencies { api("com.google.firebase:firebase-auth-ktx") } } + val androidAndroidTest by getting { + dependencies { + implementation("io.ktor:ktor-client-android:1.6.2") + } + } + val iosMain by getting + val iosTest by getting { + dependencies { + // iOS Ktor HttpClient requires 'native-mt' coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt") { + isForce = true + } + implementation("io.ktor:ktor-client-ios:1.6.2") + } + } + val jsMain by getting + + val jsTest by getting { + dependencies { + implementation("io.ktor:ktor-client-js:1.6.2") + } + } } } diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index 8f4cdb278..deb4776ba 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -147,7 +147,7 @@ class FirebaseAuthTest { } @Test - fun testSignInWithEmailLink() = runTest { + fun testSignInWithEmailLink() = runTest(skip) { val email = getRandomEmail() sendSgnInLink(email) val oobCodes = fetchOobCodes(PROJECT_ID) From bb493555aca791c4463c4d75df82746b593b72fa Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 19 Aug 2021 21:33:41 +0300 Subject: [PATCH 4/8] Upgrade serialization plugin --- firebase-auth/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index 2d392950f..5e04f2ab0 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -10,7 +10,7 @@ version = project.property("firebase-auth.version") as String plugins { id("com.android.library") kotlin("multiplatform") - kotlin("plugin.serialization") version "1.5.0" + kotlin("plugin.serialization") version "1.5.21" //id("com.quittle.android-emulator") version "0.2.0" } From 5015a66dffbc70a779b674571d659e39f57e685f Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 19 Aug 2021 21:36:41 +0300 Subject: [PATCH 5/8] Cleanup --- .../kotlin/dev/gitlive/firebase/auth/auth.kt | 28 ++++++++++--------- .../{auth => emulator}/EmulatorRestApi.kt | 10 +++++-- 2 files changed, 22 insertions(+), 16 deletions(-) rename firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/{auth => emulator}/EmulatorRestApi.kt (66%) diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index deb4776ba..ad64f74d3 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -7,6 +7,7 @@ package dev.gitlive.firebase.auth import dev.gitlive.firebase.Firebase import dev.gitlive.firebase.FirebaseOptions import dev.gitlive.firebase.apps +import dev.gitlive.firebase.emulator.fetchOobCodes import dev.gitlive.firebase.initialize import kotlin.random.Random import kotlin.test.BeforeTest @@ -25,6 +26,7 @@ expect val currentPlatform: Platform enum class Platform { Android, IOS, JS } private const val PROJECT_ID = "fir-kotlin-sdk" +private const val EMULATOR_PORT = 9099 class FirebaseAuthTest { @@ -47,7 +49,7 @@ class FirebaseAuthTest { gcmSenderId = "846484016111" ) ) - Firebase.auth.useEmulator(emulatorHost, 9099) + Firebase.auth.useEmulator(emulatorHost, EMULATOR_PORT) } } @@ -93,7 +95,7 @@ class FirebaseAuthTest { assertNotEquals(null, createResult.user?.uid) createResult.user!!.sendEmailVerification() - val oobCodes = fetchOobCodes(PROJECT_ID) + val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) assertTrue(oobCodes.any { it.email == email && it.requestType == "VERIFY_EMAIL" }) createResult.user!!.delete() @@ -107,7 +109,7 @@ class FirebaseAuthTest { Firebase.auth.sendPasswordResetEmail(email) - val oobCodes = fetchOobCodes(PROJECT_ID) + val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) assertTrue(oobCodes.any { it.email == email && it.requestType == "PASSWORD_RESET" }) createResult.user!!.delete() @@ -126,18 +128,10 @@ class FirebaseAuthTest { fun testSendSignInEmailLink() = runTest { val email = getRandomEmail() sendSgnInLink(email) - val oobCodes = fetchOobCodes(PROJECT_ID) + val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) assertTrue(oobCodes.any { it.email == email && it.requestType == "EMAIL_SIGNIN" }) } - private suspend fun sendSgnInLink(email: String) { - val actionCodeSettings = ActionCodeSettings( - url = "https://example.com/signin", - canHandleCodeInApp = true, - ) - Firebase.auth.sendSignInLinkToEmail(email, actionCodeSettings) - } - @Test fun testIsSignInWithEmailLink() { val validLink = "http://localhost:9099/emulator/action?mode=signIn&lang=en&oobCode=_vr0QcFcxcVeLZbrcU-GpTaZiuxlHquqdC8MSy0YM_vzWCTAQgV9Jq&apiKey=fake-api-key&continueUrl=https%3A%2F%2Fexample.com%2Fsignin" @@ -150,12 +144,20 @@ class FirebaseAuthTest { fun testSignInWithEmailLink() = runTest(skip) { val email = getRandomEmail() sendSgnInLink(email) - val oobCodes = fetchOobCodes(PROJECT_ID) + val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) val oobCode = oobCodes.first { it.email == email && it.requestType == "EMAIL_SIGNIN" } val authResult = Firebase.auth.signInWithEmailLink(oobCode.email, oobCode.oobLink) assertNotNull(authResult.user) } + private suspend fun sendSgnInLink(email: String) { + val actionCodeSettings = ActionCodeSettings( + url = "https://example.com/signin", + canHandleCodeInApp = true, + ) + Firebase.auth.sendSignInLinkToEmail(email, actionCodeSettings) + } + private suspend fun getTestUid(email: String, password: String): String { val uid = Firebase.auth.let { val user = try { diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt similarity index 66% rename from firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt rename to firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt index 213182f9b..b0eb18451 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/EmulatorRestApi.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt @@ -1,4 +1,4 @@ -package dev.gitlive.firebase.auth +package dev.gitlive.firebase.emulator import io.ktor.client.HttpClient import io.ktor.client.features.json.JsonFeature @@ -6,14 +6,18 @@ import io.ktor.client.features.json.serializer.KotlinxSerializer import io.ktor.client.request.get import kotlinx.serialization.Serializable -suspend fun fetchOobCodes(projectId: String): List { +suspend fun fetchOobCodes( + projectId: String, + emulatorHost: String, + emulatorPort: Int +): List { val client = HttpClient { install(JsonFeature) { serializer = KotlinxSerializer() } } - return client.get("http://$emulatorHost:9099/emulator/v1/projects/${projectId}/oobCodes").oobCodes + return client.get("http://$emulatorHost:$emulatorPort/emulator/v1/projects/${projectId}/oobCodes").oobCodes } @Serializable From 74483fdbe31ca5a33ddc9fb5096bfc475695bd8b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 1 Oct 2021 21:41:31 +0300 Subject: [PATCH 6/8] Remove Ktor --- firebase-auth/build.gradle.kts | 31 -------- .../kotlin/dev/gitlive/firebase/auth/auth.kt | 75 ++++--------------- .../firebase/emulator/EmulatorRestApi.kt | 32 -------- 3 files changed, 13 insertions(+), 125 deletions(-) delete mode 100644 firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt diff --git a/firebase-auth/build.gradle.kts b/firebase-auth/build.gradle.kts index 86a6c3ef0..54cb61fc2 100644 --- a/firebase-auth/build.gradle.kts +++ b/firebase-auth/build.gradle.kts @@ -10,7 +10,6 @@ version = project.property("firebase-auth.version") as String plugins { id("com.android.library") kotlin("multiplatform") - kotlin("plugin.serialization") version "1.5.21" //id("com.quittle.android-emulator") version "0.2.0" } @@ -160,26 +159,12 @@ kotlin { } } - val commonTest by getting { - dependencies { - // use Ktor for fetching Firebase emulator REST API in tests - implementation("io.ktor:ktor-client-core:1.6.2") - implementation("io.ktor:ktor-client-serialization:1.6.2") - } - } - val androidMain by getting { dependencies { api("com.google.firebase:firebase-auth-ktx") } } - val androidAndroidTest by getting { - dependencies { - implementation("io.ktor:ktor-client-android:1.6.2") - } - } - val iosMain by getting val iosSimulatorArm64Main by getting iosSimulatorArm64Main.dependsOn(iosMain) @@ -188,23 +173,7 @@ kotlin { val iosSimulatorArm64Test by sourceSets.getting iosSimulatorArm64Test.dependsOn(iosTest) - val iosTest by getting { - dependencies { - // iOS Ktor HttpClient requires 'native-mt' coroutines - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt") { - isForce = true - } - implementation("io.ktor:ktor-client-ios:1.6.2") - } - } - val jsMain by getting - - val jsTest by getting { - dependencies { - implementation("io.ktor:ktor-client-js:1.6.2") - } - } } } diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt index ad64f74d3..2fe59c4c6 100644 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt +++ b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/auth/auth.kt @@ -4,19 +4,9 @@ package dev.gitlive.firebase.auth -import dev.gitlive.firebase.Firebase -import dev.gitlive.firebase.FirebaseOptions -import dev.gitlive.firebase.apps -import dev.gitlive.firebase.emulator.fetchOobCodes -import dev.gitlive.firebase.initialize +import dev.gitlive.firebase.* import kotlin.random.Random -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* expect val emulatorHost: String expect val context: Any @@ -25,9 +15,6 @@ expect val currentPlatform: Platform enum class Platform { Android, IOS, JS } -private const val PROJECT_ID = "fir-kotlin-sdk" -private const val EMULATOR_PORT = 9099 - class FirebaseAuthTest { // Skip the tests on iOS simulator due keychain exceptions @@ -45,25 +32,24 @@ class FirebaseAuthTest { apiKey = "AIzaSyCK87dcMFhzCz_kJVs2cT2AVlqOTLuyWV0", databaseUrl = "https://fir-kotlin-sdk.firebaseio.com", storageBucket = "fir-kotlin-sdk.appspot.com", - projectId = PROJECT_ID, + projectId = "fir-kotlin-sdk", gcmSenderId = "846484016111" ) ) - Firebase.auth.useEmulator(emulatorHost, EMULATOR_PORT) + Firebase.auth.useEmulator(emulatorHost, 9099) } } @Test fun testSignInWithUsernameAndPassword() = runTest(skip) { - val email = getRandomEmail() - val uid = getTestUid(email, "test123") - val result = Firebase.auth.signInWithEmailAndPassword(email, "test123") + val uid = getTestUid("test@test.com", "test123") + val result = Firebase.auth.signInWithEmailAndPassword("test@test.com", "test123") assertEquals(uid, result.user!!.uid) } @Test fun testCreateUserWithEmailAndPassword() = runTest(skip) { - val email = getRandomEmail() + val email = "test+${Random.nextInt(100000)}@test.com" val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) assertEquals(null, createResult.user?.displayName) @@ -78,7 +64,7 @@ class FirebaseAuthTest { @Test fun testFetchSignInMethods() = runTest(skip) { - val email = getRandomEmail() + val email = "test+${Random.nextInt(100000)}@test.com" var signInMethodResult = Firebase.auth.fetchSignInMethodsForEmail(email) assertEquals(emptyList(), signInMethodResult) Firebase.auth.createUserWithEmailAndPassword(email, "test123") @@ -90,48 +76,33 @@ class FirebaseAuthTest { @Test fun testSendEmailVerification() = runTest(skip) { - val email = getRandomEmail() + val email = "test+${Random.nextInt(100000)}@test.com" val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) createResult.user!!.sendEmailVerification() - val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) - assertTrue(oobCodes.any { it.email == email && it.requestType == "VERIFY_EMAIL" }) - createResult.user!!.delete() } @Test - fun testSendPasswordResetEmail() = runTest(skip) { - val email = getRandomEmail() + fun sendPasswordResetEmail() = runTest(skip) { + val email = "test+${Random.nextInt(100000)}@test.com" val createResult = Firebase.auth.createUserWithEmailAndPassword(email, "test123") assertNotEquals(null, createResult.user?.uid) Firebase.auth.sendPasswordResetEmail(email) - val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) - assertTrue(oobCodes.any { it.email == email && it.requestType == "PASSWORD_RESET" }) - createResult.user!!.delete() } @Test fun testSignInWithCredential() = runTest(skip) { - val email = getRandomEmail() - val uid = getTestUid(email, "test123") - val credential = EmailAuthProvider.credential(email, "test123") + val uid = getTestUid("test@test.com", "test123") + val credential = EmailAuthProvider.credential("test@test.com", "test123") val result = Firebase.auth.signInWithCredential(credential) assertEquals(uid, result.user!!.uid) } - @Test - fun testSendSignInEmailLink() = runTest { - val email = getRandomEmail() - sendSgnInLink(email) - val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) - assertTrue(oobCodes.any { it.email == email && it.requestType == "EMAIL_SIGNIN" }) - } - @Test fun testIsSignInWithEmailLink() { val validLink = "http://localhost:9099/emulator/action?mode=signIn&lang=en&oobCode=_vr0QcFcxcVeLZbrcU-GpTaZiuxlHquqdC8MSy0YM_vzWCTAQgV9Jq&apiKey=fake-api-key&continueUrl=https%3A%2F%2Fexample.com%2Fsignin" @@ -140,24 +111,6 @@ class FirebaseAuthTest { assertFalse(Firebase.auth.isSignInWithEmailLink(invalidLink)) } - @Test - fun testSignInWithEmailLink() = runTest(skip) { - val email = getRandomEmail() - sendSgnInLink(email) - val oobCodes = fetchOobCodes(PROJECT_ID, emulatorHost, EMULATOR_PORT) - val oobCode = oobCodes.first { it.email == email && it.requestType == "EMAIL_SIGNIN" } - val authResult = Firebase.auth.signInWithEmailLink(oobCode.email, oobCode.oobLink) - assertNotNull(authResult.user) - } - - private suspend fun sendSgnInLink(email: String) { - val actionCodeSettings = ActionCodeSettings( - url = "https://example.com/signin", - canHandleCodeInApp = true, - ) - Firebase.auth.sendSignInLinkToEmail(email, actionCodeSettings) - } - private suspend fun getTestUid(email: String, password: String): String { val uid = Firebase.auth.let { val user = try { @@ -175,6 +128,4 @@ class FirebaseAuthTest { return uid } - - private fun getRandomEmail(): String = "test+${Random.nextInt(100000)}@test.com" } diff --git a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt b/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt deleted file mode 100644 index b0eb18451..000000000 --- a/firebase-auth/src/commonTest/kotlin/dev/gitlive/firebase/emulator/EmulatorRestApi.kt +++ /dev/null @@ -1,32 +0,0 @@ -package dev.gitlive.firebase.emulator - -import io.ktor.client.HttpClient -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer -import io.ktor.client.request.get -import kotlinx.serialization.Serializable - -suspend fun fetchOobCodes( - projectId: String, - emulatorHost: String, - emulatorPort: Int -): List { - val client = HttpClient { - install(JsonFeature) { - serializer = KotlinxSerializer() - } - } - - return client.get("http://$emulatorHost:$emulatorPort/emulator/v1/projects/${projectId}/oobCodes").oobCodes -} - -@Serializable -data class OobCode( - val email: String, - val requestType: String, - val oobCode: String, - val oobLink: String, -) - -@Serializable -data class OobCodesResponse(val oobCodes: List) From 8eedde2c748e89b82ca3b0d4fcc9a963f65ad1c8 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 1 Oct 2021 22:29:06 +0300 Subject: [PATCH 7/8] Make idToken and accessToken nullable --- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 2 +- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 2 +- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 8 ++++++-- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 02fcc5d9b..a5dde88cf 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -39,7 +39,7 @@ actual object GithubAuthProvider { } actual object GoogleAuthProvider { - actual fun credential(idToken: String, accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.GoogleAuthProvider.getCredential(idToken, accessToken)) + actual fun credential(idToken: String?, accessToken: String?): AuthCredential = AuthCredential(com.google.firebase.auth.GoogleAuthProvider.getCredential(idToken, accessToken)) } actual class OAuthProvider(val android: com.google.firebase.auth.OAuthProvider) { diff --git a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index d0fcf08f0..ae23f85ab 100644 --- a/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/commonMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -26,7 +26,7 @@ expect object GithubAuthProvider { } expect object GoogleAuthProvider { - fun credential(idToken: String, accessToken: String): AuthCredential + fun credential(idToken: String?, accessToken: String?): AuthCredential } expect class OAuthProvider constructor( diff --git a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 7702b0118..f20b884e1 100644 --- a/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/iosMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -31,7 +31,11 @@ actual object GithubAuthProvider { } actual object GoogleAuthProvider { - actual fun credential(idToken: String, accessToken: String): AuthCredential = AuthCredential(FIRGoogleAuthProvider.credentialWithIDToken(idToken, accessToken)) + actual fun credential(idToken: String?, accessToken: String?): AuthCredential { + requireNotNull(idToken) { "idToken must not be null" } + requireNotNull(accessToken) { "accessToken must not be null" } + return AuthCredential(FIRGoogleAuthProvider.credentialWithIDToken(idToken, accessToken)) + } } actual class OAuthProvider(val ios: FIROAuthProvider) { @@ -80,4 +84,4 @@ actual interface PhoneVerificationProvider { actual object TwitterAuthProvider { actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(FIRTwitterAuthProvider.credentialWithToken(token, secret)) -} \ No newline at end of file +} diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index 0074b5704..af0b13fdf 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -29,7 +29,7 @@ actual object GithubAuthProvider { } actual object GoogleAuthProvider { - actual fun credential(idToken: String, accessToken: String): AuthCredential = + actual fun credential(idToken: String?, accessToken: String?): AuthCredential = AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) } @@ -81,4 +81,4 @@ actual interface PhoneVerificationProvider { actual object TwitterAuthProvider { actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(firebase.auth.TwitterAuthProvider.credential(token, secret)) -} \ No newline at end of file +} From 7d52847224504993ebcdd7cbd29aeea8e07d415c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 1 Oct 2021 23:47:29 +0300 Subject: [PATCH 8/8] Add args assertion --- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 7 ++++++- .../kotlin/dev/gitlive/firebase/auth/credentials.kt | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index a5dde88cf..4e0653ab2 100644 --- a/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -39,7 +39,12 @@ actual object GithubAuthProvider { } actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential = AuthCredential(com.google.firebase.auth.GoogleAuthProvider.getCredential(idToken, accessToken)) + actual fun credential(idToken: String?, accessToken: String?): AuthCredential { + require(idToken != null || accessToken != null) { + "Both parameters are optional but at least one must be present." + } + return AuthCredential(com.google.firebase.auth.GoogleAuthProvider.getCredential(idToken, accessToken)) + } } actual class OAuthProvider(val android: com.google.firebase.auth.OAuthProvider) { diff --git a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt index af0b13fdf..f5f911cc1 100644 --- a/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt +++ b/firebase-auth/src/jsMain/kotlin/dev/gitlive/firebase/auth/credentials.kt @@ -29,8 +29,12 @@ actual object GithubAuthProvider { } actual object GoogleAuthProvider { - actual fun credential(idToken: String?, accessToken: String?): AuthCredential = - AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) + actual fun credential(idToken: String?, accessToken: String?): AuthCredential { + require(idToken != null || accessToken != null) { + "Both parameters are optional but at least one must be present." + } + return AuthCredential(firebase.auth.GoogleAuthProvider.credential(idToken, accessToken)) + } } actual class OAuthProvider(val js: firebase.auth.OAuthProvider) {