Skip to content

Commit

Permalink
feat: prepare for v2 release (#187)
Browse files Browse the repository at this point in the history
* feat: use nil as default value for configuration params

* feat: rename GoTrue to Auth

* Deprecate access to default JSONEncoder and JSONDecoder

* Refactor default initialization values

* Bump version to 2.0.0

* Fix tests
  • Loading branch information
grdsdev committed Dec 14, 2023
1 parent e5f1e4b commit 036c03d
Show file tree
Hide file tree
Showing 53 changed files with 379 additions and 264 deletions.
15 changes: 9 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ let package = Package(
],
products: [
.library(name: "Functions", targets: ["Functions"]),
.library(name: "GoTrue", targets: ["GoTrue"]),
.library(name: "Auth", targets: ["Auth"]),
.library(name: "PostgREST", targets: ["PostgREST"]),
.library(name: "Realtime", targets: ["Realtime"]),
.library(name: "Storage", targets: ["Storage"]),
.library(
name: "Supabase",
targets: ["Supabase", "Functions", "PostgREST", "GoTrue", "Realtime", "Storage"]
targets: ["Supabase", "Functions", "PostgREST", "Auth", "Realtime", "Storage"]
),
],
dependencies: [
Expand All @@ -46,20 +46,23 @@ let package = Package(
]
),
.target(
name: "GoTrue",
name: "Auth",
dependencies: [
"_Helpers",
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
.product(name: "KeychainAccess", package: "KeychainAccess"),
]
),
.testTarget(
name: "GoTrueTests",
name: "AuthTests",
dependencies: [
"GoTrue",
"Auth",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
],
exclude: [
"__Snapshots__",
],
resources: [.process("Resources")]
),
.target(
Expand Down Expand Up @@ -93,7 +96,7 @@ let package = Package(
name: "Supabase",
dependencies: [
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
"GoTrue",
"Auth",
"Storage",
"Realtime",
"PostgREST",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let package = Package(

If you're using Xcode, [use this guide](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) to add `supabase-swift` to your project. Use `https://github.com/supabase-community/supabase-swift.git` for the url when Xcode asks.

If you don't want the full Supabase environment, you can also add individual packages, such as `Functions`, `GoTrue`, `Realtime`, `Storage`, or `PostgREST`.
If you don't want the full Supabase environment, you can also add individual packages, such as `Functions`, `Auth`, `Realtime`, `Storage`, or `PostgREST`.

Then you're able to import the package and establish the connection with the database.

Expand Down
114 changes: 52 additions & 62 deletions Sources/GoTrue/GoTrueClient.swift → Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation
import FoundationNetworking
#endif

public actor GoTrueClient {
public actor AuthClient {
/// FetchHandler is a type alias for asynchronous network request handling.
public typealias FetchHandler =
@Sendable (_ request: URLRequest) async throws -> (Data, URLResponse)
Expand All @@ -15,45 +15,36 @@ public actor GoTrueClient {
public let url: URL
public var headers: [String: String]
public let flowType: AuthFlowType
public let localStorage: GoTrueLocalStorage
public let localStorage: AuthLocalStorage
public let encoder: JSONEncoder
public let decoder: JSONDecoder
public let fetch: FetchHandler

/// Initializes a GoTrueClient Configuration with optional parameters.
/// Initializes a AuthClient Configuration with optional parameters.
///
/// - Parameters:
/// - url: The base URL of the GoTrue server.
/// - headers: (Optional) Custom headers to be included in requests.
/// - flowType: (Optional) The authentication flow type. Default is `.implicit`.
/// - localStorage: (Optional) The storage mechanism for local data. Default is a
/// KeychainLocalStorage.
/// - encoder: (Optional) The JSON encoder to use for encoding requests.
/// - decoder: (Optional) The JSON decoder to use for decoding responses.
/// - fetch: (Optional) The asynchronous fetch handler for network requests.
/// - url: The base URL of the Auth server.
/// - headers: Custom headers to be included in requests.
/// - flowType: The authentication flow type.
/// - localStorage: The storage mechanism for local data.
/// - encoder: The JSON encoder to use for encoding requests.
/// - decoder: The JSON decoder to use for decoding responses.
/// - fetch: The asynchronous fetch handler for network requests.
public init(
url: URL,
headers: [String: String] = [:],
flowType: AuthFlowType = .implicit,
localStorage: GoTrueLocalStorage? = nil,
encoder: JSONEncoder = .goTrue,
decoder: JSONDecoder = .goTrue,
flowType: AuthFlowType = Configuration.defaultFlowType,
localStorage: AuthLocalStorage = Configuration.defaultLocalStorage,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
) {
var headers = headers
if headers["X-Client-Info"] == nil {
headers["X-Client-Info"] = "gotrue-swift/\(version)"
}
let headers = headers.merging(Configuration.defaultHeaders) { l, _ in l }

self.url = url
self.headers = headers
self.flowType = flowType
self.localStorage =
localStorage
?? KeychainLocalStorage(
service: "supabase.gotrue.swift",
accessGroup: nil
)
self.localStorage = localStorage
self.encoder = encoder
self.decoder = decoder
self.fetch = fetch
Expand Down Expand Up @@ -82,34 +73,33 @@ public actor GoTrueClient {

/// Returns the session, refreshing it if necessary.
///
/// If no session can be found, a ``GoTrueError/sessionNotFound`` error is thrown.
/// If no session can be found, a ``AuthError/sessionNotFound`` error is thrown.
public var session: Session {
get async throws {
try await sessionManager.session()
}
}

/// Namespace for accessing multi-factor authentication API.
public let mfa: GoTrueMFA
public let mfa: AuthMFA

/// Initializes a GoTrueClient with optional parameters.
/// Initializes a AuthClient with optional parameters.
///
/// - Parameters:
/// - url: The base URL of the GoTrue server.
/// - headers: (Optional) Custom headers to be included in requests.
/// - flowType: (Optional) The authentication flow type. Default is `.implicit`.
/// - localStorage: (Optional) The storage mechanism for local data. Default is a
/// KeychainLocalStorage.
/// - encoder: (Optional) The JSON encoder to use for encoding requests.
/// - decoder: (Optional) The JSON decoder to use for decoding responses.
/// - fetch: (Optional) The asynchronous fetch handler for network requests.
/// - url: The base URL of the Auth server.
/// - headers: Custom headers to be included in requests.
/// - flowType: The authentication flow type..
/// - localStorage: The storage mechanism for local data..
/// - encoder: The JSON encoder to use for encoding requests.
/// - decoder: The JSON decoder to use for decoding responses.
/// - fetch: The asynchronous fetch handler for network requests.
public init(
url: URL,
headers: [String: String] = [:],
flowType: AuthFlowType = .implicit,
localStorage: GoTrueLocalStorage? = nil,
encoder: JSONEncoder = .goTrue,
decoder: JSONDecoder = .goTrue,
flowType: AuthFlowType = AuthClient.Configuration.defaultFlowType,
localStorage: AuthLocalStorage = AuthClient.Configuration.defaultLocalStorage,
encoder: JSONEncoder = AuthClient.Configuration.jsonEncoder,
decoder: JSONDecoder = AuthClient.Configuration.jsonDecoder,
fetch: @escaping FetchHandler = { try await URLSession.shared.data(for: $0) }
) {
self.init(
Expand All @@ -125,7 +115,7 @@ public actor GoTrueClient {
)
}

/// Initializes a GoTrueClient with a specific configuration.
/// Initializes a AuthClient with a specific configuration.
///
/// - Parameters:
/// - configuration: The client configuration.
Expand All @@ -151,7 +141,7 @@ public actor GoTrueClient {
eventEmitter: EventEmitter,
sessionStorage: SessionStorage
) {
mfa = GoTrueMFA()
mfa = AuthMFA()

Dependencies.current.setValue(
Dependencies(
Expand Down Expand Up @@ -213,7 +203,7 @@ public actor GoTrueClient {
email: email,
password: password,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:)),
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:)),
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
)
Expand Down Expand Up @@ -243,7 +233,7 @@ public actor GoTrueClient {
password: password,
phone: phone,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:))
)
)
)
Expand Down Expand Up @@ -359,7 +349,7 @@ public actor GoTrueClient {
email: email,
createUser: shouldCreateUser,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:)),
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:)),
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
)
Expand Down Expand Up @@ -391,7 +381,7 @@ public actor GoTrueClient {
phone: phone,
createUser: shouldCreateUser,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -401,7 +391,7 @@ public actor GoTrueClient {
/// Log in an existing user by exchanging an Auth Code issued during the PKCE flow.
public func exchangeCodeForSession(authCode: String) async throws -> Session {
guard let codeVerifier = try codeVerifierStorage.getCodeVerifier() else {
throw GoTrueError.pkce(.codeVerifierNotFound)
throw AuthError.pkce(.codeVerifierNotFound)
}
do {
let session: Session = try await api.execute(
Expand Down Expand Up @@ -482,26 +472,26 @@ public actor GoTrueClient {
@discardableResult
public func session(from url: URL) async throws -> Session {
if configuration.flowType == .implicit, !isImplicitGrantFlow(url: url) {
throw GoTrueError.invalidImplicitGrantFlowURL
throw AuthError.invalidImplicitGrantFlowURL
}

if configuration.flowType == .pkce, !isPKCEFlow(url: url) {
throw GoTrueError.pkce(.invalidPKCEFlowURL)
throw AuthError.pkce(.invalidPKCEFlowURL)
}

let params = extractParams(from: url)

if isPKCEFlow(url: url) {
guard let code = params.first(where: { $0.name == "code" })?.value else {
throw GoTrueError.pkce(.codeVerifierNotFound)
throw AuthError.pkce(.codeVerifierNotFound)
}

let session = try await exchangeCodeForSession(authCode: code)
return session
}

if let errorDescription = params.first(where: { $0.name == "error_description" })?.value {
throw GoTrueError.api(.init(errorDescription: errorDescription))
throw AuthError.api(.init(errorDescription: errorDescription))
}

guard
Expand Down Expand Up @@ -565,7 +555,7 @@ public actor GoTrueClient {
expiresAt = Date(timeIntervalSince1970: exp)
hasExpired = expiresAt <= now
} else {
throw GoTrueError.missingExpClaim
throw AuthError.missingExpClaim
}

if hasExpired {
Expand Down Expand Up @@ -607,7 +597,7 @@ public actor GoTrueClient {
// ignore 401s since an invalid or expired JWT should sign out the current session
let ignoredCodes = Set([404, 401])

if case let GoTrueError.api(apiError) = error, let code = apiError.code,
if case let AuthError.api(apiError) = error, let code = apiError.code,
!ignoredCodes.contains(code)
{
throw error
Expand Down Expand Up @@ -642,7 +632,7 @@ public actor GoTrueClient {
email: email,
token: token,
type: type,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -669,7 +659,7 @@ public actor GoTrueClient {
phone: phone,
token: token,
type: type,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:))
)
)
)
Expand Down Expand Up @@ -755,7 +745,7 @@ public actor GoTrueClient {
body: configuration.encoder.encode(
RecoverParams(
email: email,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:)),
gotrueMetaSecurity: captchaToken.map(AuthMetaSecurity.init(captchaToken:)),
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
)
Expand Down Expand Up @@ -841,17 +831,17 @@ public actor GoTrueClient {
}
}

extension GoTrueClient {
extension AuthClient {
/// Notification posted when an auth state event is triggered.
public static let didChangeAuthStateNotification = Notification.Name(
"GoTrueClient.didChangeAuthStateNotification"
"AuthClient.didChangeAuthStateNotification"
)

/// A user info key to retrieve the ``AuthChangeEvent`` value for a
/// ``GoTrueClient/didChangeAuthStateNotification`` notification.
public static let authChangeEventInfoKey = "GoTrueClient.authChangeEvent"
/// ``AuthClient/didChangeAuthStateNotification`` notification.
public static let authChangeEventInfoKey = "AuthClient.authChangeEvent"

/// A user info key to retrieve the ``Session`` value for a
/// ``GoTrueClient/didChangeAuthStateNotification`` notification.
public static let authChangeSessionInfoKey = "GoTrueClient.authChangeSession"
/// ``AuthClient/didChangeAuthStateNotification`` notification.
public static let authChangeSessionInfoKey = "AuthClient.authChangeSession"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public enum GoTrueError: LocalizedError, Sendable {
public enum AuthError: LocalizedError, Sendable {
case missingExpClaim
case malformedJWT
case sessionNotFound
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Foundation
@preconcurrency import KeychainAccess

public protocol GoTrueLocalStorage: Sendable {
public protocol AuthLocalStorage: Sendable {
func store(key: String, value: Data) throws
func retrieve(key: String) throws -> Data?
func remove(key: String) throws
}

struct KeychainLocalStorage: GoTrueLocalStorage {
struct KeychainLocalStorage: AuthLocalStorage {
private let keychain: Keychain

init(service: String, accessGroup: String?) {
Expand Down
6 changes: 3 additions & 3 deletions Sources/GoTrue/GoTrueMFA.swift → Sources/Auth/AuthMFA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
@_spi(Internal) import _Helpers

/// Contains the full multi-factor authentication API.
public actor GoTrueMFA {
public actor AuthMFA {
private var api: APIClient {
Dependencies.current.value!.api
}
Expand All @@ -11,7 +11,7 @@ public actor GoTrueMFA {
Dependencies.current.value!.sessionManager
}

private var configuration: GoTrueClient.Configuration {
private var configuration: AuthClient.Configuration {
Dependencies.current.value!.configuration
}

Expand Down Expand Up @@ -148,7 +148,7 @@ public actor GoTrueMFA {
nextLevel: nextLevel,
currentAuthenticationMethods: currentAuthenticationMethods
)
} catch GoTrueError.sessionNotFound {
} catch AuthError.sessionNotFound {
return AuthMFAGetAuthenticatorAssuranceLevelResponse(
currentLevel: nil,
nextLevel: nil,
Expand Down
Loading

0 comments on commit 036c03d

Please sign in to comment.