diff --git a/Sources/GoTrue/GoTrueClient.swift b/Sources/GoTrue/GoTrueClient.swift index 23b48e87..bf79513d 100644 --- a/Sources/GoTrue/GoTrueClient.swift +++ b/Sources/GoTrue/GoTrueClient.swift @@ -587,17 +587,22 @@ public actor GoTrueClient { } /// Signs out the current user, if there is a logged in user. - public func signOut() async throws { + /// If using `SignOutScope.others` scope, no `AuthChangeEvent.signedOut` event is fired. + /// - Parameter scope: Specifies which sessions should be logged out. + public func signOut(scope: SignOutScope = .global) async throws { do { _ = try await sessionManager.session() try await api.authorizedExecute( .init( path: "/logout", - method: .post + method: .post, + query: [URLQueryItem(name: "scope", value: scope.rawValue)] ) ) - await sessionManager.remove() - eventEmitter.emit(.signedOut, session: nil) + if scope != .others { + await sessionManager.remove() + eventEmitter.emit(.signedOut, session: nil) + } } catch { eventEmitter.emit(.signedOut, session: nil) throw error diff --git a/Sources/GoTrue/Types.swift b/Sources/GoTrue/Types.swift index 639b8b16..36cdc168 100644 --- a/Sources/GoTrue/Types.swift +++ b/Sources/GoTrue/Types.swift @@ -576,6 +576,15 @@ public struct AuthMFAGetAuthenticatorAssuranceLevelResponse: Decodable, Hashable public let currentAuthenticationMethods: [AMREntry] } +public enum SignOutScope: String { + /// All sessions by this account will be signed out. + case global + /// Only this session will be signed out. + case local + /// All other sessions except the current one will be signed out. When using `others`, there is no `AuthChangeEvent.signedOut` event fired on the current session. + case others +} + // MARK: - Encodable & Decodable private let dateFormatterWithFractionalSeconds = { () -> ISO8601DateFormatter in diff --git a/Tests/GoTrueTests/RequestsTests.swift b/Tests/GoTrueTests/RequestsTests.swift index 14188600..0ef78d94 100644 --- a/Tests/GoTrueTests/RequestsTests.swift +++ b/Tests/GoTrueTests/RequestsTests.swift @@ -249,6 +249,30 @@ final class RequestsTests: XCTestCase { } } } + + func testSignOutWithLocalScope() async { + let sut = makeSUT() + await withDependencies { + $0.sessionManager.session = { @Sendable _ in .validSession } + $0.eventEmitter = .noop + } operation: { + await assert { + try await sut.signOut(scope: .local) + } + } + } + + func testSignOutWithOthersScope() async { + let sut = makeSUT() + await withDependencies { + $0.sessionManager.session = { @Sendable _ in .validSession } + $0.eventEmitter = .noop + } operation: { + await assert { + try await sut.signOut(scope: .others) + } + } + } func testVerifyOTPUsingEmail() async { let sut = makeSUT() diff --git a/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOut.1.txt b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOut.1.txt index 0fd288e8..53581e45 100644 --- a/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOut.1.txt +++ b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOut.1.txt @@ -3,4 +3,4 @@ curl \ --header "Authorization: Bearer accesstoken" \ --header "X-Client-Info: gotrue-swift/x.y.z" \ --header "apikey: dummy.api.key" \ - "http://localhost:54321/auth/v1/logout" \ No newline at end of file + "http://localhost:54321/auth/v1/logout?scope=global" \ No newline at end of file diff --git a/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt new file mode 100644 index 00000000..86a4dd75 --- /dev/null +++ b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithLocalScope.1.txt @@ -0,0 +1,6 @@ +curl \ + --request POST \ + --header "Authorization: Bearer accesstoken" \ + --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "apikey: dummy.api.key" \ + "http://localhost:54321/auth/v1/logout?scope=local" \ No newline at end of file diff --git a/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt new file mode 100644 index 00000000..8ae5959e --- /dev/null +++ b/Tests/GoTrueTests/__Snapshots__/RequestsTests/testSignOutWithOthersScope.1.txt @@ -0,0 +1,6 @@ +curl \ + --request POST \ + --header "Authorization: Bearer accesstoken" \ + --header "X-Client-Info: gotrue-swift/x.y.z" \ + --header "apikey: dummy.api.key" \ + "http://localhost:54321/auth/v1/logout?scope=others" \ No newline at end of file