Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions Sources/Auth/AuthClientConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ extension AuthClient {
public var headers: [String: String]
public let flowType: AuthFlowType
public let redirectToURL: URL?

/// Optional key name used for storing tokens in local storage.
public var storageKey: String?
public let localStorage: any AuthLocalStorage
public let logger: (any SupabaseLogger)?
public let encoder: JSONEncoder
Expand All @@ -40,6 +43,7 @@ extension AuthClient {
/// - headers: Custom headers to be included in requests.
/// - flowType: The authentication flow type.
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
/// - storageKey: Optional key name used for storing tokens in local storage.
/// - localStorage: The storage mechanism for local data.
/// - logger: The logger to use.
/// - encoder: The JSON encoder to use for encoding requests.
Expand All @@ -51,6 +55,7 @@ extension AuthClient {
headers: [String: String] = [:],
flowType: AuthFlowType = Configuration.defaultFlowType,
redirectToURL: URL? = nil,
storageKey: String? = nil,
localStorage: any AuthLocalStorage,
logger: (any SupabaseLogger)? = nil,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
Expand All @@ -64,6 +69,7 @@ extension AuthClient {
self.headers = headers
self.flowType = flowType
self.redirectToURL = redirectToURL
self.storageKey = storageKey
self.localStorage = localStorage
self.logger = logger
self.encoder = encoder
Expand All @@ -80,6 +86,7 @@ extension AuthClient {
/// - headers: Custom headers to be included in requests.
/// - flowType: The authentication flow type..
/// - redirectToURL: Default URL to be used for redirect on the flows that requires it.
/// - storageKey: Optional key name used for storing tokens in local storage.
/// - localStorage: The storage mechanism for local data..
/// - logger: The logger to use.
/// - encoder: The JSON encoder to use for encoding requests.
Expand All @@ -91,6 +98,7 @@ extension AuthClient {
headers: [String: String] = [:],
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
redirectToURL: URL? = nil,
storageKey: String? = nil,
localStorage: any AuthLocalStorage,
logger: (any SupabaseLogger)? = nil,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
Expand All @@ -104,6 +112,7 @@ extension AuthClient {
headers: headers,
flowType: flowType,
redirectToURL: redirectToURL,
storageKey: storageKey,
localStorage: localStorage,
logger: logger,
encoder: encoder,
Expand Down
2 changes: 2 additions & 0 deletions Sources/Auth/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ extension AuthClient.Configuration {

/// The default value when initializing a ``AuthClient`` instance.
public static let defaultAutoRefreshToken: Bool = true

static let defaultStorageKey = "supabase.auth.token"
}
23 changes: 20 additions & 3 deletions Sources/Auth/Internal/SessionStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,37 @@ struct StoredSession: Codable {
}

extension AuthLocalStorage {
var key: String {
Current.configuration.storageKey ?? AuthClient.Configuration.defaultStorageKey
}

var oldKey: String { "supabase.session" }

func getSession() throws -> Session? {
try retrieve(key: "supabase.session").flatMap {
var storedData = try? retrieve(key: oldKey)

if let storedData {
// migrate to new key.
try store(key: key, value: storedData)
try? remove(key: oldKey)
} else {
storedData = try retrieve(key: key)
}

return try storedData.flatMap {
try AuthClient.Configuration.jsonDecoder.decode(StoredSession.self, from: $0).session
}
}

func storeSession(_ session: Session) throws {
try store(
key: "supabase.session",
key: key,
value: AuthClient.Configuration.jsonEncoder.encode(StoredSession(session: session))
)
}

func deleteSession() throws {
try remove(key: "supabase.session")
try remove(key: key)
try? remove(key: oldKey)
}
}
4 changes: 4 additions & 0 deletions Sources/Supabase/SupabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,15 @@ public final class SupabaseClient: Sendable {
])
.merged(with: HTTPHeaders(options.global.headers))

// default storage key uses the supabase project ref as a namespace
let defaultStorageKey = "sb-\(supabaseURL.host!.split(separator: ".")[0])-auth-token"

auth = AuthClient(
url: supabaseURL.appendingPathComponent("/auth/v1"),
headers: defaultHeaders.dictionary,
flowType: options.auth.flowType,
redirectToURL: options.auth.redirectToURL,
storageKey: options.auth.storageKey ?? defaultStorageKey,
localStorage: options.auth.storage,
logger: options.global.logger,
encoder: options.auth.encoder,
Expand Down
7 changes: 7 additions & 0 deletions Sources/Supabase/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public struct SupabaseClientOptions: Sendable {
/// Default URL to be used for redirect on the flows that requires it.
public let redirectToURL: URL?

/// Optional key name used for storing tokens in local storage.
public let storageKey: String?

/// OAuth flow to use - defaults to PKCE flow. PKCE is recommended for mobile and server-side
/// applications.
public let flowType: AuthFlowType
Expand All @@ -60,13 +63,15 @@ public struct SupabaseClientOptions: Sendable {
public init(
storage: any AuthLocalStorage,
redirectToURL: URL? = nil,
storageKey: String? = nil,
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
autoRefreshToken: Bool = AuthClient.Configuration.defaultAutoRefreshToken
) {
self.storage = storage
self.redirectToURL = redirectToURL
self.storageKey = storageKey
self.flowType = flowType
self.encoder = encoder
self.decoder = decoder
Expand Down Expand Up @@ -145,6 +150,7 @@ extension SupabaseClientOptions.AuthOptions {
#if !os(Linux)
public init(
redirectToURL: URL? = nil,
storageKey: String? = nil,
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
Expand All @@ -153,6 +159,7 @@ extension SupabaseClientOptions.AuthOptions {
self.init(
storage: AuthClient.Configuration.defaultLocalStorage,
redirectToURL: redirectToURL,
storageKey: storageKey,
flowType: flowType,
encoder: encoder,
decoder: decoder,
Expand Down
12 changes: 12 additions & 0 deletions Tests/AuthTests/MockHelpers.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import TestHelpers

@testable import Auth

Expand All @@ -12,3 +13,14 @@ extension Decodable {
self = try! AuthClient.Configuration.jsonDecoder.decode(Self.self, from: json(named: name))
}
}

extension Dependencies {
static var mock = Dependencies(
configuration: AuthClient.Configuration(
url: URL(string: "https://project-id.supabase.com")!,
localStorage: InMemoryLocalStorage(),
logger: nil
),
http: HTTPClientMock()
)
}
2 changes: 1 addition & 1 deletion Tests/AuthTests/Resources/local-storage.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"supabase.session" : {
"supabase.auth.token" : {
"expiration_date" : "2024-04-01T13:25:07.000Z",
"session" : {
"access_token" : "accesstoken",
Expand Down
7 changes: 3 additions & 4 deletions Tests/AuthTests/StoredSessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import SnapshotTesting
import XCTest

final class StoredSessionTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
}

func testStoredSession() throws {
let sut = try! DiskTestStorage()

Current = .mock
Current.configuration.storageKey = "supabase.auth.token"

let _ = try sut.getSession()

let session = Session(
Expand Down
1 change: 1 addition & 0 deletions Tests/SupabaseTests/SupabaseClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ final class SupabaseClientTests: XCTestCase {
XCTAssertIdentical(realtimeOptions.logger as? Logger, logger)

XCTAssertFalse(client.auth.configuration.autoRefreshToken)
XCTAssertEqual(client.auth.configuration.storageKey, "sb-project-ref-auth-token")
}

#if !os(Linux)
Expand Down