From 3b0dbfc7ba102b22a2201c7810fa5d070ae3e713 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Thu, 9 Dec 2021 12:50:27 -0300 Subject: [PATCH] Have `clearSession()` yield a `Result` --- App/ViewController.swift | 4 +- Auth0.xcodeproj/project.pbxproj | 2 +- Auth0/ASCallbackTransaction.swift | 14 ++++- Auth0/Auth0WebAuth.swift | 20 +++---- Auth0/BaseCallbackTransaction.swift | 8 +-- Auth0/WebAuth.swift | 93 +++++++++++++++++++++-------- Auth0Tests/Matchers.swift | 8 +++ Auth0Tests/WebAuthSpec.swift | 24 +++++--- OAuth2Mac/ViewController.swift | 6 +- V2_MIGRATION_GUIDE.md | 4 ++ 10 files changed, 127 insertions(+), 56 deletions(-) diff --git a/App/ViewController.swift b/App/ViewController.swift index 5c6adbc20..2d0555336 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -35,8 +35,8 @@ class ViewController: UIViewController { .logging(enabled: true) .clearSession(federated: false) { result in switch result { - case true: print("Logged out") - case false: print("Log out failed") + case .success: print("Logged out") + case .failure(let error): print(error) } } } diff --git a/Auth0.xcodeproj/project.pbxproj b/Auth0.xcodeproj/project.pbxproj index b5a1daac2..9de7700db 100644 --- a/Auth0.xcodeproj/project.pbxproj +++ b/Auth0.xcodeproj/project.pbxproj @@ -837,11 +837,11 @@ 5F3965C01CF679B500CDE7C0 /* WebAuth */ = { isa = PBXGroup; children = ( + 5B16D8901F7141E5009476A5 /* AuthTransactions */, 5C41F6AF244DCC1100252548 /* Platforms */, 5F53F5CD1CFD157300476A46 /* AuthTransaction.swift */, 5B16D8921F714324009476A5 /* AuthSession.swift */, 5F4A1F951D00AABC00C72242 /* OAuth2Grant.swift */, - 5B16D8901F7141E5009476A5 /* AuthTransactions */, 5FCAB1751D0900CF00331C84 /* TransactionStore.swift */, 5F3965C11CF67CF000CDE7C0 /* WebAuth.swift */, 5FAE9C901D8878D400A871CE /* Auth0WebAuth.swift */, diff --git a/Auth0/ASCallbackTransaction.swift b/Auth0/ASCallbackTransaction.swift index eb75a7476..354b59369 100644 --- a/Auth0/ASCallbackTransaction.swift +++ b/Auth0/ASCallbackTransaction.swift @@ -3,12 +3,20 @@ import AuthenticationServices final class ASCallbackTransaction: BaseCallbackTransaction { - init(url: URL, schemeURL: URL, callback: @escaping (Bool) -> Void) { + init(url: URL, schemeURL: URL, callback: @escaping (WebAuthResult) -> Void) { super.init(callback: callback) let authSession = ASWebAuthenticationSession(url: url, - callbackURLScheme: schemeURL.scheme) { [weak self] url, _ in - self?.callback(url != nil) + callbackURLScheme: schemeURL.scheme) { [weak self] in + guard $0 != nil, $1 == nil else { + if let authError = $1, case ASWebAuthenticationSessionError.canceledLogin = authError { + self?.callback(.failure(WebAuthError(code: .userCancelled))) + } else { + self?.callback(.failure(WebAuthError(code: .unknown("ASWebAuthenticationSession failed")))) + } + return TransactionStore.shared.clear() + } + self?.callback(.success(())) TransactionStore.shared.clear() } diff --git a/Auth0/Auth0WebAuth.swift b/Auth0/Auth0WebAuth.swift index 3d663bbbd..de618df9b 100644 --- a/Auth0/Auth0WebAuth.swift +++ b/Auth0/Auth0WebAuth.swift @@ -158,7 +158,7 @@ final class Auth0WebAuth: WebAuth { self.storage.store(session) } - func clearSession(federated: Bool, callback: @escaping (Bool) -> Void) { + func clearSession(federated: Bool, callback: @escaping (WebAuthResult) -> Void) { let endpoint = federated ? URL(string: "v2/logout?federated", relativeTo: self.url)! : URL(string: "v2/logout", relativeTo: self.url)! @@ -170,7 +170,7 @@ final class Auth0WebAuth: WebAuth { components?.queryItems = queryItems + [returnTo, clientId] guard let logoutURL = components?.url, let redirectURL = self.redirectURL else { - return callback(false) + return callback(.failure(WebAuthError(code: .noBundleIdentifier))) } let session = ASCallbackTransaction(url: logoutURL, @@ -252,11 +252,11 @@ extension Auth0WebAuth { return Deferred { Future(self.start) }.eraseToAnyPublisher() } - public func clearSession(federated: Bool) -> AnyPublisher { + public func clearSession(federated: Bool) -> AnyPublisher { return Deferred { Future { callback in self.clearSession(federated: federated) { result in - callback(.success(result)) + callback(result) } } }.eraseToAnyPublisher() @@ -287,19 +287,19 @@ extension Auth0WebAuth { #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) - func clearSession(federated: Bool) async -> Bool { - return await withCheckedContinuation { continuation in + func clearSession(federated: Bool) async throws { + return try await withCheckedThrowingContinuation { continuation in self.clearSession(federated: federated) { result in - continuation.resume(returning: result) + continuation.resume(with: result) } } } #else @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) - func clearSession(federated: Bool) async -> Bool { - return await withCheckedContinuation { continuation in + func clearSession(federated: Bool) async throws { + return try await withCheckedThrowingContinuation { continuation in self.clearSession(federated: federated) { result in - continuation.resume(returning: result) + continuation.resume(with: result) } } } diff --git a/Auth0/BaseCallbackTransaction.swift b/Auth0/BaseCallbackTransaction.swift index 0a4a0b57c..2ec407e3a 100644 --- a/Auth0/BaseCallbackTransaction.swift +++ b/Auth0/BaseCallbackTransaction.swift @@ -5,18 +5,18 @@ class BaseCallbackTransaction: NSObject, AuthTransaction { var authSession: AuthSession? var state: String? - let callback: (Bool) -> Void + let callback: (WebAuthResult) -> Void - init(callback: @escaping (Bool) -> Void) { + init(callback: @escaping (WebAuthResult) -> Void) { self.callback = callback } func cancel() { - self.callback(false) + self.callback(.failure(WebAuthError(code: .userCancelled))) } func resume(_ url: URL) -> Bool { - self.callback(true) + self.callback(.success(())) return true } diff --git a/Auth0/WebAuth.swift b/Auth0/WebAuth.swift index 24882e24d..cf20abbb4 100644 --- a/Auth0/WebAuth.swift +++ b/Auth0/WebAuth.swift @@ -1,3 +1,5 @@ +// swiftlint:disable file_length + #if WEB_AUTH_PLATFORM import Foundation #if canImport(Combine) @@ -139,9 +141,14 @@ public protocol WebAuth: Trackable, Loggable { Starts the WebAuth flow. ``` - let credentials = try await Auth0 - .webAuth(clientId: clientId, domain: "samples.auth0.com") - .start() + do { + let credentials = try await Auth0 + .webAuth(clientId: clientId, domain: "samples.auth0.com") + .start() + print("Obtained credentials: \(credentials)") + } catch { + print("Failed with \(error)") + } ``` Any on going WebAuth Auth session will be automatically cancelled when starting a new one, @@ -194,7 +201,13 @@ public protocol WebAuth: Trackable, Loggable { ``` Auth0 .webAuth() - .clearSession { print($0) } + .clearSession { result in + switch result { + case .success: + print("Logged out") + case .failure(let error): + print("Failed with \(error)") + } ``` Remove Auth0 session and remove the IdP session: @@ -209,7 +222,7 @@ public protocol WebAuth: Trackable, Loggable { - federated: `Bool` to remove the IdP session. Defaults to `false`. - callback: Callback called with bool outcome of the call. */ - func clearSession(federated: Bool, callback: @escaping (Bool) -> Void) + func clearSession(federated: Bool, callback: @escaping (WebAuthResult) -> Void) /** Removes Auth0 session and optionally remove the Identity Provider session. @@ -222,7 +235,14 @@ public protocol WebAuth: Trackable, Loggable { Auth0 .webAuth() .clearSession() - .sink(receiveValue: { print($0) }) + .sink(receiveCompletion: { completion in + switch completion { + case .failure(let error): + print("Failed with \(error)") + case .finished: + print("Logged out") + } + }, receiveValue: { _ in }) .store(in: &cancellables) ``` @@ -240,7 +260,7 @@ public protocol WebAuth: Trackable, Loggable { - Returns: A type-erased publisher. */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) - func clearSession(federated: Bool) -> AnyPublisher + func clearSession(federated: Bool) -> AnyPublisher #if compiler(>=5.5) && canImport(_Concurrency) /** @@ -251,15 +271,20 @@ public protocol WebAuth: Trackable, Loggable { to the **Allowed Logout URLs** section of your application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications/). ``` - let result = await Auth0 - .webAuth(clientId: clientId, domain: "samples.auth0.com") - .clearSession() + do { + try await Auth0 + .webAuth(clientId: clientId, domain: "samples.auth0.com") + .clearSession() + print("Logged out") + } catch { + print("Failed with \(error)") + } ``` Remove Auth0 session and remove the IdP session: ``` - let result = await Auth0 + try await Auth0 .webAuth(clientId: clientId, domain: "samples.auth0.com") .clearSession(federated: true) ``` @@ -269,10 +294,10 @@ public protocol WebAuth: Trackable, Loggable { */ #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) - func clearSession(federated: Bool) async -> Bool + func clearSession(federated: Bool) async throws #else @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) - func clearSession(federated: Bool) async -> Bool + func clearSession(federated: Bool) async throws #endif #endif } @@ -289,7 +314,13 @@ public extension WebAuth { ``` Auth0 .webAuth() - .clearSession { print($0) } + .clearSession { result in + switch result { + case .success: + print("Logged out") + case .failure(let error): + print("Failed with \(error)") + } ``` Remove Auth0 session and remove the IdP session: @@ -304,7 +335,7 @@ public extension WebAuth { - federated: `Bool` to remove the IdP session. Defaults to `false`. - callback: Callback called with bool outcome of the call. */ - func clearSession(federated: Bool = false, callback: @escaping (Bool) -> Void) { + func clearSession(federated: Bool = false, callback: @escaping (WebAuthResult) -> Void) { self.clearSession(federated: federated, callback: callback) } @@ -319,7 +350,14 @@ public extension WebAuth { Auth0 .webAuth() .clearSession() - .sink(receiveValue: { print($0) }) + .sink(receiveCompletion: { completion in + switch completion { + case .failure(let error): + print("Failed with \(error)") + case .finished: + print("Logged out") + } + }, receiveValue: { _ in }) .store(in: &cancellables) ``` @@ -337,7 +375,7 @@ public extension WebAuth { - Returns: A type-erased publisher. */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) - func clearSession(federated: Bool = false) -> AnyPublisher { + func clearSession(federated: Bool = false) -> AnyPublisher { return self.clearSession(federated: federated) } @@ -350,15 +388,20 @@ public extension WebAuth { to the **Allowed Logout URLs** section of your application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications/). ``` - let result = await Auth0 - .webAuth(clientId: clientId, domain: "samples.auth0.com") - .clearSession() + do { + try await Auth0 + .webAuth(clientId: clientId, domain: "samples.auth0.com") + .clearSession() + print("Logged out") + } catch { + print("Failed with \(error)") + } ``` Remove Auth0 session and remove the IdP session: ``` - let result = await Auth0 + try await Auth0 .webAuth(clientId: clientId, domain: "samples.auth0.com") .clearSession(federated: true) ``` @@ -368,13 +411,13 @@ public extension WebAuth { */ #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) - func clearSession(federated: Bool = false) async -> Bool { - return await self.clearSession(federated: federated) + func clearSession(federated: Bool = false) async throws { + return try await self.clearSession(federated: federated) } #else @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) - func clearSession(federated: Bool = false) async -> Bool { - return await self.clearSession(federated: federated) + func clearSession(federated: Bool = false) async throws { + return try await self.clearSession(federated: federated) } #endif #endif diff --git a/Auth0Tests/Matchers.swift b/Auth0Tests/Matchers.swift index c953556f7..094fce3e9 100644 --- a/Auth0Tests/Matchers.swift +++ b/Auth0Tests/Matchers.swift @@ -258,6 +258,14 @@ func beSuccessful() -> Predicate> { } } +#if WEB_AUTH_PLATFORM +func beSuccessful() -> Predicate> { + return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) + } +} +#endif + func beSuccessful() -> Predicate> { return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in return try beSuccessful(expression, failureMessage) diff --git a/Auth0Tests/WebAuthSpec.swift b/Auth0Tests/WebAuthSpec.swift index e096cbfa5..7a0f988b6 100644 --- a/Auth0Tests/WebAuthSpec.swift +++ b/Auth0Tests/WebAuthSpec.swift @@ -497,40 +497,46 @@ class WebAuthSpec: QuickSpec { context("ASWebAuthenticationSession") { - var outcome: Bool? + var result: WebAuthResult? beforeEach { - outcome = nil + result = nil TransactionStore.shared.clear() } it("should launch AuthenticationServicesSessionCallback") { let auth = newWebAuth() - auth.clearSession(federated: false) { _ in } + auth.clearSession() { _ in } + expect(TransactionStore.shared.current).toNot(beNil()) + } + + it("should launch AuthenticationServicesSessionCallback with federated") { + let auth = newWebAuth() + auth.clearSession(federated: true) { _ in } expect(TransactionStore.shared.current).toNot(beNil()) } it("should cancel AuthenticationServicesSessionCallback") { let auth = newWebAuth() - auth.clearSession(federated: false) { outcome = $0 } + auth.clearSession() { result = $0 } TransactionStore.shared.cancel(TransactionStore.shared.current!) - expect(outcome).to(beFalse()) + expect(result).to(haveWebAuthError(WebAuthError(code: .userCancelled))) expect(TransactionStore.shared.current).to(beNil()) } it("should resume AuthenticationServicesSessionCallback") { let auth = newWebAuth() - auth.clearSession(federated: false) { outcome = $0 } + auth.clearSession() { result = $0 } _ = TransactionStore.shared.resume(URL(string: "http://fake.com")!) - expect(outcome).to(beTrue()) + expect(result).to(beSuccessful()) expect(TransactionStore.shared.current).to(beNil()) } it("should fail when redirect URL is missing") { let auth = newWebAuth() auth.redirectURL = nil - auth.clearSession(federated: false) { outcome = $0 } - expect(outcome).to(beFalse()) + auth.clearSession() { result = $0 } + expect(result).to(haveWebAuthError(WebAuthError(code: .noBundleIdentifier))) } } diff --git a/OAuth2Mac/ViewController.swift b/OAuth2Mac/ViewController.swift index 96afbfed4..b1b135290 100644 --- a/OAuth2Mac/ViewController.swift +++ b/OAuth2Mac/ViewController.swift @@ -6,11 +6,12 @@ class ViewController: NSViewController { @IBAction func login(_ sender: Any) { Auth0.webAuth() .logging(enabled: true) - .start { result in + .clearSession(federated: false) { result in switch result { - case .success(let credentials): print(credentials) + case .success: print("Logged out") case .failure(let error): print(error) } + } } } @@ -19,6 +20,7 @@ class ViewController: NSViewController { .logging(enabled: true) .clearSession(federated: false) { result in result ? print("Logged out") : print("Failed to log out") + } } } diff --git a/V2_MIGRATION_GUIDE.md b/V2_MIGRATION_GUIDE.md index 523903375..936d03791 100644 --- a/V2_MIGRATION_GUIDE.md +++ b/V2_MIGRATION_GUIDE.md @@ -408,6 +408,10 @@ The methods of the Management API client now only yield errors of type `Manageme The Web Auth methods now only yield errors of type `WebAuthError`. The underlying error (if any) is available via the `cause: Error?` property of the `WebAuthError`. +#### `clearSession(federated:)` + +This method now yields a `Result`, which is aliased to `WebAuthResult`. This means you can now check the type of error (e.g. if the user cancelled the operation) and act accordingly. + ### Credentials Manager #### Errors