diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrue.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrue.kt index 7e68a71f0..a844e50d5 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrue.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrue.kt @@ -257,11 +257,13 @@ sealed interface GoTrue : MainPlugin, CustomSerializationPlugin { /** * Logs out the current user, which means [sessionStatus] will be [SessionStatus.NotAuthenticated] and the access token will be revoked + * @param scope The scope of the logout. * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues + * @see LogoutScope */ - suspend fun logout() + suspend fun logout(scope: LogoutScope = LogoutScope.LOCAL) /** * Imports a user session and starts auto-refreshing if [autoRefresh] is true diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueImpl.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueImpl.kt index 7c7993781..c2802fd89 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueImpl.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueImpl.kt @@ -25,6 +25,7 @@ import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.safeBody import io.github.jan.supabase.supabaseJson import io.ktor.client.call.body +import io.ktor.client.request.parameter import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpStatusCode @@ -237,11 +238,17 @@ internal class GoTrueImpl( api.get("reauthenticate") } - override suspend fun logout() { - sessionManager.deleteSession() - sessionJob?.cancel() - _sessionStatus.value = SessionStatus.NotAuthenticated - sessionJob = null + override suspend fun logout(scope: LogoutScope) { + api.post("logout") { + parameter("scope", scope.name.lowercase()) + } + if(scope != LogoutScope.OTHERS) { + codeVerifierCache.deleteCodeVerifier() + sessionManager.deleteSession() + sessionJob?.cancel() + _sessionStatus.value = SessionStatus.NotAuthenticated + sessionJob = null + } } private suspend fun verify( diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/LogoutScope.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/LogoutScope.kt new file mode 100644 index 000000000..5970b9ea3 --- /dev/null +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/LogoutScope.kt @@ -0,0 +1,14 @@ +package io.github.jan.supabase.gotrue + +/** + * Represents the scope of a logout action. + * + * The logout scope determines the scope of the logout action being performed. + * + * @property GLOBAL Logout action applies to all sessions across the entire system. + * @property LOCAL Logout action applies only to the current session. + * @property OTHERS Logout action applies to other sessions, excluding the current session. + */ +enum class LogoutScope { + GLOBAL, LOCAL, OTHERS +} \ No newline at end of file diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt index 60495140f..a83e18d41 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt @@ -3,12 +3,14 @@ package io.github.jan.supabase.gotrue.admin import io.github.jan.supabase.annotations.SupabaseInternal import io.github.jan.supabase.gotrue.GoTrue import io.github.jan.supabase.gotrue.GoTrueImpl +import io.github.jan.supabase.gotrue.LogoutScope import io.github.jan.supabase.gotrue.user.UserInfo import io.github.jan.supabase.gotrue.user.UserMfaFactor import io.github.jan.supabase.putJsonObject import io.github.jan.supabase.safeBody import io.github.jan.supabase.supabaseJson import io.ktor.client.call.body +import io.ktor.client.request.parameter import io.ktor.http.HttpHeaders import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -37,8 +39,10 @@ sealed interface AdminApi { /** * Removes a user session + * @param jwt the jwt of the session + * @param scope the scope of the logout action */ - suspend fun logout(jwt: String) + suspend fun logout(jwt: String, scope: LogoutScope = LogoutScope.LOCAL) /** * Retrieves all users @@ -92,8 +96,9 @@ internal class AdminApiImpl(val gotrue: GoTrue) : AdminApi { val api = (gotrue as GoTrueImpl).api - override suspend fun logout(jwt: String) { + override suspend fun logout(jwt: String, scope: LogoutScope) { api.post("logout") { + parameter("scope", scope.name.lowercase()) headers[HttpHeaders.Authorization] = "Bearer $jwt" } } diff --git a/GoTrue/src/commonTest/kotlin/GoTrueMock.kt b/GoTrue/src/commonTest/kotlin/GoTrueMock.kt index df5f94882..6d48420d0 100644 --- a/GoTrue/src/commonTest/kotlin/GoTrueMock.kt +++ b/GoTrue/src/commonTest/kotlin/GoTrueMock.kt @@ -15,7 +15,6 @@ import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.http.headersOf import kotlinx.datetime.Clock -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject @@ -40,10 +39,17 @@ class GoTrueMock { urlWithoutQuery.endsWith("verify") -> handleVerify(request) urlWithoutQuery.endsWith("otp") -> handleOtp(request) urlWithoutQuery.endsWith("recover") -> handleRecovery(request) + urlWithoutQuery.endsWith("logout") -> handleLogout(request) else -> null } } + private suspend fun MockRequestHandleScope.handleLogout(request: HttpRequestData): HttpResponseData { + if(request.method != HttpMethod.Post) return respondBadRequest("Invalid method") + if(!request.headers.contains("Authorization")) return respondBadRequest("access token missing") + return respondOk() + } + private suspend fun MockRequestHandleScope.handleRecovery(request: HttpRequestData): HttpResponseData { if(request.method != HttpMethod.Post) respondBadRequest("Invalid method") val body = try {