Skip to content

Commit d482709

Browse files
authored
Add logout scope option (#220)
* Add logout scope parameter Signed-off-by: TheRealJan <[email protected]> * Delete code verifier on logout Signed-off-by: TheRealJan <[email protected]> * Fix tests Signed-off-by: TheRealJan <[email protected]> --------- Signed-off-by: TheRealJan <[email protected]>
1 parent a7db181 commit d482709

File tree

5 files changed

+43
-9
lines changed

5 files changed

+43
-9
lines changed

GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrue.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,13 @@ sealed interface GoTrue : MainPlugin<GoTrueConfig>, CustomSerializationPlugin {
257257

258258
/**
259259
* Logs out the current user, which means [sessionStatus] will be [SessionStatus.NotAuthenticated] and the access token will be revoked
260+
* @param scope The scope of the logout.
260261
* @throws RestException or one of its subclasses if receiving an error response
261262
* @throws HttpRequestTimeoutException if the request timed out
262263
* @throws HttpRequestException on network related issues
264+
* @see LogoutScope
263265
*/
264-
suspend fun logout()
266+
suspend fun logout(scope: LogoutScope = LogoutScope.LOCAL)
265267

266268
/**
267269
* Imports a user session and starts auto-refreshing if [autoRefresh] is true

GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueImpl.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.github.jan.supabase.putJsonObject
2525
import io.github.jan.supabase.safeBody
2626
import io.github.jan.supabase.supabaseJson
2727
import io.ktor.client.call.body
28+
import io.ktor.client.request.parameter
2829
import io.ktor.client.statement.HttpResponse
2930
import io.ktor.client.statement.bodyAsText
3031
import io.ktor.http.HttpStatusCode
@@ -237,11 +238,17 @@ internal class GoTrueImpl(
237238
api.get("reauthenticate")
238239
}
239240

240-
override suspend fun logout() {
241-
sessionManager.deleteSession()
242-
sessionJob?.cancel()
243-
_sessionStatus.value = SessionStatus.NotAuthenticated
244-
sessionJob = null
241+
override suspend fun logout(scope: LogoutScope) {
242+
api.post("logout") {
243+
parameter("scope", scope.name.lowercase())
244+
}
245+
if(scope != LogoutScope.OTHERS) {
246+
codeVerifierCache.deleteCodeVerifier()
247+
sessionManager.deleteSession()
248+
sessionJob?.cancel()
249+
_sessionStatus.value = SessionStatus.NotAuthenticated
250+
sessionJob = null
251+
}
245252
}
246253

247254
private suspend fun verify(
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.github.jan.supabase.gotrue
2+
3+
/**
4+
* Represents the scope of a logout action.
5+
*
6+
* The logout scope determines the scope of the logout action being performed.
7+
*
8+
* @property GLOBAL Logout action applies to all sessions across the entire system.
9+
* @property LOCAL Logout action applies only to the current session.
10+
* @property OTHERS Logout action applies to other sessions, excluding the current session.
11+
*/
12+
enum class LogoutScope {
13+
GLOBAL, LOCAL, OTHERS
14+
}

GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/admin/AdminApi.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package io.github.jan.supabase.gotrue.admin
33
import io.github.jan.supabase.annotations.SupabaseInternal
44
import io.github.jan.supabase.gotrue.GoTrue
55
import io.github.jan.supabase.gotrue.GoTrueImpl
6+
import io.github.jan.supabase.gotrue.LogoutScope
67
import io.github.jan.supabase.gotrue.user.UserInfo
78
import io.github.jan.supabase.gotrue.user.UserMfaFactor
89
import io.github.jan.supabase.putJsonObject
910
import io.github.jan.supabase.safeBody
1011
import io.github.jan.supabase.supabaseJson
1112
import io.ktor.client.call.body
13+
import io.ktor.client.request.parameter
1214
import io.ktor.http.HttpHeaders
1315
import kotlinx.serialization.json.Json
1416
import kotlinx.serialization.json.JsonObject
@@ -37,8 +39,10 @@ sealed interface AdminApi {
3739

3840
/**
3941
* Removes a user session
42+
* @param jwt the jwt of the session
43+
* @param scope the scope of the logout action
4044
*/
41-
suspend fun logout(jwt: String)
45+
suspend fun logout(jwt: String, scope: LogoutScope = LogoutScope.LOCAL)
4246

4347
/**
4448
* Retrieves all users
@@ -92,8 +96,9 @@ internal class AdminApiImpl(val gotrue: GoTrue) : AdminApi {
9296

9397
val api = (gotrue as GoTrueImpl).api
9498

95-
override suspend fun logout(jwt: String) {
99+
override suspend fun logout(jwt: String, scope: LogoutScope) {
96100
api.post("logout") {
101+
parameter("scope", scope.name.lowercase())
97102
headers[HttpHeaders.Authorization] = "Bearer $jwt"
98103
}
99104
}

GoTrue/src/commonTest/kotlin/GoTrueMock.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import io.ktor.http.HttpMethod
1515
import io.ktor.http.HttpStatusCode
1616
import io.ktor.http.headersOf
1717
import kotlinx.datetime.Clock
18-
import kotlinx.serialization.decodeFromString
1918
import kotlinx.serialization.encodeToString
2019
import kotlinx.serialization.json.Json
2120
import kotlinx.serialization.json.JsonObject
@@ -40,10 +39,17 @@ class GoTrueMock {
4039
urlWithoutQuery.endsWith("verify") -> handleVerify(request)
4140
urlWithoutQuery.endsWith("otp") -> handleOtp(request)
4241
urlWithoutQuery.endsWith("recover") -> handleRecovery(request)
42+
urlWithoutQuery.endsWith("logout") -> handleLogout(request)
4343
else -> null
4444
}
4545
}
4646

47+
private suspend fun MockRequestHandleScope.handleLogout(request: HttpRequestData): HttpResponseData {
48+
if(request.method != HttpMethod.Post) return respondBadRequest("Invalid method")
49+
if(!request.headers.contains("Authorization")) return respondBadRequest("access token missing")
50+
return respondOk()
51+
}
52+
4753
private suspend fun MockRequestHandleScope.handleRecovery(request: HttpRequestData): HttpResponseData {
4854
if(request.method != HttpMethod.Post) respondBadRequest("Invalid method")
4955
val body = try {

0 commit comments

Comments
 (0)