Skip to content

Commit e38e702

Browse files
committed
chore(auth): general auth improvements
1 parent 45273e5 commit e38e702

File tree

11 files changed

+89
-64
lines changed

11 files changed

+89
-64
lines changed

Sources/Auth/AuthClient.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ public final class AuthClient: Sendable {
3737
///
3838
/// The session returned by this property may be expired. Use ``session`` for a session that is guaranteed to be valid.
3939
public var currentSession: Session? {
40-
try? sessionStorage.get()
40+
sessionStorage.get()
4141
}
4242

4343
/// Returns the current user, if any.
4444
///
4545
/// The user returned by this property may be outdated. Use ``user(jwt:)`` method to get an up-to-date user instance.
4646
public var currentUser: User? {
47-
try? sessionStorage.get()?.user
47+
currentSession?.user
4848
}
4949

5050
/// Namespace for accessing multi-factor authentication API.

Sources/Auth/AuthClientConfiguration.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,28 @@ extension AuthClient {
2020

2121
/// Configuration struct represents the client configuration.
2222
public struct Configuration: Sendable {
23+
/// The URL of the Auth server.
2324
public let url: URL
25+
26+
/// Any additional headers to send to the Auth server.
2427
public var headers: [String: String]
2528
public let flowType: AuthFlowType
29+
30+
/// Default URL to be used for redirect on the flows that requires it.
2631
public let redirectToURL: URL?
2732

2833
/// Optional key name used for storing tokens in local storage.
2934
public var storageKey: String?
35+
36+
/// Provider your own local storage implementation to use instead of the default one.
3037
public let localStorage: any AuthLocalStorage
38+
39+
/// Custom SupabaseLogger implementation used to inspecting log messages from the Auth library.
3140
public let logger: (any SupabaseLogger)?
3241
public let encoder: JSONEncoder
3342
public let decoder: JSONDecoder
43+
44+
/// A custom fetch implementation.
3445
public let fetch: FetchHandler
3546

3647
/// Set to `true` if you want to automatically refresh the token before expiring.
@@ -51,7 +62,7 @@ extension AuthClient {
5162
/// - fetch: The asynchronous fetch handler for network requests.
5263
/// - autoRefreshToken: Set to `true` if you want to automatically refresh the token before expiring.
5364
public init(
54-
url: URL,
65+
url: URL? = nil,
5566
headers: [String: String] = [:],
5667
flowType: AuthFlowType = Configuration.defaultFlowType,
5768
redirectToURL: URL? = nil,
@@ -65,7 +76,7 @@ extension AuthClient {
6576
) {
6677
let headers = headers.merging(Configuration.defaultHeaders) { l, _ in l }
6778

68-
self.url = url
79+
self.url = url ?? defaultAuthURL
6980
self.headers = headers
7081
self.flowType = flowType
7182
self.redirectToURL = redirectToURL
@@ -94,7 +105,7 @@ extension AuthClient {
94105
/// - fetch: The asynchronous fetch handler for network requests.
95106
/// - autoRefreshToken: Set to `true` if you want to automatically refresh the token before expiring.
96107
public convenience init(
97-
url: URL,
108+
url: URL? = nil,
98109
headers: [String: String] = [:],
99110
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
100111
redirectToURL: URL? = nil,

Sources/Auth/AuthError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public enum AuthError: LocalizedError, Equatable {
182182
errorCode: .unknown,
183183
underlyingData: (try? AuthClient.Configuration.jsonEncoder.encode(error)) ?? Data(),
184184
underlyingResponse: HTTPURLResponse(
185-
url: URL(string: "http://localhost")!,
185+
url: defaultAuthURL,
186186
statusCode: error.code ?? 500,
187187
httpVersion: nil,
188188
headerFields: nil

Sources/Auth/Internal/Contants.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
import Foundation
99

10-
let EXPIRY_MARGIN: TimeInterval = 30
10+
let defaultAuthURL = URL(string: "http://localhost:9999")!
11+
let defaultExpiryMargin: TimeInterval = 30
1112
let STORAGE_KEY = "supabase.auth.token"
1213

1314
let API_VERSION_HEADER_NAME = "X-Supabase-Api-Version"

Sources/Auth/Internal/SessionManager.swift

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private actor LiveSessionManager {
3939

4040
func session() async throws -> Session {
4141
try await trace(using: logger) {
42-
guard let currentSession = try sessionStorage.get() else {
42+
guard let currentSession = sessionStorage.get() else {
4343
throw AuthError.sessionMissing
4444
}
4545

@@ -78,7 +78,9 @@ private actor LiveSessionManager {
7878
query: [
7979
URLQueryItem(name: "grant_type", value: "refresh_token"),
8080
],
81-
body: configuration.encoder.encode(UserCredentials(refreshToken: refreshToken))
81+
body: configuration.encoder.encode(
82+
UserCredentials(refreshToken: refreshToken)
83+
)
8284
)
8385
)
8486
.decoded(as: Session.self, decoder: configuration.decoder)
@@ -97,22 +99,17 @@ private actor LiveSessionManager {
9799
}
98100

99101
func update(_ session: Session) {
100-
do {
101-
try sessionStorage.store(session)
102-
} catch {
103-
logger?.error("Failed to store session: \(error)")
104-
}
102+
sessionStorage.store(session)
105103
}
106104

107105
func remove() {
108-
do {
109-
try sessionStorage.delete()
110-
} catch {
111-
logger?.error("Failed to remove session: \(error)")
112-
}
106+
sessionStorage.delete()
113107
}
114108

115-
private func scheduleNextTokenRefresh(_ refreshedSession: Session, caller: StaticString = #function) async {
109+
private func scheduleNextTokenRefresh(
110+
_ refreshedSession: Session,
111+
caller: StaticString = #function
112+
) async {
116113
await SupabaseLoggerTaskLocal.$additionalContext.withValue(
117114
merging: ["caller": .string("\(caller)")]
118115
) {

Sources/Auth/Internal/SessionStorage.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import Foundation
99
import Helpers
1010

1111
struct SessionStorage {
12-
var get: @Sendable () throws -> Session?
13-
var store: @Sendable (_ session: Session) throws -> Void
14-
var delete: @Sendable () throws -> Void
12+
var get: @Sendable () -> Session?
13+
var store: @Sendable (_ session: Session) -> Void
14+
var delete: @Sendable () -> Void
1515
}
1616

1717
extension SessionStorage {
@@ -50,19 +50,32 @@ extension SessionStorage {
5050
}
5151
}
5252

53-
let storedData = try storage.retrieve(key: key)
54-
return try storedData.flatMap {
55-
try AuthClient.Configuration.jsonDecoder.decode(Session.self, from: $0)
53+
do {
54+
let storedData = try storage.retrieve(key: key)
55+
return try storedData.flatMap {
56+
try AuthClient.Configuration.jsonDecoder.decode(Session.self, from: $0)
57+
}
58+
} catch {
59+
logger?.error("Failed to retrieve session: \(error.localizedDescription)")
60+
return nil
5661
}
5762
},
5863
store: { session in
59-
try storage.store(
60-
key: key,
61-
value: AuthClient.Configuration.jsonEncoder.encode(session)
62-
)
64+
do {
65+
try storage.store(
66+
key: key,
67+
value: AuthClient.Configuration.jsonEncoder.encode(session)
68+
)
69+
} catch {
70+
logger?.error("Failed to store session: \(error.localizedDescription)")
71+
}
6372
},
6473
delete: {
65-
try storage.remove(key: key)
74+
do {
75+
try storage.remove(key: key)
76+
} catch {
77+
logger?.error("Failed to delete session: \(error.localizedDescription)")
78+
}
6679
}
6780
)
6881
}

Sources/Auth/Types.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public struct Session: Codable, Hashable, Sendable {
114114
/// The 30 second buffer is to account for latency issues.
115115
public var isExpired: Bool {
116116
let expiresAt = Date(timeIntervalSince1970: expiresAt)
117-
return expiresAt.timeIntervalSinceNow < EXPIRY_MARGIN
117+
return expiresAt.timeIntervalSinceNow < defaultExpiryMargin
118118
}
119119
}
120120

@@ -286,6 +286,7 @@ public struct UserIdentity: Codable, Hashable, Identifiable, Sendable {
286286
}
287287
}
288288

289+
/// One of the providers supported by Auth.
289290
public enum Provider: String, Identifiable, Codable, CaseIterable, Sendable {
290291
case apple
291292
case azure
@@ -478,6 +479,7 @@ public struct UserAttributes: Codable, Hashable, Sendable {
478479
public var nonce: String?
479480

480481
/// An email change token.
482+
@available(*, deprecated, message: "This is an old field, stop relying on it.")
481483
public var emailChangeToken: String?
482484
/// A custom data object to store the user's metadata. This maps to the `auth.users.user_metadata`
483485
/// column. The `data` should be a JSON object that includes user-specific info, such as their

Tests/AuthTests/AuthClientTests.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ final class AuthClientTests: XCTestCase {
5353
func testOnAuthStateChanges() async throws {
5454
let session = Session.validSession
5555
let sut = makeSUT()
56-
try Dependencies[sut.clientID].sessionStorage.store(session)
56+
Dependencies[sut.clientID].sessionStorage.store(session)
5757

5858
let events = LockIsolated([AuthChangeEvent]())
5959

@@ -71,7 +71,7 @@ final class AuthClientTests: XCTestCase {
7171
func testAuthStateChanges() async throws {
7272
let session = Session.validSession
7373
let sut = makeSUT()
74-
try Dependencies[sut.clientID].sessionStorage.store(session)
74+
Dependencies[sut.clientID].sessionStorage.store(session)
7575

7676
let stateChange = await sut.authStateChanges.first { _ in true }
7777
expectNoDifference(stateChange?.event, .initialSession)
@@ -83,7 +83,7 @@ final class AuthClientTests: XCTestCase {
8383
.stub()
8484
}
8585

86-
try Dependencies[sut.clientID].sessionStorage.store(.validSession)
86+
Dependencies[sut.clientID].sessionStorage.store(.validSession)
8787

8888
let eventsTask = Task {
8989
await sut.authStateChanges.prefix(2).collect()
@@ -112,11 +112,11 @@ final class AuthClientTests: XCTestCase {
112112
.stub()
113113
}
114114

115-
try Dependencies[sut.clientID].sessionStorage.store(.validSession)
115+
Dependencies[sut.clientID].sessionStorage.store(.validSession)
116116

117117
try await sut.signOut(scope: .others)
118118

119-
let sessionRemoved = try Dependencies[sut.clientID].sessionStorage.get() == nil
119+
let sessionRemoved = Dependencies[sut.clientID].sessionStorage.get() == nil
120120
XCTAssertFalse(sessionRemoved)
121121
}
122122

@@ -131,7 +131,7 @@ final class AuthClientTests: XCTestCase {
131131
}
132132

133133
let validSession = Session.validSession
134-
try Dependencies[sut.clientID].sessionStorage.store(validSession)
134+
Dependencies[sut.clientID].sessionStorage.store(validSession)
135135

136136
let eventsTask = Task {
137137
await sut.authStateChanges.prefix(2).collect()
@@ -147,7 +147,7 @@ final class AuthClientTests: XCTestCase {
147147
expectNoDifference(events, [.initialSession, .signedOut])
148148
expectNoDifference(sessions, [.validSession, nil])
149149

150-
let sessionRemoved = try Dependencies[sut.clientID].sessionStorage.get() == nil
150+
let sessionRemoved = Dependencies[sut.clientID].sessionStorage.get() == nil
151151
XCTAssertTrue(sessionRemoved)
152152
}
153153

@@ -162,7 +162,7 @@ final class AuthClientTests: XCTestCase {
162162
}
163163

164164
let validSession = Session.validSession
165-
try Dependencies[sut.clientID].sessionStorage.store(validSession)
165+
Dependencies[sut.clientID].sessionStorage.store(validSession)
166166

167167
let eventsTask = Task {
168168
await sut.authStateChanges.prefix(2).collect()
@@ -178,7 +178,7 @@ final class AuthClientTests: XCTestCase {
178178
expectNoDifference(events, [.initialSession, .signedOut])
179179
expectNoDifference(sessions, [validSession, nil])
180180

181-
let sessionRemoved = try Dependencies[sut.clientID].sessionStorage.get() == nil
181+
let sessionRemoved = Dependencies[sut.clientID].sessionStorage.get() == nil
182182
XCTAssertTrue(sessionRemoved)
183183
}
184184

@@ -193,7 +193,7 @@ final class AuthClientTests: XCTestCase {
193193
}
194194

195195
let validSession = Session.validSession
196-
try Dependencies[sut.clientID].sessionStorage.store(validSession)
196+
Dependencies[sut.clientID].sessionStorage.store(validSession)
197197

198198
let eventsTask = Task {
199199
await sut.authStateChanges.prefix(2).collect()
@@ -209,7 +209,7 @@ final class AuthClientTests: XCTestCase {
209209
expectNoDifference(events, [.initialSession, .signedOut])
210210
expectNoDifference(sessions, [validSession, nil])
211211

212-
let sessionRemoved = try Dependencies[sut.clientID].sessionStorage.get() == nil
212+
let sessionRemoved = Dependencies[sut.clientID].sessionStorage.get() == nil
213213
XCTAssertTrue(sessionRemoved)
214214
}
215215

@@ -269,7 +269,7 @@ final class AuthClientTests: XCTestCase {
269269
)
270270
}
271271

272-
try Dependencies[sut.clientID].sessionStorage.store(.validSession)
272+
Dependencies[sut.clientID].sessionStorage.store(.validSession)
273273

274274
let response = try await sut.getLinkIdentityURL(provider: .github)
275275

@@ -294,7 +294,7 @@ final class AuthClientTests: XCTestCase {
294294
)
295295
}
296296

297-
try Dependencies[sut.clientID].sessionStorage.store(.validSession)
297+
Dependencies[sut.clientID].sessionStorage.store(.validSession)
298298

299299
let receivedURL = LockIsolated<URL?>(nil)
300300
Dependencies[sut.clientID].urlOpener.open = { url in

0 commit comments

Comments
 (0)