Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package io.github.jan.supabase.auth

import androidx.browser.customtabs.CustomTabsIntent
import io.github.jan.supabase.auth.providers.ExternalAuthConfig
import io.github.jan.supabase.plugins.CustomSerializationConfig

/**
* The configuration for [Auth]
*/
actual class AuthConfig : CustomSerializationConfig, AuthConfigDefaults() {
actual class AuthConfig : AuthConfigDefaults() {

/**
* The action to use for the OAuth flow. Can be overriden per-request in the [ExternalAuthConfig]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import io.github.jan.supabase.plugins.MainPlugin
/**
* Returns the access token used for requests. The token is resolved in the following order:
* 1. [jwtToken] if not null
* 2. [SupabaseClient.resolveAccessToken] if not null
* 2. [SupabaseClient.accessToken] if not null
* 3. [Auth.currentAccessTokenOrNull] if the Auth plugin is installed
* 4. [SupabaseClient.supabaseKey] if [keyAsFallback] is true
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
const val API_VERSION = 1

override fun createConfig(init: AuthConfig.() -> Unit) = AuthConfig().apply(init)

override fun create(supabaseClient: SupabaseClient, config: AuthConfig): Auth = AuthImpl(supabaseClient, config)

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.github.jan.supabase.auth

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.SupabaseClientBuilder
import io.github.jan.supabase.SupabaseSerializer
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.auth.user.UserSession
import io.github.jan.supabase.plugins.CustomSerializationConfig
import io.github.jan.supabase.plugins.MainConfig
import kotlinx.coroutines.CoroutineDispatcher
Expand All @@ -13,12 +15,12 @@ import kotlin.time.Duration.Companion.seconds
/**
* The configuration for [Auth]
*/
expect class AuthConfig() : CustomSerializationConfig, AuthConfigDefaults
expect class AuthConfig() : AuthConfigDefaults

/**
* The default values for the [AuthConfig]
*/
open class AuthConfigDefaults : MainConfig() {
open class AuthConfigDefaults : MainConfig(), AuthDependentPluginConfig, CustomSerializationConfig {

/**
* The duration after which [Auth] should retry refreshing a session, when it failed due to network issues
Expand Down Expand Up @@ -64,7 +66,7 @@ open class AuthConfigDefaults : MainConfig() {
/**
* A serializer used for serializing/deserializing objects e.g. in [Auth.signInWith]. Defaults to [SupabaseClientBuilder.defaultSerializer], when null.
*/
var serializer: SupabaseSerializer? = null
override var serializer: SupabaseSerializer? = null

/**
* The deeplink scheme used for the implicit and PKCE flow. When null, deeplinks won't be used as redirect urls
Expand Down Expand Up @@ -110,6 +112,20 @@ open class AuthConfigDefaults : MainConfig() {
@SupabaseInternal
var autoSetupPlatform: Boolean = true

/**
* Whether to check if the current session is expired on an authenticated request and possibly try to refresh it.
*
* **Note: This option is experimental and is a fail-safe for when the auto refresh fails. This option may be removed without notice.**
*/
@SupabaseExperimental
var checkSessionOnRequest: Boolean = true

/**
* Whether to require a valid [UserSession] in the [Auth] plugin to make any request with this plugin. The [SupabaseClient.supabaseKey] cannot be used as fallback.
*/
@SupabaseExperimental
override var requireValidSession: Boolean = false

}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.jan.supabase.auth

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.auth.user.UserSession

/**
* Configuration for plugins depending on the Auth plugin
*/
interface AuthDependentPluginConfig {

/**
* Whether to require a valid [UserSession] in the [Auth] plugin to make any request with this plugin. The [SupabaseClient.supabaseKey] cannot be used as fallback.
*/
@SupabaseExperimental
var requireValidSession: Boolean

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ internal class AuthImpl(
override val sessionManager = config.sessionManager ?: createDefaultSessionManager()
override val codeVerifierCache = config.codeVerifierCache ?: createDefaultCodeVerifierCache()

internal val publicApi = supabaseClient.authenticatedSupabaseApi(this, requireSession = false)
@OptIn(SupabaseInternal::class)
internal val api = supabaseClient.authenticatedSupabaseApi(this)
override val admin: AdminApi = AdminApiImpl(this)
internal val userApi = if(config.requireValidSession) supabaseClient.authenticatedSupabaseApi(this) else publicApi
override val admin: AdminApi = AdminApiImpl(publicApi)
override val mfa: MfaApi = MfaApiImpl(this)
var sessionJob: Job? = null
override val isAutoRefreshRunning: Boolean
Expand Down Expand Up @@ -148,7 +149,7 @@ internal class AuthImpl(
}, redirectUrl, config)

override suspend fun signInAnonymously(data: JsonObject?, captchaToken: String?) {
val response = api.postJson("signup", buildJsonObject {
val response = publicApi.postJson("signup", buildJsonObject {
data?.let { put("data", it) }
captchaToken?.let(::putCaptchaToken)
})
Expand All @@ -172,7 +173,7 @@ internal class AuthImpl(
val automaticallyOpen = ExternalAuthConfigDefaults().apply(config).automaticallyOpenUrl
val fetchUrl: suspend (String?) -> String = { redirectTo: String? ->
val url = getOAuthUrl(provider, redirectTo, "user/identities/authorize", config)
val response = api.rawRequest(url) {
val response = userApi.rawRequest(url) {
method = HttpMethod.Get
parameter("skip_http_redirect", true)
}
Expand All @@ -199,12 +200,12 @@ internal class AuthImpl(
config: (IDToken.Config) -> Unit
) {
val body = IDToken.Config(idToken = idToken, provider = provider, linkIdentity = true).apply(config)
val result = api.postJson("token?grant_type=id_token", body)
val result = userApi.postJson("token?grant_type=id_token", body)
importSession(result.safeBody(), source = SessionSource.UserIdentitiesChanged(result.safeBody()))
}

override suspend fun unlinkIdentity(identityId: String, updateLocalUser: Boolean) {
api.delete("user/identities/$identityId")
userApi.delete("user/identities/$identityId")
if (updateLocalUser) {
val session = currentSessionOrNull() ?: return
val newUser = session.user?.copy(identities = session.user.identities?.filter { it.identityId != identityId })
Expand All @@ -228,7 +229,7 @@ internal class AuthImpl(
}

val codeChallenge: String? = preparePKCEIfEnabled()
return api.postJson("sso", buildJsonObject {
return publicApi.postJson("sso", buildJsonObject {
redirectUrl?.let { put("redirect_to", it) }
createdConfig.captchaToken?.let(::putCaptchaToken)
codeChallenge?.let(::putCodeChallenge)
Expand All @@ -238,7 +239,8 @@ internal class AuthImpl(
createdConfig.providerId?.let {
put("provider_id", it)
}
}).body()
})
.body()
}

override suspend fun updateUser(
Expand All @@ -252,7 +254,7 @@ internal class AuthImpl(
putJsonObject(supabaseJson.encodeToJsonElement(updateBuilder).jsonObject)
codeChallenge?.let(::putCodeChallenge)
}.toString()
val response = api.putJson("user", body) {
val response = userApi.putJson("user", body) {
redirectUrl?.let { url.parameters.append("redirect_to", it) }
}
val userInfo = response.safeBody<UserInfo>()
Expand All @@ -268,7 +270,7 @@ internal class AuthImpl(
}

private suspend fun resend(type: String, body: JsonObjectBuilder.() -> Unit) {
api.postJson("resend", buildJsonObject {
userApi.postJson("resend", buildJsonObject {
put("type", type)
putJsonObject(buildJsonObject(body))
})
Expand Down Expand Up @@ -303,19 +305,19 @@ internal class AuthImpl(
captchaToken?.let(::putCaptchaToken)
codeChallenge?.let(::putCodeChallenge)
}.toString()
api.postJson("recover", body) {
publicApi.postJson("recover", body) {
redirectUrl?.let { url.encodedParameters.append("redirect_to", it) }
}
}

override suspend fun reauthenticate() {
api.get("reauthenticate")
userApi.get("reauthenticate")
}

override suspend fun signOut(scope: SignOutScope) {
if (currentSessionOrNull() != null) {
try {
api.post("logout") {
userApi.post("logout") {
parameter("scope", scope.name.lowercase())
}
} catch(e: RestException) {
Expand Down Expand Up @@ -345,7 +347,7 @@ internal class AuthImpl(
captchaToken?.let(::putCaptchaToken)
additionalData()
}
val response = api.postJson("verify", body)
val response = publicApi.postJson("verify", body)
val session = response.body<UserSession>()
importSession(session, source = SessionSource.SignIn(OTP))
}
Expand Down Expand Up @@ -377,7 +379,7 @@ internal class AuthImpl(
}

override suspend fun retrieveUser(jwt: String): UserInfo {
val response = api.get("user") {
val response = userApi.get("user") {
headers["Authorization"] = "Bearer $jwt"
}
val body = response.bodyAsText()
Expand All @@ -400,7 +402,7 @@ internal class AuthImpl(
require(codeVerifier != null) {
"No code verifier stored. Make sure to use `getOAuthUrl` for the OAuth Url to prepare the PKCE flow."
}
val session = api.postJson("token?grant_type=pkce", buildJsonObject {
val session = publicApi.postJson("token?grant_type=pkce", buildJsonObject {
put("auth_code", code)
put("code_verifier", codeVerifier)
}) {
Expand All @@ -420,7 +422,7 @@ internal class AuthImpl(
val body = buildJsonObject {
put("refresh_token", refreshToken)
}
val response = api.postJson("token?grant_type=refresh_token", body) {
val response = publicApi.postJson("token?grant_type=refresh_token", body) {
headers.remove("Authorization")
}
return response.safeBody("Auth#refreshSession")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.jan.supabase.auth

import io.github.jan.supabase.StringMasking
import io.github.jan.supabase.auth.user.UserSession

internal fun StringMasking.maskSession(value: UserSession): UserSession {
return value.copy(
accessToken = maskString(value.accessToken, showLength = true),
refreshToken = maskString(value.refreshToken, showLength = true),
providerRefreshToken = value.providerRefreshToken?.let { maskString(it, showLength = true) },
providerToken = value.providerToken?.let { maskString(it, showLength = true) }
)
}
Loading
Loading