Skip to content

Commit 5d4d7d9

Browse files
committed
Merge branch 'master' into dependabot/gradle/agp-8.9.1
2 parents ed72349 + d0d4710 commit 5d4d7d9

File tree

40 files changed

+293
-79
lines changed

40 files changed

+293
-79
lines changed

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Auth.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import io.github.jan.supabase.plugins.CustomSerializationPlugin
2525
import io.github.jan.supabase.plugins.MainPlugin
2626
import io.github.jan.supabase.plugins.SupabasePluginProvider
2727
import io.ktor.client.plugins.HttpRequestTimeoutException
28+
import kotlinx.coroutines.ensureActive
2829
import kotlinx.coroutines.flow.StateFlow
2930
import kotlinx.serialization.json.JsonObject
31+
import kotlin.coroutines.coroutineContext
3032

3133
/**
3234
* Plugin to interact with the Supabase Auth API
@@ -409,6 +411,17 @@ interface Auth : MainPlugin<AuthConfig>, CustomSerializationPlugin {
409411

410412
companion object : SupabasePluginProvider<AuthConfig, Auth> {
411413

414+
internal val HASH_PARAMETERS = listOf(
415+
"access_token",
416+
"refresh_token",
417+
"expires_in",
418+
"expires_at",
419+
"token_type",
420+
"type",
421+
"provider_refresh_token",
422+
"provider_token"
423+
)
424+
412425
override val key = "auth"
413426

414427
override val logger: SupabaseLogger = SupabaseClient.createLogger("Supabase-Auth")
@@ -434,6 +447,7 @@ val SupabaseClient.auth: Auth
434447
private suspend fun Auth.tryToGetUser(jwt: String) = try {
435448
retrieveUser(jwt)
436449
} catch (e: Exception) {
450+
coroutineContext.ensureActive()
437451
Auth.logger.e(e) { "Couldn't retrieve user using your custom jwt token. If you use the project secret ignore this message" }
438452
null
439453
}

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthConfig.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import io.github.jan.supabase.SupabaseSerializer
55
import io.github.jan.supabase.plugins.CustomSerializationConfig
66
import io.github.jan.supabase.plugins.MainConfig
77
import kotlinx.coroutines.CoroutineDispatcher
8-
import kotlinx.coroutines.Dispatchers
98
import kotlin.time.Duration
109
import kotlin.time.Duration.Companion.seconds
1110

@@ -40,19 +39,20 @@ open class AuthConfigDefaults : MainConfig() {
4039
var autoSaveToStorage: Boolean = true
4140

4241
/**
43-
* The session manager used to store/load the session. When null, the default [SettingsSessionManager] will be used
42+
* The session manager used to store/load the session. When null, the default [io.github.jan.supabase.auth.SettingsSessionManager] will be used
4443
*/
4544
var sessionManager: SessionManager? = null
4645

4746
/**
48-
* The cache used to store/load the code verifier for the [FlowType.PKCE] flow. When null, the default [SettingsCodeVerifierCache] will be used
47+
* The cache used to store/load the code verifier for the [FlowType.PKCE] flow. When null, the default [io.github.jan.supabase.auth.SettingsCodeVerifierCache] will be used
4948
*/
5049
var codeVerifierCache: CodeVerifierCache? = null
5150

5251
/**
5352
* The dispatcher used for all auth related network requests
5453
*/
55-
var coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default
54+
@Deprecated("SupabaseClientBuilder.coroutineDispatcher should be used instead")
55+
var coroutineDispatcher: CoroutineDispatcher? = null
5656

5757
/**
5858
* The type of login flow to use. Defaults to [FlowType.IMPLICIT]
@@ -120,6 +120,7 @@ enum class FlowType {
120120
/**
121121
* The deeplink used for the implicit and PKCE flow. Throws an [IllegalArgumentException], if either the scheme or host is not set
122122
*/
123+
@Suppress("unused")
123124
val AuthConfig.deepLink: String
124125
get() {
125126
val scheme = scheme ?: noDeeplinkError("scheme")
@@ -147,7 +148,7 @@ val AuthConfig.deepLinkOrNull: String?
147148
* @param enableLifecycleCallbacks Whether to stop auto-refresh on focus loss, and resume it on focus again. Currently only supported on Android.
148149
* @see AuthConfigDefaults
149150
*/
150-
@Suppress("LongParameterList")
151+
@Suppress("LongParameterList", "unused")
151152
fun AuthConfigDefaults.minimalSettings(
152153
alwaysAutoRefresh: Boolean = false,
153154
autoLoadFromStorage: Boolean = false,

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/AuthImpl.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ import io.ktor.http.HttpMethod
4040
import io.ktor.http.HttpStatusCode
4141
import kotlinx.coroutines.CoroutineScope
4242
import kotlinx.coroutines.Job
43+
import kotlinx.coroutines.SupervisorJob
4344
import kotlinx.coroutines.cancel
4445
import kotlinx.coroutines.delay
46+
import kotlinx.coroutines.ensureActive
4547
import kotlinx.coroutines.flow.MutableStateFlow
4648
import kotlinx.coroutines.flow.StateFlow
4749
import kotlinx.coroutines.flow.asStateFlow
@@ -56,6 +58,7 @@ import kotlinx.serialization.json.encodeToJsonElement
5658
import kotlinx.serialization.json.jsonObject
5759
import kotlinx.serialization.json.jsonPrimitive
5860
import kotlinx.serialization.json.put
61+
import kotlin.coroutines.coroutineContext
5962
import kotlin.time.Duration.Companion.seconds
6063

6164
private const val SESSION_REFRESH_THRESHOLD = 0.8
@@ -70,7 +73,7 @@ internal class AuthImpl(
7073

7174
private val _sessionStatus = MutableStateFlow<SessionStatus>(SessionStatus.Initializing)
7275
override val sessionStatus: StateFlow<SessionStatus> = _sessionStatus.asStateFlow()
73-
internal val authScope = CoroutineScope(config.coroutineDispatcher)
76+
internal val authScope = CoroutineScope((config.coroutineDispatcher ?: supabaseClient.coroutineDispatcher) + SupervisorJob())
7477
override val sessionManager = config.sessionManager ?: createDefaultSessionManager()
7578
override val codeVerifierCache = config.codeVerifierCache ?: createDefaultCodeVerifierCache()
7679

@@ -421,8 +424,9 @@ internal class AuthImpl(
421424
Auth.logger.d { "Session imported successfully." }
422425
return
423426
}
424-
if (session.expiresAt <= Clock.System.now()) {
425-
Auth.logger.d { "Session is expired. Handling expired session..." }
427+
val thresholdDate = session.expiresAt - session.expiresIn.seconds * (1 - SESSION_REFRESH_THRESHOLD)
428+
if (thresholdDate <= Clock.System.now()) {
429+
Auth.logger.d { "Session is under the threshold date. Refreshing session..." }
426430
tryImportingSession(
427431
{ handleExpiredSession(session, config.alwaysAutoRefresh) },
428432
{ importSession(session) }
@@ -466,6 +470,7 @@ internal class AuthImpl(
466470
clearSession()
467471
}
468472
} catch (e: Exception) {
473+
coroutineContext.ensureActive()
469474
Auth.logger.e(e) { "Couldn't reach Supabase. Either the address doesn't exist or the network might not be on. Retrying in ${config.retryDelay}..." }
470475
_sessionStatus.value = SessionStatus.RefreshFailure(RefreshFailureCause.NetworkError(e))
471476
delay(config.retryDelay)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.github.jan.supabase.auth
2+
3+
import io.github.jan.supabase.annotations.SupabaseInternal
4+
import io.github.jan.supabase.auth.status.SessionSource
5+
import io.github.jan.supabase.auth.user.UserSession
6+
import io.github.jan.supabase.buildUrl
7+
import io.github.jan.supabase.logging.d
8+
import io.ktor.client.request.HttpRequestBuilder
9+
import kotlinx.coroutines.launch
10+
11+
@SupabaseInternal
12+
fun Auth.parseFragmentAndImportSession(fragment: String, onSessionSuccess: (UserSession) -> Unit = {}) {
13+
Auth.logger.d { "Parsing deeplink fragment $fragment" }
14+
val session = try {
15+
parseSessionFromFragment(fragment)
16+
} catch(e: IllegalArgumentException) {
17+
Auth.logger.d(e) { "Received invalid session fragment. Ignoring." }
18+
return
19+
}
20+
this as AuthImpl
21+
authScope.launch {
22+
val user = retrieveUser(session.accessToken)
23+
val newSession = session.copy(user = user)
24+
onSessionSuccess(newSession)
25+
importSession(newSession, source = SessionSource.External)
26+
}
27+
}
28+
29+
@SupabaseInternal
30+
fun HttpRequestBuilder.redirectTo(url: String) {
31+
this.url.parameters["redirect_to"] = url
32+
}
33+
34+
internal fun consumeHashParameters(parameters: List<String>, url: String): String {
35+
return buildUrl(url) {
36+
fragment = fragment.split("&").filter {
37+
it.split("=").first() !in parameters
38+
}.joinToString("&")
39+
}
40+
}
41+
42+
internal fun consumeUrlParameter(parameters: List<String>, url: String): String {
43+
return buildUrl(url) {
44+
parameters.forEach { parameter ->
45+
this.parameters.remove(parameter)
46+
}
47+
}
48+
}

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/Utils.kt

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,7 @@
11
package io.github.jan.supabase.auth
22

33
import io.github.jan.supabase.SupabaseClient
4-
import io.github.jan.supabase.annotations.SupabaseInternal
5-
import io.github.jan.supabase.auth.status.SessionSource
64
import io.github.jan.supabase.auth.user.UserSession
7-
import io.github.jan.supabase.logging.d
8-
import io.ktor.client.request.HttpRequestBuilder
9-
import kotlinx.coroutines.launch
10-
11-
@SupabaseInternal
12-
fun Auth.parseFragmentAndImportSession(fragment: String, onSessionSuccess: (UserSession) -> Unit = {}) {
13-
Auth.logger.d { "Parsing deeplink fragment $fragment" }
14-
val session = parseSessionFromFragment(fragment)
15-
this as AuthImpl
16-
authScope.launch {
17-
val user = retrieveUser(session.accessToken)
18-
val newSession = session.copy(user = user)
19-
onSessionSuccess(newSession)
20-
importSession(newSession, source = SessionSource.External)
21-
}
22-
}
23-
24-
@SupabaseInternal
25-
fun HttpRequestBuilder.redirectTo(url: String) {
26-
this.url.parameters["redirect_to"] = url
27-
}
285

296
internal fun invalidArg(message: String): Nothing = throw IllegalArgumentException(message)
307

Auth/src/commonMain/kotlin/io/github/jan/supabase/auth/exception/AuthErrorCode.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ enum class AuthErrorCode(val value: String) {
8888
MfaTotpVerifyDisabled("mfa_totp_verify_not_enabled"),
8989
MfaWebAuthnEnrollDisabled("mfa_webauthn_enroll_not_enabled"),
9090
MfaWebAuthnVerifyDisabled("mfa_webauthn_verify_not_enabled"),
91-
MfaVerifiedFactorExists("mfa_verified_factor_exists");
91+
MfaVerifiedFactorExists("mfa_verified_factor_exists"),
92+
EmailAddressInvalid("email_address_invalid"),
93+
Web3ProviderDisabled("web3_provider_disabled"),
94+
Web3UnsupportedChain("web3_unsupported_chain");
9295

9396
companion object {
9497
/**
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import io.github.jan.supabase.auth.consumeHashParameters
2+
import io.github.jan.supabase.auth.consumeUrlParameter
3+
import io.github.jan.supabase.auth.redirectTo
4+
import io.ktor.client.request.HttpRequestBuilder
5+
import io.ktor.client.request.url
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
9+
class UrlUtilsTest {
10+
11+
@Test
12+
fun testConsumeHashParameters() {
13+
val url = "https://example.com/#test=123&state=abc&code=xyz"
14+
val newUrl = consumeHashParameters(listOf("test", "state"), url)
15+
val expectedUrl = "https://example.com/#code=xyz"
16+
assertEquals(expectedUrl, newUrl)
17+
}
18+
19+
@Test
20+
fun testConsumeUrlParameter() {
21+
val url = "https://example.com/?test=123&state=abc&code=xyz"
22+
val newUrl = consumeUrlParameter(listOf("test", "state"), url)
23+
val expectedUrl = "https://example.com/?code=xyz"
24+
assertEquals(expectedUrl, newUrl)
25+
}
26+
27+
@Test
28+
fun testRedirectTo() {
29+
val url = "https://example.com/"
30+
val redirectTo = "https://redirect.com"
31+
val newUrl = HttpRequestBuilder().apply {
32+
url(url)
33+
redirectTo(redirectTo)
34+
}.url.toString()
35+
val expectedUrl = "https://example.com/?redirect_to=https%3A%2F%2Fredirect.com"
36+
assertEquals(expectedUrl, newUrl)
37+
}
38+
39+
}

Auth/src/desktopMain/kotlin/io/github/jan/supabase/auth/server/HttpCallbackServer.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,18 @@ internal suspend fun createServer(
5959
}
6060
}
6161
}
62-
launch {
63-
suspendCancellableCoroutine {
64-
server.monitor.subscribe(ApplicationStopPreparing) { _ ->
62+
suspendCancellableCoroutine {
63+
server.monitor.subscribe(ApplicationStopPreparing) { _ ->
64+
if(!it.isCompleted) {
6565
it.resume(Unit)
6666
timeoutScope.cancel()
6767
}
68-
server.start()
69-
it.invokeOnCancellation {
70-
server.stop()
71-
timeoutScope.cancel()
72-
}
7368
}
74-
}.join()
69+
server.start()
70+
it.invokeOnCancellation { _ ->
71+
server.stop()
72+
}
73+
}
7574
}
7675
}
7776

Auth/src/jsMain/kotlin/io/github/jan/supabase/auth/setupPlatform.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ actual fun Auth.setupPlatform() {
2323
}
2424
Auth.logger.d { "Found hash: $afterHash" }
2525
parseFragmentAndImportSession(afterHash) {
26-
val newURL = window.location.href.split("#")[0];
26+
val newURL = consumeHashParameters(Auth.HASH_PARAMETERS, window.location.href)
2727
window.history.replaceState(null, window.document.title, newURL);
2828
}
2929
}
@@ -36,7 +36,7 @@ actual fun Auth.setupPlatform() {
3636
val session = exchangeCodeForSession(code)
3737
importSession(session, source = SessionSource.External)
3838
}
39-
val newURL = window.location.href.split("?")[0];
39+
val newURL = consumeUrlParameter(listOf("code"), window.location.href)
4040
window.history.replaceState(null, window.document.title, newURL);
4141
}
4242

Auth/src/settingsMain/kotlin/io/github/jan/supabase/auth/SettingsSessionManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import com.russhwolf.settings.Settings
55
import com.russhwolf.settings.coroutines.toSuspendSettings
66
import io.github.jan.supabase.auth.user.UserSession
77
import io.github.jan.supabase.logging.e
8+
import kotlinx.coroutines.ensureActive
89
import kotlinx.serialization.encodeToString
910
import kotlinx.serialization.json.Json
1011
import kotlinx.serialization.json.JsonBuilder
12+
import kotlin.coroutines.coroutineContext
1113

1214
private val settingsJson = Json {
1315
encodeDefaults = true
@@ -54,6 +56,7 @@ class SettingsSessionManager(
5456
return try {
5557
json.decodeFromString(session)
5658
} catch(e: Exception) {
59+
coroutineContext.ensureActive()
5760
Auth.logger.e(e) { "Failed to load session" }
5861
null
5962
}

0 commit comments

Comments
 (0)