-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create OAuthClient * Create AppAuthOAuthClient * Add AppAuth dependency and OAuthClient package * Implement main steps in AppAuthOAuthClient * Create QuranProfileService package * Register OAuthClient in Container * Perform login operation in SettingsFeature * Perform web authorization call on main thread for BG main thread runtiem warning * Cleanup and provide documentation * Add some loggigns * Cleanup * Fix linting issues * Add VLogging as a dependency for oauthclient * Some minor cleanup in AppAuthOAuthClient * cleanup
- Loading branch information
1 parent
0f6a996
commit 3d34dad
Showing
10 changed files
with
252 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// | ||
// AppAuthOAuthClient.swift | ||
// QuranEngine | ||
// | ||
// Created by Mohannad Hassan on 23/12/2024. | ||
// | ||
|
||
import AppAuth | ||
import Foundation | ||
import UIKit | ||
import VLogging | ||
|
||
public final class AppAuthOAuthClient: OAuthClient { | ||
// 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 = appConfiguration else { | ||
logger.error("login invoked without OAuth client configurations being set") | ||
throw OAuthClientError.oauthClientHasNotBeenSet | ||
} | ||
|
||
// 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 | ||
) | ||
} | ||
|
||
// 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 | ||
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 else { | ||
// This should not happen | ||
logger.error("Error fetching OAuth configuration: no configuration was loaded. An unexpected situtation.") | ||
continuation.resume(throwing: OAuthClientError.errorFetchingConfiguration(nil)) | ||
return | ||
} | ||
logger.info("OAuth configuration fetched successfully") | ||
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: [:] | ||
) | ||
|
||
logger.info("Starting OAuth flow") | ||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, any Error>) in | ||
DispatchQueue.main.async { | ||
self.authFlow = OIDAuthState.authState( | ||
byPresenting: request, | ||
presenting: viewController | ||
) { [weak self] state, error in | ||
self?.authFlow = nil | ||
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() | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// | ||
// OAuthClient.swift | ||
// QuranEngine | ||
// | ||
// Created by Mohannad Hassan on 19/12/2024. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
|
||
public enum OAuthClientError: Error { | ||
case oauthClientHasNotBeenSet | ||
case errorFetchingConfiguration(Error?) | ||
case errorAuthenticating(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 authorizationIssuerURL: URL | ||
|
||
public init(clientID: String, redirectURL: URL, scopes: [String], authorizationIssuerURL: URL) { | ||
self.clientID = clientID | ||
self.redirectURL = redirectURL | ||
self.scopes = scopes | ||
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) | ||
|
||
/// 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 | ||
} |
25 changes: 25 additions & 0 deletions
25
Domain/QuranProfileService/Sources/QuranProfileService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// | ||
// QuranProfileService.swift | ||
// QuranEngine | ||
// | ||
// Created by Mohannad Hassan on 23/12/2024. | ||
// | ||
|
||
import OAuthClient | ||
import UIKit | ||
|
||
public class QuranProfileService { | ||
private let oauthClient: OAuthClient | ||
|
||
public init(oauthClient: OAuthClient) { | ||
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) | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
Example/QuranEngineApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.