diff --git a/Sources/OAuthenticator/Authenticator.swift b/Sources/OAuthenticator/Authenticator.swift index 96d9060..9d06c26 100644 --- a/Sources/OAuthenticator/Authenticator.swift +++ b/Sources/OAuthenticator/Authenticator.swift @@ -18,7 +18,7 @@ public enum AuthenticatorError: Error { } /// Manage state required to executed authenticated URLRequests. -public final class Authenticator { +public final class Authenticator { public typealias UserAuthenticator = (URL, String) async throws -> URL public typealias AuthenticationStatusHandler = (Result) -> Void @@ -81,31 +81,32 @@ public final class Authenticator { let config: Configuration - let urlLoader: URLResponseProvider + let responseLoader: URLResponseProvider + let userDataLoader: URLUserDataProvider private var activeTokenTask: Task? private var localLogin: Login? - public init(config: Configuration, urlLoader loader: URLResponseProvider? = nil) { + public init(config: Configuration, responseLoader: URLResponseProvider? = nil, userDataLoader: @escaping URLUserDataProvider) { self.config = config - self.urlLoader = loader ?? URLSession.defaultProvider + self.responseLoader = responseLoader ?? URLSession.defaultProvider + self.userDataLoader = userDataLoader } - /// A default `URLSession`-backed `URLResponseProvider`. - @MainActor - public static let defaultResponseProvider: URLResponseProvider = { - let session = URLSession(configuration: .default) + public init(config: Configuration, urlLoader: URLResponseProvider? = nil) where UserDataType == Data { + self.config = config - return session.responseProvider - }() + self.responseLoader = urlLoader ?? URLSession.defaultProvider + self.userDataLoader = urlLoader ?? URLSession.defaultProvider + } /// Add authentication for `request`, execute it, and return its result. - public func response(for request: URLRequest) async throws -> (Data, URLResponse) { + public func response(for request: URLRequest) async throws -> (UserDataType, URLResponse) { let userAuthenticator = config.userAuthenticator let login = try await loginTaskResult(manual: false, userAuthenticator: userAuthenticator) - let result = try await authedResponse(for: request, login: login) + let result: (UserDataType, URLResponse) = try await authedResponse(for: request, login: login) let action = try config.tokenHandling.responseStatusProvider(result) @@ -141,13 +142,13 @@ public final class Authenticator { } } - private func authedResponse(for request: URLRequest, login: Login) async throws -> (Data, URLResponse) { + private func authedResponse(for request: URLRequest, login: Login) async throws -> (UserDataType, URLResponse) { var authedRequest = request let token = login.accessToken.value authedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - return try await urlLoader(authedRequest) + return try await userDataLoader(authedRequest) } /// Manually perform user authentication, if required. @@ -157,6 +158,14 @@ public final class Authenticator { } } +/// A default `URLSession`-backed `URLResponseProvider`. +@MainActor +public let defaultAuthenticatorResponseProvider: URLResponseProvider = { + let session = URLSession(configuration: .default) + + return session.responseProvider +}() + extension Authenticator { private func retrieveLogin() async throws -> Login? { guard let storage = config.loginStorage else { @@ -252,7 +261,7 @@ extension Authenticator { let scheme = try config.appCredentials.callbackURLScheme let url = try await userAuthenticator(codeURL, scheme) - let login = try await config.tokenHandling.loginProvider(url, config.appCredentials, codeURL, urlLoader) + let login = try await config.tokenHandling.loginProvider(url, config.appCredentials, codeURL, responseLoader) try await storeLogin(login) @@ -272,7 +281,7 @@ extension Authenticator { return nil } - let login = try await refreshProvider(login, config.appCredentials, urlLoader) + let login = try await refreshProvider(login, config.appCredentials, responseLoader) try await storeLogin(login) @@ -281,7 +290,7 @@ extension Authenticator { } extension Authenticator { - public var responseProvider: URLResponseProvider { + public var responseProvider: URLUserDataProvider { { try await self.response(for: $0) } } } diff --git a/Sources/OAuthenticator/Models.swift b/Sources/OAuthenticator/Models.swift index bb8b0e4..295307f 100644 --- a/Sources/OAuthenticator/Models.swift +++ b/Sources/OAuthenticator/Models.swift @@ -5,6 +5,7 @@ import Foundation /// This is used to abstract the actual networking system from the underlying authentication /// mechanism. public typealias URLResponseProvider = (URLRequest) async throws -> (Data, URLResponse) +public typealias URLUserDataProvider = (URLRequest) async throws -> (UserDataType, URLResponse) public struct Token: Codable, Hashable, Sendable { public let value: String @@ -97,7 +98,7 @@ public struct TokenHandling { public typealias AuthorizationURLProvider = (AppCredentials) throws -> URL public typealias LoginProvider = (URL, AppCredentials, URL, URLResponseProvider) async throws -> Login public typealias RefreshProvider = (Login, AppCredentials, URLResponseProvider) async throws -> Login - public typealias ResponseStatusProvider = ((Data, URLResponse)) throws -> ResponseStatus + public typealias ResponseStatusProvider = ((any Sendable, URLResponse)) throws -> ResponseStatus public let authorizationURLProvider: AuthorizationURLProvider public let loginProvider: LoginProvider @@ -114,11 +115,11 @@ public struct TokenHandling { self.responseStatusProvider = responseStatusProvider } - public static func allResponsesValid(result: (Data, URLResponse)) throws -> ResponseStatus { + public static func allResponsesValid(result: (UserDataType, URLResponse)) throws -> ResponseStatus { return .valid } - public static func refreshOrAuthorizeWhenUnauthorized(result: (Data, URLResponse)) throws -> ResponseStatus { + public static func refreshOrAuthorizeWhenUnauthorized(result: (UserDataType, URLResponse)) throws -> ResponseStatus { guard let response = result.1 as? HTTPURLResponse else { throw AuthenticatorError.httpResponseExpected } diff --git a/Tests/OAuthenticatorTests/AuthenticatorTests.swift b/Tests/OAuthenticatorTests/AuthenticatorTests.swift index 9af8e08..ace6319 100644 --- a/Tests/OAuthenticatorTests/AuthenticatorTests.swift +++ b/Tests/OAuthenticatorTests/AuthenticatorTests.swift @@ -96,10 +96,10 @@ final class AuthenticatorTests: XCTestCase { storeTokenExp.fulfill() } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - loginStorage: storage, - tokenHandling: tokenHandling, - userAuthenticator: mockUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + loginStorage: storage, + tokenHandling: tokenHandling, + userAuthenticator: mockUserAuthenticator) let auth = Authenticator(config: config, urlLoader: mockLoader) @@ -132,10 +132,10 @@ final class AuthenticatorTests: XCTestCase { XCTFail() } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - loginStorage: storage, - tokenHandling: tokenHandling, - userAuthenticator: Self.disabledUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + loginStorage: storage, + tokenHandling: tokenHandling, + userAuthenticator: Self.disabledUserAuthenticator) let auth = Authenticator(config: config, urlLoader: mockLoader) @@ -184,10 +184,10 @@ final class AuthenticatorTests: XCTestCase { XCTAssertEqual(login.accessToken.value, "REFRESHED") } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - loginStorage: storage, - tokenHandling: tokenHandling, - userAuthenticator: Self.disabledUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + loginStorage: storage, + tokenHandling: tokenHandling, + userAuthenticator: Self.disabledUserAuthenticator) let auth = Authenticator(config: config, urlLoader: mockLoader) @@ -219,10 +219,10 @@ final class AuthenticatorTests: XCTestCase { return URL(string: "my://login")! } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - tokenHandling: tokenHandling, - mode: .manualOnly, - userAuthenticator: mockUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + tokenHandling: tokenHandling, + mode: .manualOnly, + userAuthenticator: mockUserAuthenticator) let loadExp = expectation(description: "load url") let mockLoader: URLResponseProvider = { request in @@ -286,11 +286,11 @@ final class AuthenticatorTests: XCTestCase { } // Configure Authenticator with result callback - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - tokenHandling: tokenHandling, - mode: .manualOnly, - userAuthenticator: mockUserAuthenticator, - authenticationStatusHandler: authenticationCallback) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + tokenHandling: tokenHandling, + mode: .manualOnly, + userAuthenticator: mockUserAuthenticator, + authenticationStatusHandler: authenticationCallback) let loadExp = expectation(description: "load url") let mockLoader: URLResponseProvider = { request in @@ -343,11 +343,11 @@ final class AuthenticatorTests: XCTestCase { } // Configure Authenticator with result callback - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - tokenHandling: tokenHandling, - mode: .manualOnly, - userAuthenticator: Authenticator.failingUserAuthenticator, - authenticationStatusHandler: authenticationCallback) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + tokenHandling: tokenHandling, + mode: .manualOnly, + userAuthenticator: Authenticator.failingUserAuthenticator, + authenticationStatusHandler: authenticationCallback) let auth = Authenticator(config: config, urlLoader: nil) do { @@ -395,10 +395,10 @@ final class AuthenticatorTests: XCTestCase { XCTAssertEqual(login.accessToken.value, "REFRESHED") } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - loginStorage: storage, - tokenHandling: tokenHandling, - userAuthenticator: Self.disabledUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + loginStorage: storage, + tokenHandling: tokenHandling, + userAuthenticator: Self.disabledUserAuthenticator) let auth = Authenticator(config: config, urlLoader: mockLoader.responseProvider) @@ -453,10 +453,10 @@ final class AuthenticatorTests: XCTestCase { savedLogins.append(login) } - let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, - loginStorage: storage, - tokenHandling: tokenHandling, - userAuthenticator: Self.disabledUserAuthenticator) + let config = Authenticator.Configuration(appCredentials: Self.mockCredentials, + loginStorage: storage, + tokenHandling: tokenHandling, + userAuthenticator: Self.disabledUserAuthenticator) let auth = Authenticator(config: config, urlLoader: mockLoader) diff --git a/Tests/OAuthenticatorTests/GoogleTests.swift b/Tests/OAuthenticatorTests/GoogleTests.swift index 0211823..d0607fb 100644 --- a/Tests/OAuthenticatorTests/GoogleTests.swift +++ b/Tests/OAuthenticatorTests/GoogleTests.swift @@ -44,10 +44,10 @@ final class GoogleTests: XCTestCase { let creds = AppCredentials(clientId: "client_id", clientPassword: "client_pwd", scopes: ["scope1", "scope2"], callbackURL: callback!) let tokenHandling = GoogleAPI.googleAPITokenHandling(with: googleParameters) - let config = Authenticator.Configuration( + let config = Authenticator.Configuration( appCredentials: creds, tokenHandling: tokenHandling, - userAuthenticator: Authenticator.failingUserAuthenticator + userAuthenticator: Authenticator.failingUserAuthenticator ) // Validate URL is properly constructed @@ -76,10 +76,10 @@ final class GoogleTests: XCTestCase { let creds = AppCredentials(clientId: "client_id", clientPassword: "client_pwd", scopes: ["scope1", "scope2"], callbackURL: callback!) let tokenHandling = GoogleAPI.googleAPITokenHandling(with: googleParameters) - let config = Authenticator.Configuration( + let config = Authenticator.Configuration( appCredentials: creds, tokenHandling: tokenHandling, - userAuthenticator: Authenticator.failingUserAuthenticator + userAuthenticator: Authenticator.failingUserAuthenticator ) // Validate URL is properly constructed