Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.jan.supabase.auth

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.SupabaseClientBuilder
import io.github.jan.supabase.annotations.SupabaseExperimental
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.auth.admin.AdminApi
Expand Down Expand Up @@ -496,8 +497,15 @@ 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)

override fun setup(builder: SupabaseClientBuilder, config: AuthConfig) {
if(config.checkSessionOnRequest) {
builder.networkInterceptors.add(SessionNetworkInterceptor)
}
}

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ open class AuthConfigDefaults : MainConfig() {
@SupabaseExperimental
var urlLauncher: UrlLauncher = UrlLauncher.DEFAULT

/**
* 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

}

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

import io.github.jan.supabase.OSInformation
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.exceptions.RestException
import io.github.jan.supabase.logging.e
import io.github.jan.supabase.network.NetworkInterceptor
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.HttpHeaders
import kotlin.time.Clock

object SessionNetworkInterceptor: NetworkInterceptor.Before {

override suspend fun call(builder: HttpRequestBuilder, supabase: SupabaseClient) {
val authHeader = builder.headers[HttpHeaders.Authorization]?.replace("Bearer ", "")
val currentSession = supabase.auth.currentSessionOrNull()
val sessionExistsAndExpired = authHeader == currentSession?.accessToken && currentSession != null && currentSession.expiresAt < Clock.System.now()
val autoRefreshEnabled = supabase.auth.config.alwaysAutoRefresh
if(sessionExistsAndExpired && autoRefreshEnabled) {
val autoRefreshRunning = supabase.auth.isAutoRefreshRunning
Auth.logger.e { """
Authenticated request attempted with expired access token. This should not happen. Please report this issue. Trying to refresh session before...
Auto refresh running: $autoRefreshRunning
OS: ${OSInformation.CURRENT}
Session: $currentSession
""".trimIndent() }

//TODO: Exception logic
try {
supabase.auth.refreshCurrentSession()
} catch(e: RestException) {
Auth.logger.e(e) { "Failed to refresh session" }
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.jan.supabase.auth.exception

//TODO: Add actual message and docs
class TokenExpiredException: Exception("The token has expired")

Check warning

Code scanning / detekt

Public classes, interfaces and objects require documentation. Warning

TokenExpiredException is missing required documentation.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import kotlinx.coroutines.CoroutineDispatcher
*/
interface SupabaseClient {

/**
* The configuration for the Supabase Client.
*/
val config: SupabaseClientConfig

/**
* The supabase url with either a http or https scheme.
*/
Expand Down Expand Up @@ -93,7 +98,7 @@ interface SupabaseClient {
}

internal class SupabaseClientImpl(
config: SupabaseClientConfig,
override val config: SupabaseClientConfig,
) : SupabaseClient {

override val accessToken: AccessTokenProvider? = config.accessToken
Expand All @@ -117,11 +122,7 @@ internal class SupabaseClientImpl(

@OptIn(SupabaseInternal::class)
override val httpClient = KtorSupabaseHttpClient(
supabaseKey,
config.networkConfig.httpConfigOverrides,
config.networkConfig.requestTimeout.inWholeMilliseconds,
config.networkConfig.httpEngine,
config.osInformation
this
)

override val pluginManager = PluginManager(config.plugins.toList().associate { (key, value) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.github.jan.supabase
import io.github.jan.supabase.annotations.SupabaseDsl
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.logging.LogLevel
import io.github.jan.supabase.network.NetworkInterceptor
import io.github.jan.supabase.plugins.PluginManager
import io.github.jan.supabase.plugins.SupabasePlugin
import io.github.jan.supabase.plugins.SupabasePluginProvider
Expand Down Expand Up @@ -95,6 +96,12 @@ class SupabaseClientBuilder @PublishedApi internal constructor(private val supab
*/
var osInformation: OSInformation? = OSInformation.CURRENT

/**
* A list of [NetworkInterceptor]s. Used for modifying requests or handling responses.
*/
@SupabaseInternal
var networkInterceptors = mutableListOf<NetworkInterceptor>()

private val httpConfigOverrides = mutableListOf<HttpConfigOverride>()
private val plugins = mutableMapOf<String, PluginProvider>()

Expand Down Expand Up @@ -124,7 +131,8 @@ class SupabaseClientBuilder @PublishedApi internal constructor(private val supab
useHTTPS = useHTTPS,
httpEngine = httpEngine,
httpConfigOverrides = httpConfigOverrides,
requestTimeout = requestTimeout
requestTimeout = requestTimeout,
interceptors = networkInterceptors
),
defaultSerializer = defaultSerializer,
coroutineDispatcher = coroutineDispatcher,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package io.github.jan.supabase

import io.github.jan.supabase.logging.LogLevel
import io.github.jan.supabase.network.NetworkInterceptor
import io.ktor.client.engine.HttpClientEngine
import kotlinx.coroutines.CoroutineDispatcher
import kotlin.time.Duration

internal data class SupabaseClientConfig(
data class SupabaseClientConfig(
val supabaseUrl: String,
val supabaseKey: String,
val defaultLogLevel: LogLevel,
Expand All @@ -17,9 +18,10 @@ internal data class SupabaseClientConfig(
val osInformation: OSInformation?
)

internal data class SupabaseNetworkConfig(
data class SupabaseNetworkConfig(
val useHTTPS: Boolean,
val httpEngine: HttpClientEngine?,
val httpConfigOverrides: List<HttpConfigOverride>,
val interceptors: List<NetworkInterceptor>,
val requestTimeout: Duration
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package io.github.jan.supabase.network

import io.github.jan.supabase.BuildConfig
import io.github.jan.supabase.OSInformation
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseInternal
import io.github.jan.supabase.exceptions.HttpRequestException
Expand All @@ -11,7 +10,6 @@ import io.github.jan.supabase.logging.e
import io.github.jan.supabase.supabaseJson
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.HttpTimeout
Expand Down Expand Up @@ -40,15 +38,22 @@ typealias HttpRequestOverride = HttpRequestBuilder.() -> Unit
*/
@OptIn(SupabaseInternal::class)
class KtorSupabaseHttpClient @SupabaseInternal constructor(
private val supabaseKey: String,
modifiers: List<HttpClientConfig<*>.() -> Unit> = listOf(),
private val requestTimeout: Long,
engine: HttpClientEngine? = null,
private val osInformation: OSInformation?
private val supabase: SupabaseClient
): SupabaseHttpClient() {

private val supabaseKey = supabase.supabaseKey
private val osInformation = supabase.config.osInformation

private val networkConfig = supabase.config.networkConfig
private val requestTimeout = networkConfig.requestTimeout
private val engine = networkConfig.httpEngine
private val modifiers = networkConfig.httpConfigOverrides

private val beforeInterceptors = networkConfig.interceptors.filterIsInstance<NetworkInterceptor.Before>().toTypedArray()
private val afterInterceptors = networkConfig.interceptors.filterIsInstance<NetworkInterceptor.After>().toTypedArray()

init {
SupabaseClient.LOGGER.d { "Creating KtorSupabaseHttpClient with request timeout $requestTimeout ms, HttpClientEngine: $engine" }
SupabaseClient.LOGGER.d { "Creating KtorSupabaseHttpClient with request timeout $requestTimeout, HttpClientEngine: $engine" }
}

@SupabaseInternal
Expand All @@ -63,11 +68,11 @@ class KtorSupabaseHttpClient @SupabaseInternal constructor(
}
val endPoint = request.url.encodedPath
SupabaseClient.LOGGER.d { "Starting ${request.method.value} request to endpoint $endPoint" }

callBeforeInterceptors(request)
val response = try {
httpClient.request(url, builder)
} catch(e: HttpRequestTimeoutException) {
SupabaseClient.LOGGER.e { "${request.method.value} request to endpoint $endPoint timed out after $requestTimeout ms" }
SupabaseClient.LOGGER.e { "${request.method.value} request to endpoint $endPoint timed out after $requestTimeout" }
throw e
} catch(e: CancellationException) {
SupabaseClient.LOGGER.e { "${request.method.value} request to endpoint $endPoint was cancelled"}
Expand All @@ -76,6 +81,7 @@ class KtorSupabaseHttpClient @SupabaseInternal constructor(
SupabaseClient.LOGGER.e(e) { "${request.method.value} request to endpoint $endPoint failed with exception ${e.message}" }
throw HttpRequestException(e.message ?: "", request)
}
callAfterInterceptors(response)
val responseTime = (response.responseTime.timestamp - response.requestTime.timestamp).milliseconds
SupabaseClient.LOGGER.d { "${request.method.value} request to endpoint $endPoint successfully finished in $responseTime" }
return response
Expand All @@ -92,7 +98,7 @@ class KtorSupabaseHttpClient @SupabaseInternal constructor(
val response = try {
httpClient.prepareRequest(url, builder)
} catch(e: HttpRequestTimeoutException) {
SupabaseClient.LOGGER.e { "Request timed out after $requestTimeout ms on url $url" }
SupabaseClient.LOGGER.e { "Request timed out after $requestTimeout on url $url" }
throw e
} catch(e: CancellationException) {
SupabaseClient.LOGGER.e { "Request was cancelled on url $url" }
Expand All @@ -106,6 +112,18 @@ class KtorSupabaseHttpClient @SupabaseInternal constructor(

fun close() = httpClient.close()

private fun callBeforeInterceptors(requestBuilder: HttpRequestBuilder) {
beforeInterceptors.forEach {
it.call(requestBuilder, supabase)
}
}

private fun callAfterInterceptors(response: HttpResponse) {
afterInterceptors.forEach {
it.call(response, supabase)
}
}

private fun HttpClientConfig<*>.applyDefaultConfiguration(modifiers: List<HttpClientConfig<*>.() -> Unit>) {
install(DefaultRequest) {
headers {
Expand All @@ -124,7 +142,7 @@ class KtorSupabaseHttpClient @SupabaseInternal constructor(
json(supabaseJson)
}
install(HttpTimeout) {
requestTimeoutMillis = requestTimeout
requestTimeoutMillis = requestTimeout.inWholeMilliseconds
}
modifiers.forEach { it.invoke(this) }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.github.jan.supabase.network

import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.annotations.SupabaseInternal
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.statement.HttpResponse

@SupabaseInternal
sealed interface NetworkInterceptor {

fun interface Before: NetworkInterceptor {

suspend fun call(builder: HttpRequestBuilder, supabase: SupabaseClient)

}

fun interface After: NetworkInterceptor {

suspend fun call(response: HttpResponse, supabase: SupabaseClient)

}

}
Loading