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 support for linking identities #368

Merged
merged 13 commits into from
Dec 3, 2023
10 changes: 9 additions & 1 deletion GoTrue/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ kotlin {
// api(libs.cache4k)
}
}
val nonJvmMain by creating {
dependsOn(commonMain)
}
val nonLinuxMain by creating {
dependsOn(commonMain)
dependencies {
Expand All @@ -89,14 +92,19 @@ kotlin {
}
val mingwX64Main by getting {
dependsOn(nonLinuxMain)
dependsOn(nonJvmMain)
}
val appleMain by getting {
dependsOn(nonLinuxMain)
dependsOn(nonJvmMain)
}
val jsMain by getting {
dependsOn(nonLinuxMain)
dependsOn(nonJvmMain)
}
val linuxMain by getting {
dependsOn(nonJvmMain)
}
val linuxMain by getting
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,12 @@ import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.gotrue.ExternalAuthAction.CUSTOM_TABS
import io.github.jan.supabase.gotrue.ExternalAuthAction.EXTERNAL_BROWSER
import io.github.jan.supabase.gotrue.providers.ExternalAuthConfigDefaults
import io.github.jan.supabase.gotrue.providers.OAuthProvider
import io.github.jan.supabase.gotrue.user.UserSession
import kotlinx.coroutines.launch


internal fun Auth.openOAuth(provider: OAuthProvider, redirectTo: String, config: ExternalAuthConfigDefaults) {
this as AuthImpl
openUrl(
uri = Uri.parse(oAuthUrl(provider, redirectTo) {
scopes.addAll(config.scopes)
queryParams.putAll(config.queryParams)
}),
action = this.config.defaultExternalAuthAction
)
}

internal fun openUrl(uri: Uri, action: ExternalAuthAction) {
when(action) {
EXTERNAL_BROWSER -> {
Expand All @@ -46,7 +32,7 @@ internal fun openUrl(uri: Uri, action: ExternalAuthAction) {
* @param intent The intent from the activity
* @param onSessionSuccess The callback when the session was successfully imported
*/
@OptIn(SupabaseExperimental::class)
@OptIn(SupabaseInternal::class)
fun SupabaseClient.handleDeeplinks(intent: Intent, onSessionSuccess: (UserSession) -> Unit = {}) {
val data = intent.data ?: return
val scheme = data.scheme ?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package io.github.jan.supabase.gotrue
import io.github.jan.supabase.annotations.SupabaseInternal

@SupabaseInternal
actual fun Auth.generateRedirectUrl(fallbackUrl: String?): String? {
if(fallbackUrl != null) return fallbackUrl
val scheme = config.scheme ?: return null
val host = config.host ?: return null
this as AuthImpl
return "${scheme}://${host}"
}
actual fun Auth.defaultRedirectUrl(): String? = config.deepLinkOrNull

internal val AuthConfig.deepLink: String
get() {
val scheme = scheme ?: noDeeplinkError("scheme")
val host = host ?: noDeeplinkError("host")
return "${scheme}://${host}"
}

internal val AuthConfig.deepLinkOrNull: String?
get() {
val scheme = scheme ?: return null
val host = host ?: return null
return "${scheme}://${host}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@file:Suppress("RedundantSuspendModifier")
package io.github.jan.supabase.gotrue

import android.net.Uri
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.gotrue.user.UserSession

internal actual suspend fun SupabaseClient.openExternalUrl(url: String) {
openUrl(Uri.parse(url), auth.config.defaultExternalAuthAction)
}

internal actual suspend fun Auth.startExternalAuth(
redirectUrl: String?,
getUrl: suspend (redirectTo: String?) -> String,
onSessionSuccess: suspend (UserSession) -> Unit
) {
supabaseClient.openExternalUrl(getUrl(redirectUrl))
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package io.github.jan.supabase.gotrue
import io.github.jan.supabase.annotations.SupabaseInternal

@SupabaseInternal
actual fun Auth.generateRedirectUrl(fallbackUrl: String?): String? {
if(fallbackUrl != null) return fallbackUrl
val scheme = config.scheme ?: return null
val host = config.host ?: return null
this as AuthImpl
return "${scheme}://${host}"
}
actual fun Auth.defaultRedirectUrl(): String? = config.deepLinkOrNull

internal val AuthConfig.deepLink: String
get() {
val scheme = scheme ?: noDeeplinkError("scheme")
val host = host ?: noDeeplinkError("host")
return "${scheme}://${host}"
}

internal val AuthConfig.deepLinkOrNull: String?
get() {
val scheme = scheme ?: return null
val host = host ?: return null
return "${scheme}://${host}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.jan.supabase.gotrue

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.gotrue.providers.openUrl
import platform.Foundation.NSURL

internal actual suspend fun SupabaseClient.openExternalUrl(url: String) {
openUrl(NSURL(string = url))
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,5 @@
package io.github.jan.supabase.gotrue.providers

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.gotrue.auth
import io.github.jan.supabase.gotrue.deepLink
import io.github.jan.supabase.gotrue.user.UserSession
import platform.Foundation.NSURL

/**
* Represents an OAuth provider.
*/
actual abstract class OAuthProvider : AuthProvider<ExternalAuthConfig, Unit> {

/**
* The name of the provider.
*/
actual abstract val name: String

actual override suspend fun login(
supabaseClient: SupabaseClient,
onSuccess: suspend (UserSession) -> Unit,
redirectUrl: String?,
config: (ExternalAuthConfig.() -> Unit)?
) {
val externalConfig = ExternalAuthConfig().apply(config ?: {})
openOAuth(redirectUrl, supabaseClient, externalConfig)
}

actual override suspend fun signUp(
supabaseClient: SupabaseClient,
onSuccess: suspend (UserSession) -> Unit,
redirectUrl: String?,
config: (ExternalAuthConfig.() -> Unit)?
) = login(supabaseClient, onSuccess, redirectUrl, config = config)

private fun openOAuth(
redirectUrl: String? = null,
supabaseClient: SupabaseClient,
externalConfig: ExternalAuthConfig
) {
val gotrue = supabaseClient.auth
val url = NSURL(string = supabaseClient.auth.oAuthUrl(this, redirectUrl ?: gotrue.config.deepLink) {
scopes.addAll(externalConfig.scopes)
queryParams.putAll(externalConfig.queryParams)
})
openUrl(url)
}

actual companion object

}

internal expect fun openUrl(url: NSURL)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.jan.supabase.gotrue

import co.touchlab.kermit.Logger
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.exceptions.HttpRequestException
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.gotrue.admin.AdminApi
Expand Down Expand Up @@ -95,7 +96,7 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
*/
suspend fun <C, R, Provider : AuthProvider<C, R>> signUpWith(
provider: Provider,
redirectUrl: String? = null,
redirectUrl: String? = defaultRedirectUrl(),
config: (C.() -> Unit)? = null
): R?

Expand All @@ -121,16 +122,40 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
*/
suspend fun <C, R, Provider : AuthProvider<C, R>> signInWith(
provider: Provider,
redirectUrl: String? = null,
redirectUrl: String? = defaultRedirectUrl(),
config: (C.() -> Unit)? = null
)

/**
* Retrieves the sso url for the specified [type]
* Links an OAuth Identity to an existing user.
*
* This methods works similar to signing in with OAuth providers. Refer to the [documentation](https://supabase.com/docs/reference/kotlin/initializing) to learn how to handle OAuth and OTP links.
* @param provider The OAuth provider
* @param redirectUrl The redirect url to use. If you don't specify this, the platform specific will be used, like deeplinks on android.
* @param config Extra configuration
*/
@SupabaseExperimental
suspend fun linkIdentity(
provider: OAuthProvider,
redirectUrl: String? = defaultRedirectUrl(),
config: ExternalAuthConfigDefaults.() -> Unit = {}
)

/**
* Unlinks an OAuth Identity from an existing user.
* @param identityId The id of the OAuth identity
*/
@SupabaseExperimental
suspend fun unlinkIdentity(
identityId: String
)

/**
* Retrieves the sso url for the given [config]
* @param redirectUrl The redirect url to use
* @param config The configuration to use
*/
suspend fun retrieveSSOUrl(redirectUrl: String? = null, config: SSO.Config.() -> Unit): SSO.Result
suspend fun retrieveSSOUrl(redirectUrl: String? = defaultRedirectUrl(), config: SSO.Config.() -> Unit): SSO.Result

/**
* Modifies the current user
Expand All @@ -142,7 +167,7 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
*/
suspend fun modifyUser(
updateCurrentUser: Boolean = true,
redirectUrl: String? = null,
redirectUrl: String? = defaultRedirectUrl(),
config: UserUpdateBuilder.() -> Unit
): UserInfo

Expand Down Expand Up @@ -176,7 +201,7 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
* @throws HttpRequestTimeoutException if the request timed out
* @throws HttpRequestException on network related issues
*/
suspend fun resetPasswordForEmail(email: String, redirectUrl: String? = null, captchaToken: String? = null)
suspend fun resetPasswordForEmail(email: String, redirectUrl: String? = defaultRedirectUrl(), captchaToken: String? = null)

/**
* Sends a nonce to the user's email (preferred) or phone
Expand Down Expand Up @@ -294,7 +319,7 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
* @param provider The provider to use
* @param redirectUrl The redirect url to use
*/
fun oAuthUrl(provider: OAuthProvider, redirectUrl: String? = null, additionalConfig: ExternalAuthConfigDefaults.() -> Unit = {}): String
fun oAuthUrl(provider: OAuthProvider, redirectUrl: String? = defaultRedirectUrl(), url: String = "authorize", additionalConfig: ExternalAuthConfigDefaults.() -> Unit = {}): String

/**
* Stops auto-refreshing the current session
Expand All @@ -319,6 +344,11 @@ sealed interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
*/
fun currentUserOrNull() = currentSessionOrNull()?.user

/**
* Returns the connected identities to the current user or null
*/
fun currentIdentitiesOrNull() = currentUserOrNull()?.identities

companion object : SupabasePluginProvider<AuthConfig, Auth> {

override val key = "auth"
Expand Down
Loading
Loading