Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preparation for v2 #187

Merged
merged 6 commits into from
Dec 14, 2023
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
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
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