From 452b97ad075df80c2f3e5370e3a9c404daf52b4e Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 00:23:38 +0200 Subject: [PATCH 01/15] Create OAuthClient --- Data/OAuthClient/Sources/OAuthClient.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Data/OAuthClient/Sources/OAuthClient.swift diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift new file mode 100644 index 00000000..88495601 --- /dev/null +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -0,0 +1,18 @@ +// +// File.swift +// QuranEngine +// +// Created by Mohannad Hassan on 19/12/2024. +// + +import Foundation + + +// TODO: Need to add functions for authenticating the requests and getting the profile information. +public final protocol OAuthClient { + + public func set(clientID: String) + + // TODO: May return the profile information + public func login(on viewController: UIViewController) async throws +} From 9979b233fc0d234ac0f9baa895585aa7c81f047d Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 00:31:10 +0200 Subject: [PATCH 02/15] Create AppAuthOAuthClient --- .../Sources/AppAuthOAuthClient.swift | 20 +++++++++++++++++++ Data/OAuthClient/Sources/OAuthClient.swift | 8 ++++---- 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 Data/OAuthClient/Sources/AppAuthOAuthClient.swift diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift new file mode 100644 index 00000000..e0e48c77 --- /dev/null +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -0,0 +1,20 @@ +// +// File.swift +// QuranEngine +// +// Created by Mohannad Hassan on 23/12/2024. +// + +import Foundation +import UIKit + +public class AppAuthOAuthClient: OAuthClient { + + public func set(clientID: String) { + + } + + public func login(on viewController: UIViewController) async throws { + + } +} diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift index 88495601..af5a028b 100644 --- a/Data/OAuthClient/Sources/OAuthClient.swift +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -6,13 +6,13 @@ // import Foundation - +import UIKit // TODO: Need to add functions for authenticating the requests and getting the profile information. -public final protocol OAuthClient { +public protocol OAuthClient { - public func set(clientID: String) + func set(clientID: String) // TODO: May return the profile information - public func login(on viewController: UIViewController) async throws + func login(on viewController: UIViewController) async throws } From 0b485deacf281fcf56cda9b17f0ad5689a270453 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 02:26:38 +0200 Subject: [PATCH 03/15] Add AppAuth dependency and OAuthClient package --- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ Package.swift | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/Example/QuranEngineApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/QuranEngineApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0307a7d7..b5d7d114 100644 --- a/Example/QuranEngineApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/QuranEngineApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "appauth-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/openid/AppAuth-iOS.git", + "state" : { + "revision" : "2781038865a80e2c425a1da12cc1327bcd56501f", + "version" : "1.7.6" + } + }, { "identity" : "combine-schedulers", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index cda443d3..a8882d15 100644 --- a/Package.swift +++ b/Package.swift @@ -47,6 +47,9 @@ let package = Package( // Async .package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"), + + // OAuth + .package(url: "https://github.com/openid/AppAuth-iOS", .upToNextMajor(from: "1.3.0")), // UI .package(url: "https://github.com/GenericDataSource/GenericDataSource", from: "3.1.3"), @@ -295,6 +298,11 @@ private func dataTargets() -> [[Target]] { "BatchDownloader", "NetworkSupportFake", ]), + + // MARK: - Quran.com OAuth + target(type, name: "OAuthClient", hasTests: false, dependencies: [ + .product(name: "AppAuth", package: "AppAuth-iOS"), + ]) ] } From 9ff61f51ec6369dc0058c5d34df6652b9e09fbe2 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 02:37:09 +0200 Subject: [PATCH 04/15] Implement main steps in AppAuthOAuthClient --- .../Sources/AppAuthOAuthClient.swift | 67 +++++++++++++++++-- Data/OAuthClient/Sources/OAuthClient.swift | 18 ++++- 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index e0e48c77..4c94877d 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -7,14 +7,71 @@ import Foundation import UIKit +import AppAuth public class AppAuthOAuthClient: OAuthClient { - - public func set(clientID: String) { - + + private var authFlow: (any OIDExternalUserAgentSession)? + private var appConfiguration: OAuthAppConfiguration? + + public func set(appConfiguration: OAuthAppConfiguration) { + self.appConfiguration = appConfiguration } - + public func login(on viewController: UIViewController) async throws { - + guard let configuration = self.appConfiguration else { + throw OAuthClientError.oauthClientHasNotBeenSet + } + + let serviceConfiguration = try await discoverConfiguration(forIssuer: configuration.authorizationHost) + try await login(withConfiguration: serviceConfiguration, + appConfiguration: configuration, + on: viewController) + } + + private func discoverConfiguration(forIssuer issuer: URL) async throws -> OIDServiceConfiguration { + try await withCheckedThrowingContinuation { continuation in + OIDAuthorizationService + .discoverConfiguration(forIssuer: issuer) { configuration, error in + guard error != nil else { + continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(error)) + return + } + guard let configuration = configuration else { + // This should not happen + continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(nil)) + return + } + continuation.resume(returning: configuration) + } + } + } + + private func login(withConfiguration configuration: OIDServiceConfiguration, + appConfiguration: OAuthAppConfiguration, + on viewController: UIViewController) async throws { + let scopes = [OIDScopeOpenID, OIDScopeProfile] + appConfiguration.scopes + let request = OIDAuthorizationRequest(configuration: configuration, + clientId: appConfiguration.clientID, + clientSecret: nil, + scopes: scopes, + redirectURL: appConfiguration.redirectURL, + responseType: OIDResponseTypeCode, + additionalParameters: [:]) + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + self.authFlow = OIDAuthState.authState(byPresenting: request, + presenting: viewController) { state, error in + guard error == nil else { + continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) + return + } + guard let state = state else { + continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) + return + } + continuation.resume() + } + } } } diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift index af5a028b..df5feaee 100644 --- a/Data/OAuthClient/Sources/OAuthClient.swift +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -8,11 +8,23 @@ import Foundation import UIKit -// TODO: Need to add functions for authenticating the requests and getting the profile information. +public enum OAuthClientError: Error { + case oauthClientHasNotBeenSet + case errorFetchingConfiguration(Error?) + case errorAuthenticating(Error?) +} + +public struct OAuthAppConfiguration { + public let clientID: String + public let redirectURL: URL + public let scopes: [String] + public let authorizationHost: URL +} + public protocol OAuthClient { - func set(clientID: String) - + func set(appConfiguration: OAuthAppConfiguration) + // TODO: May return the profile information func login(on viewController: UIViewController) async throws } From bd080f4a8cebc8d612957dd00c63a8b9575840b8 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 02:48:20 +0200 Subject: [PATCH 05/15] Create QuranProfileService package --- .../Sources/AppAuthOAuthClient.swift | 5 +++- Data/OAuthClient/Sources/OAuthClient.swift | 7 ++++++ .../Sources/QuranProfileService.swift | 23 +++++++++++++++++++ Package.swift | 4 ++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Domain/QuranProfileService/Sources/QuranProfileService.swift diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index 4c94877d..dad50179 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -9,11 +9,14 @@ import Foundation import UIKit import AppAuth -public class AppAuthOAuthClient: OAuthClient { +public final class AppAuthOAuthClient: OAuthClient { + // TODO: Do we need to maintain that? private var authFlow: (any OIDExternalUserAgentSession)? private var appConfiguration: OAuthAppConfiguration? + public init() {} + public func set(appConfiguration: OAuthAppConfiguration) { self.appConfiguration = appConfiguration } diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift index df5feaee..0de6ac19 100644 --- a/Data/OAuthClient/Sources/OAuthClient.swift +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -19,6 +19,13 @@ public struct OAuthAppConfiguration { public let redirectURL: URL public let scopes: [String] public let authorizationHost: URL + + public init(clientID: String, redirectURL: URL, scopes: [String], authorizationHost: URL) { + self.clientID = clientID + self.redirectURL = redirectURL + self.scopes = scopes + self.authorizationHost = authorizationHost + } } public protocol OAuthClient { diff --git a/Domain/QuranProfileService/Sources/QuranProfileService.swift b/Domain/QuranProfileService/Sources/QuranProfileService.swift new file mode 100644 index 00000000..6191d138 --- /dev/null +++ b/Domain/QuranProfileService/Sources/QuranProfileService.swift @@ -0,0 +1,23 @@ +// +// File.swift +// QuranEngine +// +// Created by Mohannad Hassan on 23/12/2024. +// + +import UIKit +import OAuthClient + +public class QuranProfileService { + + private let oauthClient: OAuthClient + + public init(oauthClient: OAuthClient) { + self.oauthClient = oauthClient + } + + public func login(on viewController: UIViewController) async throws { + try await oauthClient.login(on: viewController) + } +} + diff --git a/Package.swift b/Package.swift index a8882d15..5f04c1f8 100644 --- a/Package.swift +++ b/Package.swift @@ -466,6 +466,10 @@ private func domainTargets() -> [[Target]] { "Utilities", ]), + + target(type, name: "QuranProfileService", hasTests: false, dependencies: [ + "OAuthClient", + ]) ] } From a2d0cdbdfed098187f5e85248544a0b3381e2b85 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 04:52:46 +0200 Subject: [PATCH 06/15] Register OAuthClient in Container --- Example/QuranEngineApp/Classes/Container.swift | 15 +++++++++++++++ Features/AppDependencies/AppDependencies.swift | 3 +++ 2 files changed, 18 insertions(+) diff --git a/Example/QuranEngineApp/Classes/Container.swift b/Example/QuranEngineApp/Classes/Container.swift index 8b873f47..85e3d12d 100644 --- a/Example/QuranEngineApp/Classes/Container.swift +++ b/Example/QuranEngineApp/Classes/Container.swift @@ -15,6 +15,7 @@ import LastPagePersistence import NotePersistence import PageBookmarkPersistence import ReadingService +import OAuthClient import UIKit /// Hosts singleton dependencies @@ -35,6 +36,13 @@ class Container: AppDependencies { private(set) lazy var lastPagePersistence: LastPagePersistence = CoreDataLastPagePersistence(stack: coreDataStack) private(set) lazy var pageBookmarkPersistence: PageBookmarkPersistence = CoreDataPageBookmarkPersistence(stack: coreDataStack) private(set) lazy var notePersistence: NotePersistence = CoreDataNotePersistence(stack: coreDataStack) + private(set) lazy var oauthClient: any OAuthClient = { + let client = AppAuthOAuthClient() + if let config = Constant.QuranOAuthAppConfigurations { + client.set(appConfiguration: config) + } + return client + }() private(set) lazy var downloadManager: DownloadManager = { let configuration = URLSessionConfiguration.background(withIdentifier: "DownloadsBackgroundIdentifier") @@ -78,4 +86,11 @@ private enum Constant { static let databasesURL = FileManager.documentsURL .appendingPathComponent("databases", isDirectory: true) + + static let QuranOAuthAppConfigurations: OAuthAppConfiguration? = OAuthAppConfiguration( + clientID: "954eb549-3566-4f9a-b65f-fa61bf9a9e37", + redirectURL: URL(validURL: "com.example.app:/oauth2redirect/example-provider"), + scopes: [], + authorizationHost: URL(validURL: "https://staging-oauth2.quran.foundation") + ) } diff --git a/Features/AppDependencies/AppDependencies.swift b/Features/AppDependencies/AppDependencies.swift index dbed97bc..a6b9f134 100644 --- a/Features/AppDependencies/AppDependencies.swift +++ b/Features/AppDependencies/AppDependencies.swift @@ -15,6 +15,7 @@ import PageBookmarkPersistence import QuranResources import QuranTextKit import ReadingService +import OAuthClient public protocol AppDependencies { var databasesURL: URL { get } @@ -35,6 +36,8 @@ public protocol AppDependencies { var lastPagePersistence: LastPagePersistence { get } var notePersistence: NotePersistence { get } var pageBookmarkPersistence: PageBookmarkPersistence { get } + + var oauthClient: OAuthClient { get } } extension AppDependencies { From ad298de9efb78e8a8b07560439c20e495506f225 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 09:20:12 +0200 Subject: [PATCH 07/15] Perform login operation in SettingsFeature --- Features/SettingsFeature/SettingsBuilder.swift | 2 ++ Features/SettingsFeature/SettingsRootView.swift | 14 ++++++++++++-- .../SettingsFeature/SettingsRootViewModel.swift | 16 ++++++++++++++++ Package.swift | 2 ++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Features/SettingsFeature/SettingsBuilder.swift b/Features/SettingsFeature/SettingsBuilder.swift index d78a4444..23da491b 100644 --- a/Features/SettingsFeature/SettingsBuilder.swift +++ b/Features/SettingsFeature/SettingsBuilder.swift @@ -11,6 +11,7 @@ import AudioDownloadsFeature import Localization import ReadingSelectorFeature import SettingsService +import QuranProfileService import SwiftUI import TranslationsFeature import UIKit @@ -29,6 +30,7 @@ public struct SettingsBuilder { let viewModel = SettingsRootViewModel( analytics: container.analytics, reviewService: ReviewService(analytics: container.analytics), + quranProfileService: QuranProfileService(oauthClient: container.oauthClient), audioDownloadsBuilder: AudioDownloadsBuilder(container: container), translationsListBuilder: TranslationsListBuilder(container: container), readingSelectorBuilder: ReadingSelectorBuilder(container: container), diff --git a/Features/SettingsFeature/SettingsRootView.swift b/Features/SettingsFeature/SettingsRootView.swift index ab96076e..7cb077f2 100644 --- a/Features/SettingsFeature/SettingsRootView.swift +++ b/Features/SettingsFeature/SettingsRootView.swift @@ -25,7 +25,8 @@ struct SettingsRootView: View { shareApp: { viewModel.shareApp() }, writeReview: { viewModel.writeReview() }, contactUs: { viewModel.contactUs() }, - navigateToDiagnotics: { viewModel.navigateToDiagnotics() } + navigateToDiagnotics: { viewModel.navigateToDiagnotics() }, + loginAction: { await viewModel.loginToQuranCom() } ) } } @@ -41,6 +42,7 @@ private struct SettingsRootViewUI: View { let writeReview: AsyncAction let contactUs: AsyncAction let navigateToDiagnotics: AsyncAction + let loginAction: AsyncAction var body: some View { NoorList { @@ -108,6 +110,13 @@ private struct SettingsRootViewUI: View { ) } + NoorBasicSection { + NoorListItem( + title: .text(l("Login with Quran.com")), + action: loginAction + ) + } + NoorBasicSection { NoorListItem( image: .init(.debug), @@ -135,7 +144,8 @@ struct SettingsRootView_Previews: PreviewProvider { shareApp: {}, writeReview: {}, contactUs: {}, - navigateToDiagnotics: {} + navigateToDiagnotics: {}, + loginAction: {} ) } } diff --git a/Features/SettingsFeature/SettingsRootViewModel.swift b/Features/SettingsFeature/SettingsRootViewModel.swift index b5fdc195..9fcb39ff 100644 --- a/Features/SettingsFeature/SettingsRootViewModel.swift +++ b/Features/SettingsFeature/SettingsRootViewModel.swift @@ -14,6 +14,7 @@ import QuranAudio import QuranAudioKit import ReadingSelectorFeature import SettingsService +import QuranProfileService import TranslationsFeature import UIKit import UIx @@ -26,6 +27,7 @@ final class SettingsRootViewModel: ObservableObject { init( analytics: AnalyticsLibrary, reviewService: ReviewService, + quranProfileService: QuranProfileService, audioDownloadsBuilder: AudioDownloadsBuilder, translationsListBuilder: TranslationsListBuilder, readingSelectorBuilder: ReadingSelectorBuilder, @@ -36,6 +38,7 @@ final class SettingsRootViewModel: ObservableObject { audioEnd = audioPreferences.audioEnd self.analytics = analytics self.reviewService = reviewService + self.quranProfileService = quranProfileService self.audioDownloadsBuilder = audioDownloadsBuilder self.translationsListBuilder = translationsListBuilder self.readingSelectorBuilder = readingSelectorBuilder @@ -50,6 +53,7 @@ final class SettingsRootViewModel: ObservableObject { let analytics: AnalyticsLibrary let reviewService: ReviewService + private let quranProfileService: QuranProfileService let audioDownloadsBuilder: AudioDownloadsBuilder let translationsListBuilder: TranslationsListBuilder let readingSelectorBuilder: ReadingSelectorBuilder @@ -128,6 +132,18 @@ final class SettingsRootViewModel: ObservableObject { navigationController?.pushViewController(viewController, animated: true) } + func loginToQuranCom() async { + guard let viewController = navigationController else { + return + } + do { + try await self.quranProfileService.login(on: viewController) + print("Login seems successful") + } catch { + logger.error("Failed to login to Quran.com: \(error)") + } + } + // MARK: Private private func showSingleChoiceSelector( diff --git a/Package.swift b/Package.swift index 5f04c1f8..65a4ae77 100644 --- a/Package.swift +++ b/Package.swift @@ -485,6 +485,7 @@ private func featuresTargets() -> [[Target]] { "LastPagePersistence", "ReadingService", "QuranResources", + "OAuthClient", ]), target(type, name: "FeaturesSupport", hasTests: false, dependencies: [ @@ -668,6 +669,7 @@ private func featuresTargets() -> [[Target]] { "ReadingSelectorFeature", "Preferences", "Zip", + "QuranProfileService", ]), target(type, name: "AppStructureFeature", hasTests: false, dependencies: [ From 7172377eb2c5481efbc55f77d2ca59c9297680cf Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Mon, 23 Dec 2024 09:20:44 +0200 Subject: [PATCH 08/15] Perform web authorization call on main thread for BG main thread runtiem warning --- .../Sources/AppAuthOAuthClient.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index dad50179..2e3524b5 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -36,7 +36,7 @@ public final class AppAuthOAuthClient: OAuthClient { try await withCheckedThrowingContinuation { continuation in OIDAuthorizationService .discoverConfiguration(forIssuer: issuer) { configuration, error in - guard error != nil else { + guard error == nil else { continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(error)) return } @@ -63,8 +63,7 @@ public final class AppAuthOAuthClient: OAuthClient { additionalParameters: [:]) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.authFlow = OIDAuthState.authState(byPresenting: request, - presenting: viewController) { state, error in + login(request: request, on: viewController) { state, error in guard error == nil else { continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) return @@ -73,8 +72,22 @@ public final class AppAuthOAuthClient: OAuthClient { continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) return } + print(state) continuation.resume() } } } + + private func login(request: OIDAuthorizationRequest, + on viewController: UIViewController, + callback: @escaping OIDAuthStateAuthorizationCallback) { + Task { + await MainActor.run { + self.authFlow = OIDAuthState.authState(byPresenting: request, + presenting: viewController) { state, error in + callback(state, error) + } + } + } + } } From f5e5900d7ba596e4c8de6fa870aa14e161c1e589 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Tue, 24 Dec 2024 03:26:47 +0200 Subject: [PATCH 09/15] Cleanup and provide documentation --- .../Sources/AppAuthOAuthClient.swift | 17 +++++++++------- Data/OAuthClient/Sources/OAuthClient.swift | 18 ++++++++++++----- .../Sources/QuranProfileService.swift | 4 ++++ .../QuranEngineApp/Classes/Container.swift | 8 ++------ .../SettingsFeature/SettingsRootView.swift | 1 + .../SettingsRootViewModel.swift | 20 +++++++++---------- 6 files changed, 40 insertions(+), 28 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index 2e3524b5..fbcba1c1 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -11,7 +11,7 @@ import AppAuth public final class AppAuthOAuthClient: OAuthClient { - // TODO: Do we need to maintain that? + // Needed mainly for retention. private var authFlow: (any OIDExternalUserAgentSession)? private var appConfiguration: OAuthAppConfiguration? @@ -26,7 +26,8 @@ public final class AppAuthOAuthClient: OAuthClient { throw OAuthClientError.oauthClientHasNotBeenSet } - let serviceConfiguration = try await discoverConfiguration(forIssuer: configuration.authorizationHost) + // Quran.com relies on dicovering the configuration from the issuer, and not using a static configuration. + let serviceConfiguration = try await discoverConfiguration(forIssuer: configuration.authorizationIssuerURL) try await login(withConfiguration: serviceConfiguration, appConfiguration: configuration, on: viewController) @@ -63,7 +64,7 @@ public final class AppAuthOAuthClient: OAuthClient { additionalParameters: [:]) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - login(request: request, on: viewController) { state, error in + fire(loginRequest: request, on: viewController) { state, error in guard error == nil else { continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) return @@ -78,13 +79,15 @@ public final class AppAuthOAuthClient: OAuthClient { } } - private func login(request: OIDAuthorizationRequest, - on viewController: UIViewController, - callback: @escaping OIDAuthStateAuthorizationCallback) { + /// Executes the request on the main actor. + private func fire(loginRequest: OIDAuthorizationRequest, + on viewController: UIViewController, + callback: @escaping OIDAuthStateAuthorizationCallback) { Task { await MainActor.run { - self.authFlow = OIDAuthState.authState(byPresenting: request, + self.authFlow = OIDAuthState.authState(byPresenting: loginRequest, presenting: viewController) { state, error in + self.authFlow = nil callback(state, error) } } diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift index 0de6ac19..2d53433a 100644 --- a/Data/OAuthClient/Sources/OAuthClient.swift +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -17,21 +17,29 @@ public enum OAuthClientError: Error { public struct OAuthAppConfiguration { public let clientID: String public let redirectURL: URL + /// Indicates the Quran.com specific scopes to be requested by the app. + /// The client requests the `offline` and `openid` scopes by default. public let scopes: [String] - public let authorizationHost: URL + public let authorizationIssuerURL: URL - public init(clientID: String, redirectURL: URL, scopes: [String], authorizationHost: URL) { + public init(clientID: String, redirectURL: URL, scopes: [String], authorizationIssuerURL: URL) { self.clientID = clientID self.redirectURL = redirectURL self.scopes = scopes - self.authorizationHost = authorizationHost + self.authorizationIssuerURL = authorizationIssuerURL } } +/// Handles the OAuth flow to Quran.com +/// +/// Note that the connection relies on dicvoering the configuration from the issuer service. public protocol OAuthClient { - + func set(appConfiguration: OAuthAppConfiguration) - // TODO: May return the profile information + /// Performs the login flow to Quran.com + /// + /// - Parameter viewController: The view controller to be used as base for presenting the login flow. + /// - Returns: Nothing is returned for now. The client may return the profile infromation in the future. func login(on viewController: UIViewController) async throws } diff --git a/Domain/QuranProfileService/Sources/QuranProfileService.swift b/Domain/QuranProfileService/Sources/QuranProfileService.swift index 6191d138..7cc0a4df 100644 --- a/Domain/QuranProfileService/Sources/QuranProfileService.swift +++ b/Domain/QuranProfileService/Sources/QuranProfileService.swift @@ -16,6 +16,10 @@ public class QuranProfileService { self.oauthClient = oauthClient } + /// Performs the login flow to Quran.com + /// + /// - Parameter viewController: The view controller to be used as base for presenting the login flow. + /// - Returns: Nothing is returned for now. The client may return the profile infromation in the future. public func login(on viewController: UIViewController) async throws { try await oauthClient.login(on: viewController) } diff --git a/Example/QuranEngineApp/Classes/Container.swift b/Example/QuranEngineApp/Classes/Container.swift index 85e3d12d..8842d5c0 100644 --- a/Example/QuranEngineApp/Classes/Container.swift +++ b/Example/QuranEngineApp/Classes/Container.swift @@ -87,10 +87,6 @@ private enum Constant { static let databasesURL = FileManager.documentsURL .appendingPathComponent("databases", isDirectory: true) - static let QuranOAuthAppConfigurations: OAuthAppConfiguration? = OAuthAppConfiguration( - clientID: "954eb549-3566-4f9a-b65f-fa61bf9a9e37", - redirectURL: URL(validURL: "com.example.app:/oauth2redirect/example-provider"), - scopes: [], - authorizationHost: URL(validURL: "https://staging-oauth2.quran.foundation") - ) + /// If set, the Quran.com login will be enabled. + static let QuranOAuthAppConfigurations: OAuthAppConfiguration? = nil } diff --git a/Features/SettingsFeature/SettingsRootView.swift b/Features/SettingsFeature/SettingsRootView.swift index 7cb077f2..702797b0 100644 --- a/Features/SettingsFeature/SettingsRootView.swift +++ b/Features/SettingsFeature/SettingsRootView.swift @@ -110,6 +110,7 @@ private struct SettingsRootViewUI: View { ) } + // TODO: Pending translations, and hiding if OAuth is not configured. NoorBasicSection { NoorListItem( title: .text(l("Login with Quran.com")), diff --git a/Features/SettingsFeature/SettingsRootViewModel.swift b/Features/SettingsFeature/SettingsRootViewModel.swift index 9fcb39ff..66b4c6b2 100644 --- a/Features/SettingsFeature/SettingsRootViewModel.swift +++ b/Features/SettingsFeature/SettingsRootViewModel.swift @@ -51,19 +51,19 @@ final class SettingsRootViewModel: ObservableObject { // MARK: Internal - let analytics: AnalyticsLibrary - let reviewService: ReviewService + private let analytics: AnalyticsLibrary + private let reviewService: ReviewService private let quranProfileService: QuranProfileService - let audioDownloadsBuilder: AudioDownloadsBuilder - let translationsListBuilder: TranslationsListBuilder - let readingSelectorBuilder: ReadingSelectorBuilder - let diagnosticsBuilder: DiagnosticsBuilder + private let audioDownloadsBuilder: AudioDownloadsBuilder + private let translationsListBuilder: TranslationsListBuilder + private let readingSelectorBuilder: ReadingSelectorBuilder + private let diagnosticsBuilder: DiagnosticsBuilder - let contactUsService = ContactUsService() - let themeService = ThemeService.shared - let audioPreferences = AudioPreferences.shared + private let contactUsService = ContactUsService() + private let themeService = ThemeService.shared + private let audioPreferences = AudioPreferences.shared - weak var navigationController: UINavigationController? + private weak var navigationController: UINavigationController? @Published var audioEnd: AudioEnd From 38efebdee61b1eab2f3d39ac956886f91a645c62 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Tue, 24 Dec 2024 04:53:54 +0200 Subject: [PATCH 10/15] Add some loggigns --- Data/OAuthClient/Sources/AppAuthOAuthClient.swift | 13 +++++++++++-- .../SettingsFeature/SettingsRootViewModel.swift | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index fbcba1c1..e1fda305 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -8,6 +8,7 @@ import Foundation import UIKit import AppAuth +import VLogging public final class AppAuthOAuthClient: OAuthClient { @@ -23,6 +24,7 @@ public final class AppAuthOAuthClient: OAuthClient { public func login(on viewController: UIViewController) async throws { guard let configuration = self.appConfiguration else { + logger.error("login invoked without OAuth client configurations being set") throw OAuthClientError.oauthClientHasNotBeenSet } @@ -34,18 +36,22 @@ public final class AppAuthOAuthClient: OAuthClient { } private func discoverConfiguration(forIssuer issuer: URL) async throws -> OIDServiceConfiguration { - try await withCheckedThrowingContinuation { continuation in + logger.info("Discovering configuration for OAuth") + return try await withCheckedThrowingContinuation { continuation in OIDAuthorizationService .discoverConfiguration(forIssuer: issuer) { configuration, error in guard error == nil else { + logger.error("Error fetching OAuth configuration: \(error!)") continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(error)) return } guard let configuration = configuration else { // This should not happen + logger.error("Error fetching OAuth configuration: no cofniguration was loaded. An unexpected situtation.") continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(nil)) return } + logger.info("OAuth configuration fetched successfully") continuation.resume(returning: configuration) } } @@ -63,17 +69,20 @@ public final class AppAuthOAuthClient: OAuthClient { responseType: OIDResponseTypeCode, additionalParameters: [:]) + logger.info("Starting OAuth flow") try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in fire(loginRequest: request, on: viewController) { state, error in guard error == nil else { + logger.error("Error authenticating: \(error!)") continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) return } guard let state = state else { + logger.error("Error authenticating: no state returned. An unexpected situtation.") continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) return } - print(state) + logger.info("OAuth flow completed successfully") continuation.resume() } } diff --git a/Features/SettingsFeature/SettingsRootViewModel.swift b/Features/SettingsFeature/SettingsRootViewModel.swift index 66b4c6b2..ff33f807 100644 --- a/Features/SettingsFeature/SettingsRootViewModel.swift +++ b/Features/SettingsFeature/SettingsRootViewModel.swift @@ -133,11 +133,13 @@ final class SettingsRootViewModel: ObservableObject { } func loginToQuranCom() async { + logger.info("Settings: Login to Quran.com") guard let viewController = navigationController else { return } do { try await self.quranProfileService.login(on: viewController) + // TODO: Replace with the needed UI changes. print("Login seems successful") } catch { logger.error("Failed to login to Quran.com: \(error)") From 0a7da601de78692d9cf5a045a33668f748a0caaf Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Tue, 24 Dec 2024 09:15:13 +0200 Subject: [PATCH 11/15] Cleanup --- Data/OAuthClient/Sources/AppAuthOAuthClient.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index e1fda305..bb5b31e8 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -28,7 +28,8 @@ public final class AppAuthOAuthClient: OAuthClient { throw OAuthClientError.oauthClientHasNotBeenSet } - // Quran.com relies on dicovering the configuration from the issuer, and not using a static configuration. + // Quran.com relies on dicovering the service configuration from the issuer, + // and not using a static configuration. let serviceConfiguration = try await discoverConfiguration(forIssuer: configuration.authorizationIssuerURL) try await login(withConfiguration: serviceConfiguration, appConfiguration: configuration, From 7825940fe31222643b29dd8e05655e4f8c0b5880 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Tue, 24 Dec 2024 10:07:36 +0200 Subject: [PATCH 12/15] Fix linting issues --- .../Sources/AppAuthOAuthClient.swift | 73 ++++++++++++------- Data/OAuthClient/Sources/OAuthClient.swift | 3 +- .../Sources/QuranProfileService.swift | 6 +- .../QuranEngineApp/Classes/Container.swift | 2 +- .../AppDependencies/AppDependencies.swift | 2 +- .../SettingsFeature/SettingsBuilder.swift | 2 +- .../SettingsFeature/SettingsRootView.swift | 1 + .../SettingsRootViewModel.swift | 26 +++---- Package.swift | 9 ++- 9 files changed, 70 insertions(+), 54 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index bb5b31e8..b17d9a9b 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -1,29 +1,28 @@ // -// File.swift +// AppAuthOAuthClient.swift // QuranEngine // // Created by Mohannad Hassan on 23/12/2024. // +import AppAuth import Foundation import UIKit -import AppAuth import VLogging public final class AppAuthOAuthClient: OAuthClient { - - // Needed mainly for retention. - private var authFlow: (any OIDExternalUserAgentSession)? - private var appConfiguration: OAuthAppConfiguration? + // MARK: Lifecycle public init() {} + // MARK: Public + public func set(appConfiguration: OAuthAppConfiguration) { self.appConfiguration = appConfiguration } public func login(on viewController: UIViewController) async throws { - guard let configuration = self.appConfiguration else { + guard let configuration = appConfiguration else { logger.error("login invoked without OAuth client configurations being set") throw OAuthClientError.oauthClientHasNotBeenSet } @@ -31,11 +30,21 @@ public final class AppAuthOAuthClient: OAuthClient { // Quran.com relies on dicovering the service configuration from the issuer, // and not using a static configuration. let serviceConfiguration = try await discoverConfiguration(forIssuer: configuration.authorizationIssuerURL) - try await login(withConfiguration: serviceConfiguration, - appConfiguration: configuration, - on: viewController) + try await login( + withConfiguration: serviceConfiguration, + appConfiguration: configuration, + on: viewController + ) } + // MARK: Private + + // Needed mainly for retention. + private var authFlow: (any OIDExternalUserAgentSession)? + private var appConfiguration: OAuthAppConfiguration? + + // MARK: - Authenication Flow + private func discoverConfiguration(forIssuer issuer: URL) async throws -> OIDServiceConfiguration { logger.info("Discovering configuration for OAuth") return try await withCheckedThrowingContinuation { continuation in @@ -46,7 +55,7 @@ public final class AppAuthOAuthClient: OAuthClient { continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(error)) return } - guard let configuration = configuration else { + guard let configuration else { // This should not happen logger.error("Error fetching OAuth configuration: no cofniguration was loaded. An unexpected situtation.") continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(nil)) @@ -58,17 +67,21 @@ public final class AppAuthOAuthClient: OAuthClient { } } - private func login(withConfiguration configuration: OIDServiceConfiguration, - appConfiguration: OAuthAppConfiguration, - on viewController: UIViewController) async throws { + private func login( + withConfiguration configuration: OIDServiceConfiguration, + appConfiguration: OAuthAppConfiguration, + on viewController: UIViewController + ) async throws { let scopes = [OIDScopeOpenID, OIDScopeProfile] + appConfiguration.scopes - let request = OIDAuthorizationRequest(configuration: configuration, - clientId: appConfiguration.clientID, - clientSecret: nil, - scopes: scopes, - redirectURL: appConfiguration.redirectURL, - responseType: OIDResponseTypeCode, - additionalParameters: [:]) + let request = OIDAuthorizationRequest( + configuration: configuration, + clientId: appConfiguration.clientID, + clientSecret: nil, + scopes: scopes, + redirectURL: appConfiguration.redirectURL, + responseType: OIDResponseTypeCode, + additionalParameters: [:] + ) logger.info("Starting OAuth flow") try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in @@ -78,7 +91,7 @@ public final class AppAuthOAuthClient: OAuthClient { continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) return } - guard let state = state else { + guard let _ = state else { logger.error("Error authenticating: no state returned. An unexpected situtation.") continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) return @@ -90,14 +103,18 @@ public final class AppAuthOAuthClient: OAuthClient { } /// Executes the request on the main actor. - private func fire(loginRequest: OIDAuthorizationRequest, - on viewController: UIViewController, - callback: @escaping OIDAuthStateAuthorizationCallback) { + private func fire( + loginRequest: OIDAuthorizationRequest, + on viewController: UIViewController, + callback: @escaping OIDAuthStateAuthorizationCallback + ) { Task { await MainActor.run { - self.authFlow = OIDAuthState.authState(byPresenting: loginRequest, - presenting: viewController) { state, error in - self.authFlow = nil + authFlow = OIDAuthState.authState( + byPresenting: loginRequest, + presenting: viewController + ) { [weak self] state, error in + self?.authFlow = nil callback(state, error) } } diff --git a/Data/OAuthClient/Sources/OAuthClient.swift b/Data/OAuthClient/Sources/OAuthClient.swift index 2d53433a..e37bd0a2 100644 --- a/Data/OAuthClient/Sources/OAuthClient.swift +++ b/Data/OAuthClient/Sources/OAuthClient.swift @@ -1,5 +1,5 @@ // -// File.swift +// OAuthClient.swift // QuranEngine // // Created by Mohannad Hassan on 19/12/2024. @@ -34,7 +34,6 @@ public struct OAuthAppConfiguration { /// /// Note that the connection relies on dicvoering the configuration from the issuer service. public protocol OAuthClient { - func set(appConfiguration: OAuthAppConfiguration) /// Performs the login flow to Quran.com diff --git a/Domain/QuranProfileService/Sources/QuranProfileService.swift b/Domain/QuranProfileService/Sources/QuranProfileService.swift index 7cc0a4df..be21737b 100644 --- a/Domain/QuranProfileService/Sources/QuranProfileService.swift +++ b/Domain/QuranProfileService/Sources/QuranProfileService.swift @@ -1,15 +1,14 @@ // -// File.swift +// QuranProfileService.swift // QuranEngine // // Created by Mohannad Hassan on 23/12/2024. // -import UIKit import OAuthClient +import UIKit public class QuranProfileService { - private let oauthClient: OAuthClient public init(oauthClient: OAuthClient) { @@ -24,4 +23,3 @@ public class QuranProfileService { try await oauthClient.login(on: viewController) } } - diff --git a/Example/QuranEngineApp/Classes/Container.swift b/Example/QuranEngineApp/Classes/Container.swift index 8842d5c0..b27a94f9 100644 --- a/Example/QuranEngineApp/Classes/Container.swift +++ b/Example/QuranEngineApp/Classes/Container.swift @@ -13,9 +13,9 @@ import CoreDataPersistence import Foundation import LastPagePersistence import NotePersistence +import OAuthClient import PageBookmarkPersistence import ReadingService -import OAuthClient import UIKit /// Hosts singleton dependencies diff --git a/Features/AppDependencies/AppDependencies.swift b/Features/AppDependencies/AppDependencies.swift index a6b9f134..1f61a541 100644 --- a/Features/AppDependencies/AppDependencies.swift +++ b/Features/AppDependencies/AppDependencies.swift @@ -11,11 +11,11 @@ import BatchDownloader import Foundation import LastPagePersistence import NotePersistence +import OAuthClient import PageBookmarkPersistence import QuranResources import QuranTextKit import ReadingService -import OAuthClient public protocol AppDependencies { var databasesURL: URL { get } diff --git a/Features/SettingsFeature/SettingsBuilder.swift b/Features/SettingsFeature/SettingsBuilder.swift index 23da491b..42b01f58 100644 --- a/Features/SettingsFeature/SettingsBuilder.swift +++ b/Features/SettingsFeature/SettingsBuilder.swift @@ -9,9 +9,9 @@ import AppDependencies import AudioDownloadsFeature import Localization +import QuranProfileService import ReadingSelectorFeature import SettingsService -import QuranProfileService import SwiftUI import TranslationsFeature import UIKit diff --git a/Features/SettingsFeature/SettingsRootView.swift b/Features/SettingsFeature/SettingsRootView.swift index 702797b0..f36fc2dc 100644 --- a/Features/SettingsFeature/SettingsRootView.swift +++ b/Features/SettingsFeature/SettingsRootView.swift @@ -33,6 +33,7 @@ struct SettingsRootView: View { private struct SettingsRootViewUI: View { @Binding var theme: Theme + let audioEnd: String let navigateToAudioEndSelector: AsyncAction let navigateToAudioManager: AsyncAction diff --git a/Features/SettingsFeature/SettingsRootViewModel.swift b/Features/SettingsFeature/SettingsRootViewModel.swift index ff33f807..9b9d8975 100644 --- a/Features/SettingsFeature/SettingsRootViewModel.swift +++ b/Features/SettingsFeature/SettingsRootViewModel.swift @@ -12,9 +12,9 @@ import Localization import NoorUI import QuranAudio import QuranAudioKit +import QuranProfileService import ReadingSelectorFeature import SettingsService -import QuranProfileService import TranslationsFeature import UIKit import UIx @@ -51,19 +51,19 @@ final class SettingsRootViewModel: ObservableObject { // MARK: Internal - private let analytics: AnalyticsLibrary - private let reviewService: ReviewService - private let quranProfileService: QuranProfileService - private let audioDownloadsBuilder: AudioDownloadsBuilder - private let translationsListBuilder: TranslationsListBuilder - private let readingSelectorBuilder: ReadingSelectorBuilder - private let diagnosticsBuilder: DiagnosticsBuilder + let analytics: AnalyticsLibrary + let reviewService: ReviewService + let quranProfileService: QuranProfileService + let audioDownloadsBuilder: AudioDownloadsBuilder + let translationsListBuilder: TranslationsListBuilder + let readingSelectorBuilder: ReadingSelectorBuilder + let diagnosticsBuilder: DiagnosticsBuilder - private let contactUsService = ContactUsService() - private let themeService = ThemeService.shared - private let audioPreferences = AudioPreferences.shared + let contactUsService = ContactUsService() + let themeService = ThemeService.shared + let audioPreferences = AudioPreferences.shared - private weak var navigationController: UINavigationController? + weak var navigationController: UINavigationController? @Published var audioEnd: AudioEnd @@ -138,7 +138,7 @@ final class SettingsRootViewModel: ObservableObject { return } do { - try await self.quranProfileService.login(on: viewController) + try await quranProfileService.login(on: viewController) // TODO: Replace with the needed UI changes. print("Login seems successful") } catch { diff --git a/Package.swift b/Package.swift index 65a4ae77..43c5f247 100644 --- a/Package.swift +++ b/Package.swift @@ -47,7 +47,7 @@ let package = Package( // Async .package(url: "https://github.com/apple/swift-async-algorithms", from: "0.1.0"), - + // OAuth .package(url: "https://github.com/openid/AppAuth-iOS", .upToNextMajor(from: "1.3.0")), @@ -298,11 +298,12 @@ private func dataTargets() -> [[Target]] { "BatchDownloader", "NetworkSupportFake", ]), - + // MARK: - Quran.com OAuth + target(type, name: "OAuthClient", hasTests: false, dependencies: [ .product(name: "AppAuth", package: "AppAuth-iOS"), - ]) + ]), ] } @@ -469,7 +470,7 @@ private func domainTargets() -> [[Target]] { target(type, name: "QuranProfileService", hasTests: false, dependencies: [ "OAuthClient", - ]) + ]), ] } From 9b0d1d125d4a862a110f1602ec54a349aacacb59 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Tue, 24 Dec 2024 12:07:03 +0200 Subject: [PATCH 13/15] Add VLogging as a dependency for oauthclient --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 43c5f247..5b677c0e 100644 --- a/Package.swift +++ b/Package.swift @@ -302,6 +302,7 @@ private func dataTargets() -> [[Target]] { // MARK: - Quran.com OAuth target(type, name: "OAuthClient", hasTests: false, dependencies: [ + "VLogging", .product(name: "AppAuth", package: "AppAuth-iOS"), ]), ] From 6edbdc981ffad6e67eeeb58f35ba4185a80e7691 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Wed, 25 Dec 2024 22:06:37 +0200 Subject: [PATCH 14/15] Some minor cleanup in AppAuthOAuthClient --- .../Sources/AppAuthOAuthClient.swift | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index b17d9a9b..6fc14ff6 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -57,7 +57,7 @@ public final class AppAuthOAuthClient: OAuthClient { } guard let configuration else { // This should not happen - logger.error("Error fetching OAuth configuration: no cofniguration was loaded. An unexpected situtation.") + logger.error("Error fetching OAuth configuration: no configuration was loaded. An unexpected situtation.") continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(nil)) return } @@ -85,39 +85,26 @@ public final class AppAuthOAuthClient: OAuthClient { logger.info("Starting OAuth flow") try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - fire(loginRequest: request, on: viewController) { state, error in - guard error == nil else { - logger.error("Error authenticating: \(error!)") - continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) - return - } - guard let _ = state else { - logger.error("Error authenticating: no state returned. An unexpected situtation.") - continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) - return - } - logger.info("OAuth flow completed successfully") - continuation.resume() - } - } - } - - /// Executes the request on the main actor. - private func fire( - loginRequest: OIDAuthorizationRequest, - on viewController: UIViewController, - callback: @escaping OIDAuthStateAuthorizationCallback - ) { - Task { - await MainActor.run { - authFlow = OIDAuthState.authState( - byPresenting: loginRequest, + DispatchQueue.main.async { + self.authFlow = OIDAuthState.authState( + byPresenting: request, presenting: viewController ) { [weak self] state, error in self?.authFlow = nil - callback(state, error) + guard error == nil else { + logger.error("Error authenticating: \(error!)") + continuation.resume(throwing: OAuthClientError.errorAuthenticating(error)) + return + } + guard let _ = state else { + logger.error("Error authenticating: no state returned. An unexpected situtation.") + continuation.resume(throwing: OAuthClientError.errorAuthenticating(nil)) + return + } + logger.info("OAuth flow completed successfully") + continuation.resume() } } } - } + } } From 12ed7f5fa49501ddd31efef1c519ff3123e8a4c7 Mon Sep 17 00:00:00 2001 From: "Mohannad A. Hassan" Date: Wed, 25 Dec 2024 22:07:58 +0200 Subject: [PATCH 15/15] cleanup --- Data/OAuthClient/Sources/AppAuthOAuthClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift index 6fc14ff6..122a58d9 100644 --- a/Data/OAuthClient/Sources/AppAuthOAuthClient.swift +++ b/Data/OAuthClient/Sources/AppAuthOAuthClient.swift @@ -106,5 +106,5 @@ public final class AppAuthOAuthClient: OAuthClient { } } } - } + } }