Skip to content

Commit

Permalink
Increase Auth coverage (#79)
Browse files Browse the repository at this point in the history
* Update FirebaseCore.def

* Adding missing Auth calls

* Add iOS implementation

* Add providers

* Trying to resolve Firestore issues

* Added iOS methods

* Add Javascript

* Cleaned up .def files

Dependencies now part of actual submodules

* Add Test

* Fixed compile for tests

* Add missing firetore frameworks

* Account for nullable results

* Use Carthage instead of zip for linking.

This cleans up dependencies and reduces build time

* Track firestore version

* Update yaml

* Remove useless podspec

* Code cleanup

* Fixing remarks

* Move ActionData into operator

* fix test

Co-authored-by: Grigory Avdyushin <avdyushin.g@gmail.com>
  • Loading branch information
Daeda88 and avdyushin committed Oct 21, 2020
1 parent 7552695 commit 5844b72
Show file tree
Hide file tree
Showing 18 changed files with 1,463 additions and 138 deletions.
135 changes: 98 additions & 37 deletions firebase-auth/src/androidMain/kotlin/dev/gitlive/firebase/auth/auth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

@file:JvmName("android")
package dev.gitlive.firebase.auth

import com.google.firebase.auth.EmailAuthProvider
import com.google.firebase.auth.ActionCodeEmailInfo
import com.google.firebase.auth.ActionCodeMultiFactorInfo
import com.google.firebase.auth.FirebaseAuth.AuthStateListener
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.tasks.await

Expand All @@ -22,71 +25,129 @@ actual class FirebaseAuth internal constructor(val android: com.google.firebase.
actual val currentUser: FirebaseUser?
get() = android.currentUser?.let { FirebaseUser(it) }

actual suspend fun sendPasswordResetEmail(email: String) {
android.sendPasswordResetEmail(email).await()
actual val authStateChanged get() = callbackFlow {
val listener = AuthStateListener { auth -> offer(auth.currentUser?.let { FirebaseUser(it) }) }
android.addAuthStateListener(listener)
awaitClose { android.removeAuthStateListener(listener) }
}

actual suspend fun signInWithEmailAndPassword(email: String, password: String) =
AuthResult(android.signInWithEmailAndPassword(email, password).await())
actual val idTokenChanged: Flow<FirebaseUser?>
get() = callbackFlow {
val listener = com.google.firebase.auth.FirebaseAuth.IdTokenListener { auth -> offer(auth.currentUser?.let { FirebaseUser(it) })}
android.addIdTokenListener(listener)
awaitClose { android.removeIdTokenListener(listener) }
}

actual var languageCode: String
get() = android.languageCode ?: ""
set(value) { android.setLanguageCode(value) }

actual suspend fun applyActionCode(code: String) = android.applyActionCode(code).await().run { Unit }
actual suspend fun checkActionCode(code: String): ActionCodeResult = ActionCodeResult(android.checkActionCode(code).await())
actual suspend fun confirmPasswordReset(code: String, newPassword: String) = android.confirmPasswordReset(code, newPassword).await().run { Unit }

actual suspend fun createUserWithEmailAndPassword(email: String, password: String) =
AuthResult(android.createUserWithEmailAndPassword(email, password).await())

actual suspend fun fetchSignInMethodsForEmail(email: String): SignInMethodQueryResult = SignInMethodQueryResult(android.fetchSignInMethodsForEmail(email).await())

actual suspend fun sendPasswordResetEmail(email: String, actionCodeSettings: ActionCodeSettings?) {
android.sendPasswordResetEmail(email, actionCodeSettings?.android).await()
}

actual suspend fun sendSignInLinkToEmail(email: String, actionCodeSettings: ActionCodeSettings) = android.sendSignInLinkToEmail(email, actionCodeSettings.android).await().run { Unit }

actual suspend fun signInWithEmailAndPassword(email: String, password: String) =
AuthResult(android.signInWithEmailAndPassword(email, password).await())

actual suspend fun signInWithCustomToken(token: String) =
AuthResult(android.signInWithCustomToken(token).await())

actual suspend fun signInAnonymously() = AuthResult(android.signInAnonymously().await())

actual val authStateChanged get() = callbackFlow {
val listener = object : AuthStateListener {
override fun onAuthStateChanged(auth: com.google.firebase.auth.FirebaseAuth) {
offer(auth.currentUser?.let { FirebaseUser(it) })
}
}
android.addAuthStateListener(listener)
awaitClose { android.removeAuthStateListener(listener) }
}
actual suspend fun signInWithCredential(authCredential: AuthCredential) =
AuthResult(android.signInWithCredential(authCredential.android).await())

actual suspend fun signOut() = android.signOut()
}

actual class AuthCredential(val android: com.google.firebase.auth.AuthCredential)
actual suspend fun updateCurrentUser(user: FirebaseUser) = android.updateCurrentUser(user.android).await().run { Unit }
actual suspend fun verifyPasswordResetCode(code: String): String = android.verifyPasswordResetCode(code).await()
}

actual class AuthResult internal constructor(val android: com.google.firebase.auth.AuthResult) {
actual val user: FirebaseUser?
get() = android.user?.let { FirebaseUser(it) }
}

actual class FirebaseUser internal constructor(val android: com.google.firebase.auth.FirebaseUser) {
actual val uid: String
get() = android.uid
actual val displayName: String?
get() = android.displayName
actual val email: String?
get() = android.email
actual val phoneNumber: String?
get() = android.phoneNumber
actual val isAnonymous: Boolean
get() = android.isAnonymous
actual suspend fun delete() = android.delete().await().run { Unit }
actual suspend fun reload() = android.reload().await().run { Unit }
actual suspend fun sendEmailVerification() = android.sendEmailVerification().await().run { Unit }
actual class ActionCodeResult(val android: com.google.firebase.auth.ActionCodeResult) {
actual val operation: Operation
get() = when (android.operation) {
com.google.firebase.auth.ActionCodeResult.PASSWORD_RESET -> Operation.PasswordReset(this)
com.google.firebase.auth.ActionCodeResult.VERIFY_EMAIL -> Operation.VerifyEmail(this)
com.google.firebase.auth.ActionCodeResult.RECOVER_EMAIL -> Operation.RecoverEmail(this)
com.google.firebase.auth.ActionCodeResult.ERROR -> Operation.Error
com.google.firebase.auth.ActionCodeResult.SIGN_IN_WITH_EMAIL_LINK -> Operation.SignInWithEmailLink
com.google.firebase.auth.ActionCodeResult.VERIFY_BEFORE_CHANGE_EMAIL -> Operation.VerifyBeforeChangeEmail(this)
com.google.firebase.auth.ActionCodeResult.REVERT_SECOND_FACTOR_ADDITION -> Operation.RevertSecondFactorAddition(this)
else -> Operation.Error
}
}

internal actual sealed class ActionCodeDataType<out T> {

actual abstract fun dataForResult(result: ActionCodeResult): T

actual object Email : ActionCodeDataType<String>() {
override fun dataForResult(result: ActionCodeResult): String = result.android.info!!.email
}
actual object PreviousEmail : ActionCodeDataType<String>() {
override fun dataForResult(result: ActionCodeResult): String = (result.android.info as ActionCodeEmailInfo).previousEmail
}
actual object MultiFactor : ActionCodeDataType<MultiFactorInfo?>() {
override fun dataForResult(result: ActionCodeResult): MultiFactorInfo? = (result.android.info as? ActionCodeMultiFactorInfo)?.multiFactorInfo?.let { MultiFactorInfo(it) }
}
}

actual class SignInMethodQueryResult(val android: com.google.firebase.auth.SignInMethodQueryResult) {
actual val signInMethods: List<String>
get() = android.signInMethods ?: emptyList()
}

actual class ActionCodeSettings private constructor(val android: com.google.firebase.auth.ActionCodeSettings) {

actual constructor(url: String,
androidPackageName: AndroidPackageName?,
dynamicLinkDomain: String?,
canHandleCodeInApp: Boolean,
iOSBundleId: String?
) : this(com.google.firebase.auth.ActionCodeSettings.newBuilder().apply {
this.url = url
androidPackageName?.let {
this.setAndroidPackageName(it.androidPackageName, it.installIfNotAvailable, it.minimumVersion)
}
this.dynamicLinkDomain = dynamicLinkDomain
this.handleCodeInApp = canHandleCodeInApp
this.iosBundleId = iosBundleId
}.build())

actual val canHandleCodeInApp: Boolean
get() = android.canHandleCodeInApp()
actual val androidPackageName: AndroidPackageName?
get() = android.androidPackageName?.let {
AndroidPackageName(it, android.androidInstallApp, android.androidMinimumVersion)
}
actual val iOSBundle: String?
get() = android.iosBundle
actual val url: String
get() = android.url
}

actual typealias FirebaseAuthException = com.google.firebase.auth.FirebaseAuthException
actual typealias FirebaseAuthActionCodeException = com.google.firebase.auth.FirebaseAuthActionCodeException
actual typealias FirebaseAuthEmailException = com.google.firebase.auth.FirebaseAuthEmailException
actual typealias FirebaseAuthInvalidCredentialsException = com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
actual typealias FirebaseAuthInvalidUserException = com.google.firebase.auth.FirebaseAuthInvalidUserException
actual typealias FirebaseAuthMultiFactorException = com.google.firebase.auth.FirebaseAuthMultiFactorException
actual typealias FirebaseAuthRecentLoginRequiredException = com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException
actual typealias FirebaseAuthUserCollisionException = com.google.firebase.auth.FirebaseAuthUserCollisionException
actual typealias FirebaseAuthWebException = com.google.firebase.auth.FirebaseAuthWebException

actual object EmailAuthProvider {
actual fun credentialWithEmail(
email: String,
password: String
): AuthCredential = AuthCredential(EmailAuthProvider.getCredential(email, password))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.gitlive.firebase.auth

import android.app.Activity
import com.google.firebase.FirebaseException
import com.google.firebase.auth.PhoneAuthProvider
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import java.util.concurrent.TimeUnit

actual open class AuthCredential(open val android: com.google.firebase.auth.AuthCredential) {
actual val providerId: String
get() = android.provider
}

actual class PhoneAuthCredential(override val android: com.google.firebase.auth.PhoneAuthCredential) : AuthCredential(android)

actual class OAuthCredential(override val android: com.google.firebase.auth.OAuthCredential) : AuthCredential(android)

actual object EmailAuthProvider {
actual fun credential(
email: String,
password: String
): AuthCredential = AuthCredential(com.google.firebase.auth.EmailAuthProvider.getCredential(email, password))
}

actual object FacebookAuthProvider {
actual fun credential(accessToken: String): AuthCredential = AuthCredential(com.google.firebase.auth.FacebookAuthProvider.getCredential(accessToken))
}

actual object GithubAuthProvider {
actual fun credential(token: String): AuthCredential = AuthCredential(com.google.firebase.auth.GithubAuthProvider.getCredential(token))
}

actual object GoogleAuthProvider {
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.Builder, private val auth: FirebaseAuth) {
actual constructor(provider: String, auth: FirebaseAuth) : this(com.google.firebase.auth.OAuthProvider.newBuilder(provider, auth.android), auth)

actual companion object {
actual fun credentials(type: OAuthCredentialsType): AuthCredential {
val credential = com.google.firebase.auth.OAuthProvider.newCredentialBuilder(type.providerId).apply {
when (type) {
is OAuthCredentialsType.AccessToken -> accessToken = type.accessToken
is OAuthCredentialsType.IdAndAccessToken -> {
accessToken = type.accessToken
setIdToken(type.idToken)
}
is OAuthCredentialsType.IdAndAccessTokenAndRawNonce -> {
accessToken = type.accessToken
setIdTokenWithRawNonce(type.idToken, type.rawNonce)
}
is OAuthCredentialsType.IdTokenAndRawNonce -> {
setIdTokenWithRawNonce(type.idToken, type.rawNonce)
}
}
}.build()
return (credential as? com.google.firebase.auth.OAuthCredential)?.let { OAuthCredential(it) } ?: AuthCredential(credential)
}
}

private var customParameters: Map<String, String> = emptyMap()

actual fun addScope(vararg scope: String) {
android.scopes = android.scopes + scope.asList()
}
actual fun setCustomParameters(parameters: Map<String, String>) {
customParameters = parameters
}

actual suspend fun signIn(signInProvider: SignInProvider): AuthResult = AuthResult(auth.android.startActivityForSignInWithProvider(signInProvider, android.apply { addCustomParameters(customParameters) }.build()).await())
}

actual typealias SignInProvider = Activity

actual class PhoneAuthProvider(val android: com.google.firebase.auth.PhoneAuthProvider) {


actual constructor(auth: FirebaseAuth) : this(com.google.firebase.auth.PhoneAuthProvider.getInstance(auth.android))

actual fun credential(verificationId: String, smsCode: String): PhoneAuthCredential = PhoneAuthCredential(com.google.firebase.auth.PhoneAuthProvider.getCredential(verificationId, smsCode))
actual suspend fun verifyPhoneNumber(phoneNumber: String, verificationProvider: PhoneVerificationProvider): AuthCredential = coroutineScope {
val response = CompletableDeferred<Result<AuthCredential>>()
val callback = object :
PhoneAuthProvider.OnVerificationStateChangedCallbacks() {

override fun onCodeSent(verificationId: String, forceResending: PhoneAuthProvider.ForceResendingToken) {
verificationProvider.codeSent { android.verifyPhoneNumber(phoneNumber, verificationProvider.timeout, verificationProvider.unit, verificationProvider.activity, this, forceResending) }
}

override fun onCodeAutoRetrievalTimeOut(verificationId: String) {
launch {
val code = verificationProvider.getVerificationCode()
try {
val credentials =
credential(verificationId, code)
response.complete(Result.success(credentials))
} catch (e: Exception) {
response.complete(Result.failure(e))
}
}
}

override fun onVerificationCompleted(credential: com.google.firebase.auth.PhoneAuthCredential) {
response.complete(Result.success(AuthCredential(credential)))
}

override fun onVerificationFailed(error: FirebaseException) {
response.complete(Result.failure(error))
}

}
android.verifyPhoneNumber(phoneNumber, verificationProvider.timeout, verificationProvider.unit, verificationProvider.activity, callback)

response.await().getOrThrow()
}
}

actual interface PhoneVerificationProvider {
val activity: Activity
val timeout: Long
val unit: TimeUnit
fun codeSent(triggerResend: (Unit) -> Unit)
suspend fun getVerificationCode(): String
}

actual object TwitterAuthProvider {
actual fun credential(token: String, secret: String): AuthCredential = AuthCredential(com.google.firebase.auth.TwitterAuthProvider.getCredential(token, secret))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.gitlive.firebase.auth

import kotlinx.coroutines.tasks.await

actual class MultiFactor(val android: com.google.firebase.auth.MultiFactor) {
actual val enrolledFactors: List<MultiFactorInfo>
get() = android.enrolledFactors.map { MultiFactorInfo(it) }
actual suspend fun enroll(multiFactorAssertion: MultiFactorAssertion, displayName: String?) = android.enroll(multiFactorAssertion.android, displayName).await().run { Unit }
actual suspend fun getSession(): MultiFactorSession = MultiFactorSession(android.session.await())
actual suspend fun unenroll(multiFactorInfo: MultiFactorInfo) = android.unenroll(multiFactorInfo.android).await().run { Unit }
actual suspend fun unenroll(factorUid: String) = android.unenroll(factorUid).await().run { Unit }
}

actual class MultiFactorInfo(val android: com.google.firebase.auth.MultiFactorInfo) {
actual val displayName: String?
get() = android.displayName
actual val enrollmentTime: Double
get() = android.enrollmentTimestamp.toDouble()
actual val factorId: String
get() = android.factorId
actual val uid: String
get() = android.uid
}

actual class MultiFactorAssertion(val android: com.google.firebase.auth.MultiFactorAssertion) {
actual val factorId: String
get() = android.factorId
}

actual class MultiFactorSession(val android: com.google.firebase.auth.MultiFactorSession)

actual class MultiFactorResolver(val android: com.google.firebase.auth.MultiFactorResolver) {
actual val auth: FirebaseAuth = FirebaseAuth(android.firebaseAuth)
actual val hints: List<MultiFactorInfo> = android.hints.map { MultiFactorInfo(it) }
actual val session: MultiFactorSession = MultiFactorSession(android.session)

actual suspend fun resolveSignIn(assertion: MultiFactorAssertion): AuthResult = AuthResult(android.resolveSignIn(assertion.android).await())
}
Loading

0 comments on commit 5844b72

Please sign in to comment.