diff --git a/Sources/OAuthenticator/Authenticator.swift b/Sources/OAuthenticator/Authenticator.swift index c6dbe0b..6815d99 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 actor Authenticator { +public actor Authenticator { public typealias UserAuthenticator = @Sendable (URL, String) async throws -> URL public typealias AuthenticationStatusHandler = (Result) -> Void @@ -85,32 +85,32 @@ public actor 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`. - @available(*, deprecated, message: "Please move to URLSession.defaultProvider") - @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) @@ -146,13 +146,13 @@ public actor 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. @@ -161,6 +161,15 @@ public actor Authenticator { } } +/// A default `URLSession`-backed `URLResponseProvider`. + @available(*, deprecated, message: "Please move to URLSession.defaultProvider") +@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 { @@ -256,7 +265,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) @@ -276,7 +285,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) @@ -285,7 +294,7 @@ extension Authenticator { } extension Authenticator { - public nonisolated var responseProvider: URLResponseProvider { + public nonisolated var responseProvider: URLUserDataProvider { { try await self.response(for: $0) } } } diff --git a/Sources/OAuthenticator/Models.swift b/Sources/OAuthenticator/Models.swift index 5945b3c..f6e206c 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 = @Sendable (URLRequest) async throws -> (Data, URLResponse) +public typealias URLUserDataProvider = @Sendable (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 = @Sendable (AppCredentials) throws -> URL public typealias LoginProvider = @Sendable (URL, AppCredentials, URL, URLResponseProvider) async throws -> Login public typealias RefreshProvider = @Sendable (Login, AppCredentials, URLResponseProvider) async throws -> Login - public typealias ResponseStatusProvider = @Sendable ((Data, URLResponse)) throws -> ResponseStatus + public typealias ResponseStatusProvider = @Sendable ((any Sendable, URLResponse)) throws -> ResponseStatus public let authorizationURLProvider: AuthorizationURLProvider public let loginProvider: LoginProvider @@ -115,12 +116,12 @@ public struct TokenHandling { } @Sendable - public static func allResponsesValid(result: (Data, URLResponse)) throws -> ResponseStatus { + public static func allResponsesValid(result: (UserDataType, URLResponse)) throws -> ResponseStatus { return .valid } @Sendable - 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 6142739..c33f210 100644 --- a/Tests/OAuthenticatorTests/AuthenticatorTests.swift +++ b/Tests/OAuthenticatorTests/AuthenticatorTests.swift @@ -101,7 +101,7 @@ final class AuthenticatorTests: XCTestCase { storeTokenExp.fulfill() } - let config = Authenticator.Configuration( + let config = Authenticator.Configuration( appCredentials: Self.mockCredentials, loginStorage: storage, // loginStorage: nil, @@ -148,10 +148,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) @@ -200,10 +200,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) @@ -235,10 +235,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 @@ -301,11 +301,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 @@ -357,11 +357,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 { @@ -408,10 +408,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) @@ -468,10 +468,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