Skip to content

Commit 7601e17

Browse files
authored
feat(auth): add support for error codes and refactor AuthError (#518)
* feat(auth): add support for error codes and refactor errors * update tests * refactor * deprecate old errors * lower camel case error codes * add log * Fix api version parsing * fix tests on linux * Add doc comments for auth errors * implement ~= operator
1 parent fa8db0c commit 7601e17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+489
-129
lines changed

Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ let package = Package(
5555
dependencies: [
5656
.product(name: "CustomDump", package: "swift-custom-dump"),
5757
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
58+
.product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing"),
5859
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
5960
"Helpers",
6061
"Auth",

Sources/Auth/AuthClient.swift

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,10 @@ public final class AuthClient: Sendable {
449449

450450
/// Log in an existing user by exchanging an Auth Code issued during the PKCE flow.
451451
public func exchangeCodeForSession(authCode: String) async throws -> Session {
452-
guard let codeVerifier = codeVerifierStorage.get() else {
453-
throw AuthError.pkce(.codeVerifierNotFound)
452+
let codeVerifier = codeVerifierStorage.get()
453+
454+
if codeVerifier == nil {
455+
logger?.error("code verifier not found, a code verifier should exist when calling this method.")
454456
}
455457

456458
let session: Session = try await api.execute(
@@ -519,14 +521,10 @@ public final class AuthClient: Sendable {
519521
queryParams: [(name: String, value: String?)] = [],
520522
launchFlow: @MainActor @Sendable (_ url: URL) async throws -> URL
521523
) async throws -> Session {
522-
guard let redirectTo = (redirectTo ?? configuration.redirectToURL) else {
523-
throw AuthError.invalidRedirectScheme
524-
}
525-
526524
let url = try getOAuthSignInURL(
527525
provider: provider,
528526
scopes: scopes,
529-
redirectTo: redirectTo,
527+
redirectTo: redirectTo ?? configuration.redirectToURL,
530528
queryParams: queryParams
531529
)
532530

@@ -566,8 +564,9 @@ public final class AuthClient: Sendable {
566564
) { @MainActor url in
567565
try await withCheckedThrowingContinuation { continuation in
568566
guard let callbackScheme = (configuration.redirectToURL ?? redirectTo)?.scheme else {
569-
continuation.resume(throwing: AuthError.invalidRedirectScheme)
570-
return
567+
preconditionFailure(
568+
"Please, provide a valid redirect URL, either thorugh `redirectTo` param, or globally thorugh `AuthClient.Configuration.redirectToURL`."
569+
)
571570
}
572571

573572
#if !os(tvOS) && !os(watchOS)
@@ -583,7 +582,7 @@ public final class AuthClient: Sendable {
583582
} else if let url {
584583
continuation.resume(returning: url)
585584
} else {
586-
continuation.resume(throwing: AuthError.missingURL)
585+
fatalError("Expected url or error, but got none.")
587586
}
588587

589588
#if !os(tvOS) && !os(watchOS)
@@ -674,24 +673,28 @@ public final class AuthClient: Sendable {
674673
let params = extractParams(from: url)
675674

676675
if configuration.flowType == .implicit, !isImplicitGrantFlow(params: params) {
677-
throw AuthError.invalidImplicitGrantFlowURL
676+
throw AuthError.implicitGrantRedirect(message: "Not a valid implicit grant flow url: \(url)")
678677
}
679678

680679
if configuration.flowType == .pkce, !isPKCEFlow(params: params) {
681-
throw AuthError.pkce(.invalidPKCEFlowURL)
680+
throw AuthError.pkceGrantCodeExchange(message: "Not a valid PKCE flow url: \(url)")
682681
}
683682

684683
if isPKCEFlow(params: params) {
685684
guard let code = params["code"] else {
686-
throw AuthError.pkce(.codeVerifierNotFound)
685+
throw AuthError.pkceGrantCodeExchange(message: "No code detected.")
687686
}
688687

689688
let session = try await exchangeCodeForSession(authCode: code)
690689
return session
691690
}
692691

693-
if let errorDescription = params["error_description"] {
694-
throw AuthError.api(.init(errorDescription: errorDescription))
692+
if params["error"] != nil || params["error_description"] != nil || params["error_code"] != nil {
693+
throw AuthError.pkceGrantCodeExchange(
694+
message: params["error_description"] ?? "Error in URL with unspecified error_description.",
695+
error: params["error"] ?? "unspecified_error",
696+
code: params["error_code"] ?? "unspecified_code"
697+
)
695698
}
696699

697700
guard
@@ -700,7 +703,7 @@ public final class AuthClient: Sendable {
700703
let refreshToken = params["refresh_token"],
701704
let tokenType = params["token_type"]
702705
else {
703-
throw URLError(.badURL)
706+
throw AuthError.implicitGrantRedirect(message: "No session defined in URL")
704707
}
705708

706709
let expiresAt = params["expires_at"].flatMap(TimeInterval.init)
@@ -753,11 +756,9 @@ public final class AuthClient: Sendable {
753756
var session: Session
754757

755758
let jwt = try decode(jwt: accessToken)
756-
if let exp = jwt["exp"] as? TimeInterval {
759+
if let exp = jwt?["exp"] as? TimeInterval {
757760
expiresAt = Date(timeIntervalSince1970: exp)
758761
hasExpired = expiresAt <= now
759-
} else {
760-
throw AuthError.missingExpClaim
761762
}
762763

763764
if hasExpired {
@@ -803,16 +804,9 @@ public final class AuthClient: Sendable {
803804
headers: [.init(name: "Authorization", value: "Bearer \(accessToken)")]
804805
)
805806
)
806-
} catch {
807+
} catch let AuthError.api(_, _, _, response) where [404, 403, 401].contains(response.statusCode) {
807808
// ignore 404s since user might not exist anymore
808809
// ignore 401s, and 403s since an invalid or expired JWT should sign out the current session.
809-
let ignoredCodes = Set([404, 403, 401])
810-
811-
if case let AuthError.api(apiError) = error, let code = apiError.code,
812-
!ignoredCodes.contains(code)
813-
{
814-
throw error
815-
}
816810
}
817811
}
818812

@@ -1169,7 +1163,7 @@ public final class AuthClient: Sendable {
11691163
@discardableResult
11701164
public func refreshSession(refreshToken: String? = nil) async throws -> Session {
11711165
guard let refreshToken = refreshToken ?? currentSession?.refreshToken else {
1172-
throw AuthError.sessionNotFound
1166+
throw AuthError.sessionMissing
11731167
}
11741168

11751169
return try await sessionManager.refreshSession(refreshToken)

0 commit comments

Comments
 (0)