Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to link user account with existing account #35

Merged
2 changes: 2 additions & 0 deletions kmpauth-firebase/api/kmpauth-firebase.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
public final class com/mmk/kmpauth/firebase/apple/AppleButtonUiContainerKt {
public static final fun AppleButtonUiContainer (Landroidx/compose/ui/Modifier;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
public static final fun AppleButtonUiContainer (Landroidx/compose/ui/Modifier;Ljava/util/List;Lkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public abstract interface class com/mmk/kmpauth/firebase/apple/AppleSignInRequestScope {
Expand Down Expand Up @@ -31,5 +32,6 @@ public final class com/mmk/kmpauth/firebase/google/GoogleButtonUiContainerFireba

public final class com/mmk/kmpauth/firebase/oauth/OAuthContainer_androidKt {
public static final fun OAuthContainer (Landroidx/compose/ui/Modifier;Ldev/gitlive/firebase/auth/OAuthProvider;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
public static final fun OAuthContainer (Landroidx/compose/ui/Modifier;Ldev/gitlive/firebase/auth/OAuthProvider;Lkotlin/jvm/functions/Function1;ZLkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import dev.gitlive.firebase.auth.OAuthProvider
*
* [onResult] callback will return [Result] with [FirebaseUser] type.
* @param requestScopes list of request scopes type of [AppleSignInRequestScope].
* @param linkAccount if true, it will link the account with the current user. Default value is false
* Example Usage:
* ```
* //Apple Sign-In with Custom Button and authentication with Firebase
Expand All @@ -30,6 +31,7 @@ public actual fun AppleButtonUiContainer(
modifier: Modifier,
requestScopes: List<AppleSignInRequestScope>,
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean,
content: @Composable UiContainerScope.() -> Unit,
) {
val oathProviderRequestScopes = requestScopes.map {
Expand All @@ -43,6 +45,22 @@ public actual fun AppleButtonUiContainer(
modifier = modifier,
oAuthProvider = oAuthProvider,
onResult = onResult,
linkAccount = linkAccount,
content = content
)
}

@Deprecated(
"Use AppleButtonUiContainer with the linkAccount parameter, which defaults to false.",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public actual fun AppleButtonUiContainer(
modifier: Modifier,
requestScopes: List<AppleSignInRequestScope>,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
AppleButtonUiContainer(modifier, requestScopes, onResult, false, content)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,41 @@ public actual fun OAuthContainer(
modifier: Modifier,
oAuthProvider: OAuthProvider,
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean,
content: @Composable UiContainerScope.() -> Unit,
) {
val activity = LocalContext.current.getActivity()
val uiContainerScope = remember {
object : UiContainerScope {
override fun onClick() {
onClickSignIn(activity, oAuthProvider, onResult)
onClickSignIn(activity, oAuthProvider, onResult, linkAccount)
}
}
}
Box(modifier = modifier) { uiContainerScope.content() }
}

@Deprecated(
"Use OAuthContainer with linkAccount parameter, which defaults to false",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@OptIn(KMPAuthInternalApi::class)
@Composable
public actual fun OAuthContainer(
modifier: Modifier,
oAuthProvider: OAuthProvider,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
OAuthContainer(modifier, oAuthProvider, onResult, false, content)
}

private fun onClickSignIn(
activity: ComponentActivity?,
oAuthProvider: OAuthProvider,
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean,
) {
val auth = Firebase.auth.android
val pendingAuthResult = auth.pendingAuthResult
Expand All @@ -47,9 +65,16 @@ private fun onClickSignIn(
} else {
if (activity == null)
onResult(Result.failure(IllegalStateException("Activity is null")))
else
auth.startActivityForSignInWithProvider(activity, oAuthProvider.android)
.resultAsFirebaseUser(onResult)
else {
val currentUser = auth.currentUser
val result = if (linkAccount && currentUser != null) {
currentUser.startActivityForLinkWithProvider(activity, oAuthProvider.android)
} else {
auth.startActivityForSignInWithProvider(activity, oAuthProvider.android)
}

result.resultAsFirebaseUser(onResult)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dev.gitlive.firebase.auth.FirebaseUser
*
* [onResult] callback will return [Result] with [FirebaseUser] type.
* @param requestScopes list of request scopes type of [AppleSignInRequestScope].
* @param linkAccount boolean value to link account with existing account. Default value is false
* Example Usage:
* ```
* //Apple Sign-In with Custom Button and authentication with Firebase
Expand All @@ -23,6 +24,23 @@ import dev.gitlive.firebase.auth.FirebaseUser
*
*/
@Composable
public expect fun AppleButtonUiContainer(
modifier: Modifier = Modifier,
requestScopes: List<AppleSignInRequestScope> = listOf(
AppleSignInRequestScope.FullName,
AppleSignInRequestScope.Email
),
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean = false,
content: @Composable UiContainerScope.() -> Unit,
)

@Deprecated(
"Use AppleButtonUiContainer with the linkAccount parameter, which defaults to false.",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public expect fun AppleButtonUiContainer(
modifier: Modifier = Modifier,
requestScopes: List<AppleSignInRequestScope> = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.mmk.kmpauth.core.UiContainerScope
import com.mmk.kmpauth.firebase.oauth.OAuthContainer
import com.mmk.kmpauth.google.GoogleUser
import dev.gitlive.firebase.auth.FirebaseUser
import dev.gitlive.firebase.auth.OAuthProvider

Expand All @@ -16,6 +15,7 @@ import dev.gitlive.firebase.auth.OAuthProvider
* [onResult] callback will return [Result] with [FirebaseUser] type.
* @param requestScopes Request Scopes that is provided in Github OAuth. By Default, user's email is requested.
* @param customParameters Custom Parameters that is provided in Github OAuth.
* @param linkAccount [Boolean] flag to link account with current user. Default value is false.
*
* Example Usage:
* ```
Expand All @@ -32,6 +32,7 @@ public fun GithubButtonUiContainer(
modifier: Modifier = Modifier,
requestScopes: List<String> = listOf("user:email"),
customParameters: Map<String, String> = emptyMap(),
linkAccount: Boolean = false,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
Expand All @@ -43,8 +44,33 @@ public fun GithubButtonUiContainer(
OAuthContainer(
modifier = modifier,
oAuthProvider = oAuthProvider,
linkAccount = linkAccount,
onResult = onResult,
content = content
)

}
}


@Deprecated(
"Use GithubButtonUiContainer with linkAccount parameter, which defaults to false",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public fun GithubButtonUiContainer(
modifier: Modifier = Modifier,
requestScopes: List<String> = listOf("user:email"),
customParameters: Map<String, String> = emptyMap(),
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
GithubButtonUiContainer(
modifier = modifier,
requestScopes = requestScopes,
linkAccount = false,
customParameters = customParameters,
onResult = onResult,
content = content
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import kotlinx.coroutines.launch
@Composable
public fun GoogleButtonUiContainerFirebase(
modifier: Modifier = Modifier,
linkAccount: Boolean = false,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
Expand All @@ -50,7 +51,13 @@ public fun GoogleButtonUiContainerFirebase(
val authCredential = GoogleAuthProvider.credential(idToken, accessToken)
coroutineScope.launch {
try {
val result = Firebase.auth.signInWithCredential(authCredential)
val auth = Firebase.auth
val currentUser = auth.currentUser
val result = if (linkAccount && currentUser != null) {
currentUser.linkWithCredential(authCredential)
} else {
auth.signInWithCredential(authCredential)
}
if (result.user == null) updatedOnResult(Result.failure(IllegalStateException("Firebase Null user")))
else updatedOnResult(Result.success(result.user))
} catch (e: Exception) {
Expand All @@ -61,4 +68,24 @@ public fun GoogleButtonUiContainerFirebase(

}, content = content)

}
}

@Deprecated(
"Use GoogleButtonUiContainerFirebase with linkAccount parameter, which defaults to false",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public fun GoogleButtonUiContainerFirebase(
modifier: Modifier = Modifier,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
GoogleButtonUiContainerFirebase(
modifier = modifier,
linkAccount = false,
onResult = onResult,
content = content
)
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.gitlive.firebase.auth.OAuthProvider
*
* [onResult] callback will return [Result] with [FirebaseUser] type.
* @param oAuthProvider [OAuthProvider] class object.
* @param linkAccount [Boolean] flag to link account with current user. Default value is false.
*
* Example Usage:
* ```
Expand All @@ -29,6 +30,20 @@ import dev.gitlive.firebase.auth.OAuthProvider
*
*/
@Composable
public expect fun OAuthContainer(
modifier: Modifier = Modifier,
oAuthProvider: OAuthProvider,
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean = false,
content: @Composable UiContainerScope.() -> Unit,
)

@Deprecated(
"Use OAuthContainer with linkAccount parameter, which defaults to false",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public expect fun OAuthContainer(
modifier: Modifier = Modifier,
oAuthProvider: OAuthProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.interop.UIKitView
import androidx.compose.ui.unit.dp
import cocoapods.FirebaseAuth.FIRAuth
import cocoapods.FirebaseAuth.FIRAuthDataResult
import cocoapods.FirebaseAuth.FIROAuthProvider
import com.mmk.kmpauth.core.UiContainerScope
import dev.gitlive.firebase.Firebase
Expand Down Expand Up @@ -61,6 +62,7 @@ private var currentNonce: String? = null
*
* [onResult] callback will return [Result] with [FirebaseUser] type.
* @param requestScopes list of request scopes type of [AppleSignInRequestScope].
* @param linkAccount if true, it will link the account with the current user. Default value is false
* Example Usage:
* ```
* //Apple Sign-In with Custom Button and authentication with Firebase
Expand All @@ -76,12 +78,13 @@ public actual fun AppleButtonUiContainer(
modifier: Modifier,
requestScopes: List<AppleSignInRequestScope>,
onResult: (Result<FirebaseUser?>) -> Unit,
linkAccount: Boolean,
content: @Composable UiContainerScope.() -> Unit,
) {
val updatedOnResultFunc by rememberUpdatedState(onResult)
val presentationContextProvider = PresentationContextProvider()
val asAuthorizationControllerDelegate =
ASAuthorizationControllerDelegate(updatedOnResultFunc)
ASAuthorizationControllerDelegate(linkAccount, updatedOnResultFunc)

val uiContainerScope = remember {
object : UiContainerScope {
Expand All @@ -99,6 +102,21 @@ public actual fun AppleButtonUiContainer(

}

@Deprecated(
"Use AppleButtonUiContainer with the linkAccount parameter, which defaults to false.",
ReplaceWith(""),
DeprecationLevel.WARNING
)
@Composable
public actual fun AppleButtonUiContainer(
modifier: Modifier,
requestScopes: List<AppleSignInRequestScope>,
onResult: (Result<FirebaseUser?>) -> Unit,
content: @Composable UiContainerScope.() -> Unit,
) {
AppleButtonUiContainer(modifier, requestScopes, onResult, false, content)
}

private fun signIn(
requestScopes: List<AppleSignInRequestScope>,
authorizationController: ASAuthorizationControllerDelegate,
Expand Down Expand Up @@ -167,7 +185,10 @@ private class PresentationContextProvider :
}
}

private class ASAuthorizationControllerDelegate(private val onResult: (Result<FirebaseUser?>) -> Unit) :
private class ASAuthorizationControllerDelegate(
private val linkAccount: Boolean,
private val onResult: (Result<FirebaseUser?>) -> Unit
) :
ASAuthorizationControllerDelegateProtocol, NSObject() {

@OptIn(BetaInteropApi::class, ExperimentalForeignApi::class)
Expand Down Expand Up @@ -199,16 +220,21 @@ private class ASAuthorizationControllerDelegate(private val onResult: (Result<Fi
idTokenString, currentNonce, appleIDCredential.fullName
)

FIRAuth.auth().signInWithCredential(credential) { firAuthDataResult, nsError ->
val currentUser = FIRAuth.auth().currentUser()

val handleResult: (FIRAuthDataResult?, NSError?) -> Unit = { firAuthDataResult, nsError ->
if (nsError != null || firAuthDataResult == null) {
onResult(Result.failure(IllegalStateException(nsError?.localizedFailureReason)))
return@signInWithCredential
} else {
onResult(Result.success(Firebase.auth.currentUser))
return@signInWithCredential
}
}

if (linkAccount && currentUser != null) {
currentUser.linkWithCredential(credential, handleResult)
} else {
FIRAuth.auth().signInWithCredential(credential, handleResult)
}
}

override fun authorizationController(
Expand Down
Loading