From aedd0677a85986181a8b085f5b79cb62c488d342 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 30 Nov 2021 13:39:43 -0300 Subject: [PATCH 1/3] Use strongly-typed result and error types --- App/ViewController.swift | 2 +- Auth0/ASTransaction.swift | 5 +- Auth0/Auth0.swift | 29 ++- Auth0/Auth0Error.swift | 15 +- Auth0/Auth0WebAuth.swift | 43 +++-- Auth0/AuthenticationError.swift | 4 +- Auth0/BaseTransaction.swift | 7 +- Auth0/CredentialsManager.swift | 26 +-- Auth0/Handlers.swift | 26 ++- Auth0/JWK+RSA.swift | 1 - Auth0/Management.swift | 8 +- Auth0/ManagementError.swift | 4 +- Auth0/OAuth2Grant.swift | 12 +- Auth0/Request.swift | 2 +- Auth0/Requestable.swift | 3 +- Auth0/Response.swift | 2 +- Auth0/WebAuth.swift | 12 +- Auth0/WebAuthError.swift | 3 + Auth0Tests/AuthenticationSpec.swift | 27 ++- Auth0Tests/BaseTransactionSpec.swift | 4 +- Auth0Tests/CredentialsManagerSpec.swift | 217 ++++++++------------- Auth0Tests/ManagementSpec.swift | 31 ++- Auth0Tests/Matchers.swift | 242 ++++++++++++++++-------- Auth0Tests/WebAuthSpec.swift | 30 +++ Package.swift | 2 +- README.md | 14 +- V2_MIGRATION_GUIDE.md | 30 ++- 27 files changed, 490 insertions(+), 311 deletions(-) diff --git a/App/ViewController.swift b/App/ViewController.swift index 178ca74f..5c6adbc2 100644 --- a/App/ViewController.swift +++ b/App/ViewController.swift @@ -3,7 +3,7 @@ import Auth0 class ViewController: UIViewController { - var onAuth: ((Auth0Result) -> ())! + var onAuth: ((WebAuthResult) -> ())! override func viewDidLoad() { super.viewDidLoad() diff --git a/Auth0/ASTransaction.swift b/Auth0/ASTransaction.swift index 2f614863..40572030 100644 --- a/Auth0/ASTransaction.swift +++ b/Auth0/ASTransaction.swift @@ -19,11 +19,10 @@ final class ASTransaction: BaseTransaction { let authSession = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: self.redirectURL.scheme) { [weak self] in guard $1 == nil, let callbackURL = $0 else { - let authError = $1 ?? WebAuthError(code: .unknown("ASWebAuthenticationSession failed")) - if case ASWebAuthenticationSessionError.canceledLogin = authError { + if let authError = $1, case ASWebAuthenticationSessionError.canceledLogin = authError { self?.callback(.failure(WebAuthError(code: .userCancelled))) } else { - self?.callback(.failure(authError)) + self?.callback(.failure(WebAuthError(code: .unknown("ASWebAuthenticationSession failed")))) } return TransactionStore.shared.clear() } diff --git a/Auth0/Auth0.swift b/Auth0/Auth0.swift index e83f2f66..ce5cf2b6 100644 --- a/Auth0/Auth0.swift +++ b/Auth0/Auth0.swift @@ -1,9 +1,26 @@ import Foundation /** - Auth0.swift wrapper for the Swift `Result` type + `Result` wrapper for Authentication API operations */ -public typealias Auth0Result = Result +public typealias AuthenticationResult = Result + +/** + `Result` wrapper for Management API operations + */ +public typealias ManagementResult = Result + +#if WEB_AUTH_PLATFORM +/** + `Result` wrapper for Web Auth operations + */ +public typealias WebAuthResult = Result +#endif + +/** + `Result` wrapper for Credentials Manager operations + */ +public typealias CredentialsManagerResult = Result /** Default scope value used across Auth0.swift @@ -19,7 +36,7 @@ public let defaultScope = "openid profile email" - parameter clientId: clientId of your Auth0 application - parameter domain: domain of your Auth0 account. e.g.: 'samples.auth0.com' - - parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - returns: Auth0 Authentication API */ @@ -49,7 +66,7 @@ public func authentication(clientId: String, domain: String, session: URLSession ``` - - parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle - returns: Auth0 Authentication API @@ -90,7 +107,7 @@ public func authentication(session: URLSession = .shared, bundle: Bundle = .main ``` - parameter token: token of Management API v2 with the correct allowed scopes to perform the desired action - - parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle - returns: Auth0 Management API v2 @@ -117,7 +134,7 @@ public func users(token: String, session: URLSession = .shared, bundle: Bundle = - parameter token: token of Management API v2 with the correct allowed scopes to perform the desired action - parameter domain: domain of your Auth0 account. e.g.: 'samples.auth0.com' - - parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - returns: Auth0 Management API v2 */ diff --git a/Auth0/Auth0Error.swift b/Auth0/Auth0Error.swift index 42318dd1..0c637406 100644 --- a/Auth0/Auth0Error.swift +++ b/Auth0/Auth0Error.swift @@ -33,11 +33,24 @@ public protocol Auth0APIError: Auth0Error { */ init(info: [String: Any], statusCode: Int?) + /** + Returns a value from the error data + + - parameter key: key of the value to return + + - returns: the value of key or nil if cannot be found or is of the wrong type. + */ + subscript(_ key: String) -> T? { get } + } extension Auth0APIError { - init(error: Error, statusCode: Int? = 0) { + init(info: [String: Any], statusCode: Int? = 0) { + self.init(info: info, statusCode: statusCode) + } + + init(cause error: Error, statusCode: Int? = 0) { let info: [String: Any] = [ "code": nonJSONError, "description": error.localizedDescription, diff --git a/Auth0/Auth0WebAuth.swift b/Auth0/Auth0WebAuth.swift index 9c83aee4..ddd229fc 100644 --- a/Auth0/Auth0WebAuth.swift +++ b/Auth0/Auth0WebAuth.swift @@ -1,30 +1,32 @@ #if WEB_AUTH_PLATFORM -import AuthenticationServices +import Foundation final class Auth0WebAuth: WebAuth { let clientId: String let url: URL + let session: URLSession let storage: TransactionStore + var telemetry: Telemetry var logger: Logger? - var ephemeralSession = false #if os(macOS) private let platform = "macos" #else private let platform = "ios" #endif - private let responseType = "code" private let requiredScope = "openid" + private(set) var parameters: [String: String] = [:] + private(set) var ephemeralSession = false private(set) var issuer: String private(set) var leeway: Int = 60 * 1000 // Default leeway is 60 seconds + private(set) var nonce: String? + private(set) var maxAge: Int? private(set) var organization: String? private(set) var invitationURL: URL? - private var nonce: String? - private var maxAge: Int? lazy var redirectURL: URL? = { guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return nil } @@ -38,10 +40,12 @@ final class Auth0WebAuth: WebAuth { init(clientId: String, url: URL, + session: URLSession = URLSession.shared, storage: TransactionStore = TransactionStore.shared, telemetry: Telemetry = Telemetry()) { self.clientId = clientId self.url = url + self.session = session self.storage = storage self.telemetry = telemetry self.issuer = "\(url.absoluteString)/" @@ -117,7 +121,7 @@ final class Auth0WebAuth: WebAuth { return self } - func start(_ callback: @escaping (Auth0Result) -> Void) { + func start(_ callback: @escaping (WebAuthResult) -> Void) { guard let redirectURL = self.redirectURL else { return callback(.failure(WebAuthError(code: .noBundleIdentifier))) } @@ -207,18 +211,6 @@ final class Auth0WebAuth: WebAuth { return components.url! } - private func handler(_ redirectURL: URL) -> OAuth2Grant { - var authentication = Auth0Authentication(clientId: self.clientId, url: self.url, telemetry: self.telemetry) - authentication.logger = self.logger - return PKCE(authentication: authentication, - redirectURL: redirectURL, - issuer: self.issuer, - leeway: self.leeway, - maxAge: self.maxAge, - nonce: self.nonce, - organization: self.organization) - } - func generateDefaultState() -> String? { let data = Data(count: 32) var tempData = data @@ -231,6 +223,21 @@ final class Auth0WebAuth: WebAuth { return tempData.a0_encodeBase64URLSafe() } + private func handler(_ redirectURL: URL) -> OAuth2Grant { + var authentication = Auth0Authentication(clientId: self.clientId, + url: self.url, + session: self.session, + telemetry: self.telemetry) + authentication.logger = self.logger + return PKCE(authentication: authentication, + redirectURL: redirectURL, + issuer: self.issuer, + leeway: self.leeway, + maxAge: self.maxAge, + nonce: self.nonce, + organization: self.organization) + } + } extension Auth0Authentication { diff --git a/Auth0/AuthenticationError.swift b/Auth0/AuthenticationError.swift index 437dcc4d..d3591de5 100644 --- a/Auth0/AuthenticationError.swift +++ b/Auth0/AuthenticationError.swift @@ -140,13 +140,13 @@ extension AuthenticationError: CustomDebugStringConvertible { extension AuthenticationError { /** - Returns a value from the error's `info` dictionary + Returns a value from the error data - parameter key: key of the value to return - returns: the value of key or nil if cannot be found or is of the wrong type. */ - subscript(_ key: String) -> T? { + public subscript(_ key: String) -> T? { return self.info[key] as? T } diff --git a/Auth0/BaseTransaction.swift b/Auth0/BaseTransaction.swift index be334933..3a1ff642 100644 --- a/Auth0/BaseTransaction.swift +++ b/Auth0/BaseTransaction.swift @@ -3,7 +3,7 @@ import Foundation class BaseTransaction: NSObject, AuthTransaction { - typealias FinishTransaction = (Auth0Result) -> Void + typealias FinishTransaction = (WebAuthResult) -> Void var authSession: AuthSession? let state: String? @@ -44,13 +44,14 @@ class BaseTransaction: NSObject, AuthTransaction { private func handleURL(_ url: URL) -> Bool { guard url.absoluteString.lowercased().hasPrefix(self.redirectURL.absoluteString.lowercased()) else { return false } guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { - self.callback(.failure(AuthenticationError(description: url.absoluteString, statusCode: 200))) + let error = WebAuthError(code: .unknown("Invalid callback URL: \(url.absoluteString)")) + self.callback(.failure(error)) return false } let items = self.handler.values(fromComponents: components) guard has(state: self.state, inItems: items) else { return false } if items["error"] != nil { - self.callback(.failure(AuthenticationError(info: items, statusCode: 0))) + self.callback(.failure(WebAuthError(code: .other, cause: AuthenticationError(info: items)))) } else { self.handler.credentials(from: items, callback: self.callback) } diff --git a/Auth0/CredentialsManager.swift b/Auth0/CredentialsManager.swift index 06a6946d..427afb00 100644 --- a/Auth0/CredentialsManager.swift +++ b/Auth0/CredentialsManager.swift @@ -162,17 +162,17 @@ public struct CredentialsManager { /// - Important: This method only works for a refresh token obtained after auth with OAuth 2.0 API Authorization. /// - Note: [Auth0 Refresh Tokens Docs](https://auth0.com/docs/tokens/concepts/refresh-tokens) #if WEB_AUTH_PLATFORM - public func credentials(withScope scope: String? = nil, minTTL: Int = 0, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerError?, Credentials?) -> Void) { - guard self.hasValid(minTTL: minTTL) else { return callback(.noCredentials, nil) } + public func credentials(withScope scope: String? = nil, minTTL: Int = 0, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerResult) -> Void) { + guard self.hasValid(minTTL: minTTL) else { return callback(.failure(.noCredentials)) } if let bioAuth = self.bioAuth { guard bioAuth.available else { let error = CredentialsManagerError(code: .biometricsFailed, cause: LAError(LAError.biometryNotAvailable)) - return callback(error, nil) + return callback(.failure(error)) } bioAuth.validateBiometric { guard $0 == nil else { - return callback(CredentialsManagerError(code: .biometricsFailed, cause: $0!), nil) + return callback(.failure(CredentialsManagerError(code: .biometricsFailed, cause: $0!))) } self.retrieveCredentials(withScope: scope, minTTL: minTTL, parameters: parameters, callback: callback) } @@ -181,8 +181,8 @@ public struct CredentialsManager { } } #else - public func credentials(withScope scope: String? = nil, minTTL: Int = 0, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerError?, Credentials?) -> Void) { - guard self.hasValid(minTTL: minTTL) else { return callback(.noCredentials, nil) } + public func credentials(withScope scope: String? = nil, minTTL: Int = 0, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerResult) -> Void) { + guard self.hasValid(minTTL: minTTL) else { return callback(.failure(.noCredentials)) } self.retrieveCredentials(withScope: scope, minTTL: minTTL, parameters: parameters, callback: callback) } #endif @@ -194,24 +194,24 @@ public struct CredentialsManager { return credentials } - private func retrieveCredentials(withScope scope: String?, minTTL: Int, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerError?, Credentials?) -> Void) { + private func retrieveCredentials(withScope scope: String?, minTTL: Int, parameters: [String: Any] = [:], callback: @escaping (CredentialsManagerResult) -> Void) { self.dispatchQueue.async { self.dispatchGroup.enter() DispatchQueue.global(qos: .userInitiated).async { guard let credentials = retrieveCredentials() else { self.dispatchGroup.leave() - return callback(.noCredentials, nil) + return callback(.failure(.noCredentials)) } guard self.hasExpired(credentials) || self.willExpire(credentials, within: minTTL) || self.hasScopeChanged(credentials, from: scope) else { self.dispatchGroup.leave() - return callback(nil, credentials) + return callback(.success(credentials)) } guard let refreshToken = credentials.refreshToken else { self.dispatchGroup.leave() - return callback(.noRefreshToken, nil) + return callback(.failure(.noRefreshToken)) } self.authentication .renew(withRefreshToken: refreshToken, scope: scope) @@ -229,15 +229,15 @@ public struct CredentialsManager { let tokenLifetime = Int(credentials.expiresIn.timeIntervalSinceNow) let error = CredentialsManagerError(code: .largeMinTTL(minTTL: minTTL, lifetime: tokenLifetime)) self.dispatchGroup.leave() - callback(error, nil) + callback(.failure(error)) } else { _ = self.store(credentials: newCredentials) self.dispatchGroup.leave() - callback(nil, newCredentials) + callback(.success(newCredentials)) } case .failure(let error): self.dispatchGroup.leave() - callback(CredentialsManagerError(code: .refreshFailed, cause: error), nil) + callback(.failure(CredentialsManagerError(code: .refreshFailed, cause: error))) } } } diff --git a/Auth0/Handlers.swift b/Auth0/Handlers.swift index f2228bc8..f6b7aac5 100644 --- a/Auth0/Handlers.swift +++ b/Auth0/Handlers.swift @@ -7,9 +7,10 @@ func plainJson(from response: Response, callback: Request<[ } else { callback(.failure(AuthenticationError(from: response))) } - - } catch let error { + } catch let error as AuthenticationError { callback(.failure(error)) + } catch { + callback(.failure(AuthenticationError(cause: error))) } } @@ -24,9 +25,10 @@ func codable(from response: Response, callback: } else { callback(.failure(AuthenticationError(from: response))) } - - } catch let error { + } catch let error as AuthenticationError { callback(.failure(error)) + } catch { + callback(.failure(AuthenticationError(cause: error))) } } @@ -37,9 +39,10 @@ func authenticationObject(from response: Response, callback: Reques } else { callback(.failure(AuthenticationError(from: response))) } - - } catch let error { + } catch let error as AuthenticationError { callback(.failure(error)) + } catch { + callback(.failure(AuthenticationError(cause: error))) } } @@ -62,9 +66,11 @@ func noBody(from response: Response, callback: Request(_ key: String) -> T? { + public subscript(_ key: String) -> T? { return self.info[key] as? T } diff --git a/Auth0/OAuth2Grant.swift b/Auth0/OAuth2Grant.swift index c168ec26..7d35bcd7 100644 --- a/Auth0/OAuth2Grant.swift +++ b/Auth0/OAuth2Grant.swift @@ -1,9 +1,8 @@ import Foundation -import JWTDecode protocol OAuth2Grant { var defaults: [String: String] { get } - func credentials(from values: [String: String], callback: @escaping (Auth0Result) -> Void) + func credentials(from values: [String: String], callback: @escaping (WebAuthResult) -> Void) func values(fromComponents components: URLComponents) -> [String: String] } @@ -63,10 +62,9 @@ struct PKCE: OAuth2Grant { self.defaults = newDefaults } - func credentials(from values: [String: String], callback: @escaping (Auth0Result) -> Void) { + func credentials(from values: [String: String], callback: @escaping (WebAuthResult) -> Void) { guard let code = values["code"] else { - let string = "No code found in parameters \(values)" - return callback(.failure(AuthenticationError(description: string))) + return callback(.failure(WebAuthError(code: .noAuthorizationCode(values)))) } let authentication = self.authentication let verifier = self.verifier @@ -81,7 +79,7 @@ struct PKCE: OAuth2Grant { .tokenExchange(withCode: code, codeVerifier: verifier, redirectURI: redirectUrlString) .start { result in switch result { - case .failure(let error as AuthenticationError) where error.localizedDescription == "Unauthorized": + case .failure(let error) where error.localizedDescription == "Unauthorized": // Special case for PKCE when the correct method for token endpoint authentication is not set (it should be None) return callback(.failure(WebAuthError(code: .pkceNotAllowed))) case .failure(let error): return callback(.failure(WebAuthError(code: .other, cause: error))) @@ -90,7 +88,7 @@ struct PKCE: OAuth2Grant { if let error = error { return callback(.failure(WebAuthError(code: .idTokenValidationFailed, cause: error))) } - callback(result) + callback(.success(credentials)) } } } diff --git a/Auth0/Request.swift b/Auth0/Request.swift index e4931b6c..eaccb174 100644 --- a/Auth0/Request.swift +++ b/Auth0/Request.swift @@ -15,7 +15,7 @@ import Foundation ``` */ public struct Request: Requestable { - public typealias Callback = (Auth0Result) -> Void + public typealias Callback = (Result) -> Void let session: URLSession let url: URL diff --git a/Auth0/Requestable.swift b/Auth0/Requestable.swift index e06eac70..fe438d30 100644 --- a/Auth0/Requestable.swift +++ b/Auth0/Requestable.swift @@ -2,6 +2,7 @@ import Foundation protocol Requestable { associatedtype ResultType + associatedtype ErrorType: Auth0APIError - func start(_ callback: @escaping (Auth0Result) -> Void) + func start(_ callback: @escaping (Result) -> Void) } diff --git a/Auth0/Response.swift b/Auth0/Response.swift index 13af2d7c..e37ca71b 100644 --- a/Auth0/Response.swift +++ b/Auth0/Response.swift @@ -16,7 +16,7 @@ struct Response { let error: Error? func result() throws -> Any? { - guard error == nil else { throw E(error: error!, statusCode: response?.statusCode) } + guard error == nil else { throw E(cause: error!, statusCode: response?.statusCode) } guard let response = self.response else { throw E(description: nil) } guard (200...300).contains(response.statusCode) else { if let json = json(data) as? [String: Any] { diff --git a/Auth0/WebAuth.swift b/Auth0/WebAuth.swift index 659faf62..e55f7ba2 100644 --- a/Auth0/WebAuth.swift +++ b/Auth0/WebAuth.swift @@ -23,14 +23,15 @@ import Foundation ``` + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle - returns: Auth0 WebAuth component - important: Calling this method without a valid `Auth0.plist` will crash your application */ -public func webAuth(bundle: Bundle = Bundle.main) -> WebAuth { +public func webAuth(session: URLSession = .shared, bundle: Bundle = Bundle.main) -> WebAuth { let values = plistValues(bundle: bundle)! - return webAuth(clientId: values.clientId, domain: values.domain) + return webAuth(clientId: values.clientId, domain: values.domain, session: session) } /** @@ -42,11 +43,12 @@ public func webAuth(bundle: Bundle = Bundle.main) -> WebAuth { - parameter clientId: Id of your Auth0 client - parameter domain: name of your Auth0 domain + - parameter session: instance of URLSession used for networking. By default it will use the shared URLSession - returns: Auth0 WebAuth component */ -public func webAuth(clientId: String, domain: String) -> WebAuth { - return Auth0WebAuth(clientId: clientId, url: .a0_url(domain)) +public func webAuth(clientId: String, domain: String, session: URLSession = .shared) -> WebAuth { + return Auth0WebAuth(clientId: clientId, url: .a0_url(domain), session: session) } /// WebAuth Authentication using Auth0 @@ -189,7 +191,7 @@ public protocol WebAuth: Trackable, Loggable { - parameter callback: callback called with the result of the WebAuth flow */ - func start(_ callback: @escaping (Auth0Result) -> Void) + func start(_ callback: @escaping (WebAuthResult) -> Void) /** Removes Auth0 session and optionally remove the Identity Provider session. diff --git a/Auth0/WebAuthError.swift b/Auth0/WebAuthError.swift index da5ee560..588e8a28 100644 --- a/Auth0/WebAuthError.swift +++ b/Auth0/WebAuthError.swift @@ -10,6 +10,7 @@ public struct WebAuthError: Auth0Error { case noBundleIdentifier case malformedInvitationURL(String) case userCancelled + case noAuthorizationCode([String: String]) case pkceNotAllowed case idTokenValidationFailed case other @@ -40,6 +41,7 @@ public struct WebAuthError: Auth0Error { case .malformedInvitationURL(let url): return "The invitation URL (\(url)) is missing the required query " + "parameters 'invitation' and 'organization'." case .userCancelled: return "User cancelled Web Authentication." + case .noAuthorizationCode(let values): return "No authorization code found in \(values)" case .pkceNotAllowed: return "Unable to complete authentication with PKCE. PKCE support can be enabled by " + "setting Application Type to 'Native' and Token Endpoint Authentication Method to 'None' for this app " + "in the Auth0 Dashboard." @@ -52,6 +54,7 @@ public struct WebAuthError: Auth0Error { public static let malformedInvitationURL: WebAuthError = .init(code: .malformedInvitationURL("")) public static let userCancelled: WebAuthError = .init(code: .userCancelled) public static let pkceNotAllowed: WebAuthError = .init(code: .pkceNotAllowed) + public static let noAuthorizationCode: WebAuthError = .init(code: .noAuthorizationCode([:])) public static let idTokenValidationFailed: WebAuthError = .init(code: .idTokenValidationFailed) public static let other: WebAuthError = .init(code: .other) public static let unknown: WebAuthError = .init(code: .unknown("")) diff --git a/Auth0Tests/AuthenticationSpec.swift b/Auth0Tests/AuthenticationSpec.swift index 2c05939d..4774f303 100644 --- a/Auth0Tests/AuthenticationSpec.swift +++ b/Auth0Tests/AuthenticationSpec.swift @@ -10,6 +10,7 @@ import OHHTTPStubsSwift private let ClientId = "CLIENT_ID" private let Domain = "samples.auth0.com" +private let DomainURL = URL(string: "https://\(Domain)")! private let Phone = "+144444444444" private let ValidPassword = "I.O.U. a password" @@ -26,7 +27,7 @@ private let PasswordlessGrantType = "http://auth0.com/oauth/grant-type/passwordl class AuthenticationSpec: QuickSpec { override func spec() { - let auth: Authentication = Auth0Authentication(clientId: ClientId, url: URL(string: "https://\(Domain)")!) + let auth: Authentication = Auth0Authentication(clientId: ClientId, url: DomainURL) beforeEach { stub(condition: isHost(Domain)) { _ in @@ -38,6 +39,30 @@ class AuthenticationSpec: QuickSpec { HTTPStubs.removeAllStubs() } + describe("init") { + + it("should init with client id & url") { + let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL) + expect(authentication.clientId) == ClientId + expect(authentication.url) == DomainURL + } + + it("should init with client id, url & session") { + let session = URLSession(configuration: URLSession.shared.configuration) + let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL, session: session) + expect(authentication.session).to(be(session)) + } + + it("should init with client id, url & telemetry") { + let telemetryInfo = "info" + var telemetry = Telemetry() + telemetry.info = telemetryInfo + let authentication = Auth0Authentication(clientId: ClientId, url: DomainURL, telemetry: telemetry) + expect(authentication.telemetry.info) == telemetryInfo + } + + } + describe("login MFA OTP") { beforeEach { diff --git a/Auth0Tests/BaseTransactionSpec.swift b/Auth0Tests/BaseTransactionSpec.swift index 0aa5b93b..7c260064 100644 --- a/Auth0Tests/BaseTransactionSpec.swift +++ b/Auth0Tests/BaseTransactionSpec.swift @@ -17,8 +17,8 @@ class BaseTransactionSpec: QuickSpec { override func spec() { var transaction: BaseTransaction! - var result: Auth0Result? = nil - let callback: (Auth0Result) -> () = { result = $0 } + var result: WebAuthResult? = nil + let callback: (WebAuthResult) -> () = { result = $0 } let authentication = Auth0Authentication(clientId: ClientId, url: Domain) let generator = ChallengeGenerator() let handler = PKCE(authentication: authentication, diff --git a/Auth0Tests/CredentialsManagerSpec.swift b/Auth0Tests/CredentialsManagerSpec.swift index d172ed6d..04bf6665 100644 --- a/Auth0Tests/CredentialsManagerSpec.swift +++ b/Auth0Tests/CredentialsManagerSpec.swift @@ -111,8 +111,8 @@ class CredentialsManagerSpec: QuickSpec { it("should clear credentials and revoke the refresh token") { waitUntil(timeout: Timeout) { done in - credentialsManager.revoke { - expect($0).to(beNil()) + credentialsManager.revoke { error in + expect(error).to(beNil()) expect(credentialsManager.hasValid()).to(beFalse()) done() } @@ -123,8 +123,8 @@ class CredentialsManagerSpec: QuickSpec { _ = credentialsManager.clear() waitUntil(timeout: Timeout) { done in - credentialsManager.revoke { - expect($0).to(beNil()) + credentialsManager.revoke { error in + expect(error).to(beNil()) expect(credentialsManager.hasValid()).to(beFalse()) done() } @@ -141,8 +141,8 @@ class CredentialsManagerSpec: QuickSpec { _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.revoke { - expect($0).to(beNil()) + credentialsManager.revoke { error in + expect(error).to(beNil()) expect(credentialsManager.hasValid()).to(beFalse()) done() } @@ -150,16 +150,13 @@ class CredentialsManagerSpec: QuickSpec { } it("should return the failure if the token could not be revoked, and not clear credentials") { + let cause = AuthenticationError(description: "Revoke failed", statusCode: 400) stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in return authFailure(code: "400", description: "Revoke failed") } - waitUntil(timeout: Timeout) { done in - credentialsManager.revoke { - expect($0).to(matchError( - CredentialsManagerError(code: .revokeFailed, cause: AuthenticationError(description: "Revoke failed", statusCode: 400))) - ) - + credentialsManager.revoke { error in + expect(error).to(matchError(CredentialsManagerError(code: .revokeFailed, cause: cause))) expect(credentialsManager.hasValid()).to(beTrue()) done() } @@ -182,21 +179,15 @@ class CredentialsManagerSpec: QuickSpec { expect(secondaryCredentialsManager.store(credentials: secondaryCredentials)).to(beTrue()) waitUntil(timeout: .seconds(200)) { done in - credentialsManager.credentials { - expect($0).to(beNil()) - expect($1?.accessToken) == AccessToken - expect($1?.refreshToken) == RefreshToken - expect($1?.idToken) == IdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() } } waitUntil(timeout: Timeout) { done in - secondaryCredentialsManager.credentials { - expect($0).to(beNil()) - expect($1?.accessToken) == "SecondaryAccessToken" - expect($1?.refreshToken) == "SecondaryRefreshToken" - expect($1?.idToken) == "SecondaryIdToken" + secondaryCredentialsManager.credentials { result in + expect(result).to(haveCredentials("SecondaryAccessToken", "SecondaryIdToken", "SecondaryRefreshToken")) done() } } @@ -215,21 +206,15 @@ class CredentialsManagerSpec: QuickSpec { expect(credentialsManager.store(credentials: credentials)).to(beTrue()) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { - expect($0).to(beNil()) - expect($1?.accessToken) == AccessToken - expect($1?.refreshToken) == RefreshToken - expect($1?.idToken) == IdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() } } waitUntil(timeout: Timeout) { done in - secondaryCredentialsManager.credentials { - expect($0).to(beNil()) - expect($1?.accessToken) == AccessToken - expect($1?.refreshToken) == RefreshToken - expect($1?.idToken) == IdToken + secondaryCredentialsManager.credentials { result in + expect(result).to(haveCredentials(AccessToken, IdToken, RefreshToken)) done() } } @@ -375,12 +360,7 @@ class CredentialsManagerSpec: QuickSpec { describe("retrieval") { - var error: Error? - var newCredentials: Credentials? - beforeEach { - error = nil - newCredentials = nil stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn * 2) }.name = "renew success" @@ -392,24 +372,24 @@ class CredentialsManagerSpec: QuickSpec { it("should error when no credentials stored") { A0SimpleKeychain().clearAll() - credentialsManager.credentials { error = $0; newCredentials = $1 } - expect(error).to(matchError(CredentialsManagerError.noCredentials)) - expect(newCredentials).toEventually(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noCredentials))) + } } it("should error when token expired") { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: nil, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) - credentialsManager.credentials { error = $0; newCredentials = $1 } - expect(error).to(matchError(CredentialsManagerError.noCredentials)) - expect(newCredentials).toEventually(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentialsManagerError(CredentialsManagerError(code: .noCredentials))) + } } it("should return original credentials as not expired") { _ = credentialsManager.store(credentials: credentials) - credentialsManager.credentials { error = $0; newCredentials = $1 } - expect(error).to(beNil()) - expect(newCredentials).toEventuallyNot(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentials()) + } } #if os(iOS) @@ -420,13 +400,14 @@ class CredentialsManagerSpec: QuickSpec { } it("should error when touch unavailable") { + let error = CredentialsManagerError(code: .biometricsFailed, + cause: LAError(LAError.biometryNotAvailable)) credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(matchError(CredentialsManagerError(code: .biometricsFailed, cause: LAError(LAError.biometryNotAvailable)))) - expect(newCredentials).to(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentialsManagerError(error)) done() } } @@ -440,11 +421,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == RefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, RefreshToken)) done() } } @@ -457,11 +435,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() } } @@ -471,12 +446,10 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) - credentialsManager.credentials { - expect($1?.accessToken) == NewAccessToken - expect($1?.refreshToken) == RefreshToken - expect($1?.idToken) == NewIdToken + credentialsManager.credentials { result in + expect(result).to(beSuccessful()) + credentialsManager.credentials { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, RefreshToken)) done() } } @@ -484,13 +457,14 @@ class CredentialsManagerSpec: QuickSpec { } it("should yield error on failed renew") { + let error = CredentialsManagerError(code: .refreshFailed, + cause: AuthenticationError(description: "")) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return authFailure(code: "invalid_request", description: "missing_params") }.name = "renew failed" credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(matchError(CredentialsManagerError(code: .refreshFailed, cause: AuthenticationError(description: "")))) - expect(newCredentials).to(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentialsManagerError(error)) done() } } @@ -504,11 +478,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(parameters: ["some_id": someId]) { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials(parameters: ["some_id": someId]) { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() } } @@ -523,9 +494,8 @@ class CredentialsManagerSpec: QuickSpec { it("should not yield a new access token by default") { waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == AccessToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(AccessToken)) done() } } @@ -535,9 +505,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile") _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(withScope: nil) { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == AccessToken + credentialsManager.credentials(withScope: nil) { result in + expect(result).to(haveCredentials(AccessToken)) done() } } @@ -547,9 +516,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile") _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(withScope: "openid profile") { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == AccessToken + credentialsManager.credentials(withScope: "openid profile") { result in + expect(result).to(haveCredentials(AccessToken)) done() } } @@ -559,9 +527,8 @@ class CredentialsManagerSpec: QuickSpec { credentials = Credentials(accessToken: AccessToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: ExpiresIn), scope: "openid profile offline_access") _ = credentialsManager.store(credentials: credentials) waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(withScope: "openid profile") { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == NewAccessToken + credentialsManager.credentials(withScope: "openid profile") { result in + expect(result).to(haveCredentials(NewAccessToken)) done() } } @@ -569,9 +536,8 @@ class CredentialsManagerSpec: QuickSpec { it("should not yield a new access token with a min ttl less than its expiry") { waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(minTTL: ValidTTL) { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == AccessToken + credentialsManager.credentials(minTTL: ValidTTL) { result in + expect(result).to(haveCredentials(AccessToken)) done() } } @@ -579,9 +545,8 @@ class CredentialsManagerSpec: QuickSpec { it("should yield a new access token with a min ttl greater than its expiry") { waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(minTTL: InvalidTTL) { error, newCredentials in - expect(error).to(beNil()) - expect(newCredentials?.accessToken) == NewAccessToken + credentialsManager.credentials(minTTL: InvalidTTL) { result in + expect(result).to(haveCredentials(NewAccessToken)) done() } } @@ -590,15 +555,13 @@ class CredentialsManagerSpec: QuickSpec { it("should fail to yield a renewed access token with a min ttl greater than its expiry") { let minTTL = 100_000 // The dates are not mocked, so they won't match exactly - let expectedError = CredentialsManagerError(code: .largeMinTTL(minTTL: minTTL, lifetime: Int(ExpiresIn - 1))) + let error = CredentialsManagerError(code: .largeMinTTL(minTTL: minTTL, lifetime: Int(ExpiresIn - 1))) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: nil, expiresIn: ExpiresIn) } waitUntil(timeout: Timeout) { done in - credentialsManager.credentials(withScope: nil, minTTL: minTTL) { error, newCredentials in - expect(error?.code) == expectedError.code - expect(error?.localizedDescription) == "The minTTL requested (\(minTTL)s) is greater than the lifetime of the renewed Access Token (\(Int(ExpiresIn - 1))s). Request a lower minTTL or increase the 'Token Expiration' setting of your Auth0 API in the dashboard." - expect(newCredentials).to(beNil()) + credentialsManager.credentials(withScope: nil, minTTL: minTTL) { result in + expect(result).to(haveCredentialsManagerError(error)) done() } } @@ -615,13 +578,12 @@ class CredentialsManagerSpec: QuickSpec { return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) } waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials).toNot(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentials()) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": NewRefreshToken])) { _ in return authFailure() } } - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) + credentialsManager.credentials { result in + expect(result).to(beSuccessful()) done() } } @@ -634,16 +596,12 @@ class CredentialsManagerSpec: QuickSpec { return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) } waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": NewRefreshToken])) { _ in return authFailure() } } - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() } } @@ -654,16 +612,14 @@ class CredentialsManagerSpec: QuickSpec { _ = credentialsManager.store(credentials: credentials) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return authFailure() } waitUntil(timeout: Timeout) { done in - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).toNot(beNil()) - expect(newCredentials).to(beNil()) + credentialsManager.credentials { result in + expect(result).to(beFailure()) stub(condition: isToken(Domain) && hasAtLeast(["refresh_token": RefreshToken])) { _ in return authResponse(accessToken: NewAccessToken, idToken: NewIdToken, refreshToken: NewRefreshToken, expiresIn: ExpiresIn) } } - credentialsManager.credentials { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials).toNot(beNil()) + credentialsManager.credentials { result in + expect(result).to(haveCredentials()) done() } } @@ -684,14 +640,13 @@ class CredentialsManagerSpec: QuickSpec { } waitUntil(timeout: Timeout) { done in DispatchQueue.global(qos: .utility).sync { - credentialsManager.credentials(parameters: ["request": "first"]) { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials).toNot(beNil()) + credentialsManager.credentials(parameters: ["request": "first"]) { result in + expect(result).to(haveCredentials()) } } DispatchQueue.global(qos: .background).sync { - credentialsManager.credentials(parameters: ["request": "second"]) { error = $0; newCredentials = $1 - expect(error).to(beNil()) + credentialsManager.credentials(parameters: ["request": "second"]) { result in + expect(result).to(beSuccessful()) done() } } @@ -709,17 +664,13 @@ class CredentialsManagerSpec: QuickSpec { } waitUntil(timeout: Timeout) { done in DispatchQueue.global(qos: .utility).sync { - credentialsManager.credentials(parameters: ["request": "first"]) { error = $0; newCredentials = $1 - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials(parameters: ["request": "first"]) { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) } } DispatchQueue.global(qos: .background).sync { - credentialsManager.credentials(parameters: ["request": "second"]) { error = $0; newCredentials = $1 - expect(newCredentials?.accessToken) == NewAccessToken - expect(newCredentials?.refreshToken) == NewRefreshToken - expect(newCredentials?.idToken) == NewIdToken + credentialsManager.credentials(parameters: ["request": "second"]) { result in + expect(result).to(haveCredentials(NewAccessToken, NewIdToken, NewRefreshToken)) done() } } @@ -737,15 +688,13 @@ class CredentialsManagerSpec: QuickSpec { } waitUntil(timeout: Timeout) { done in DispatchQueue.global(qos: .utility).sync { - credentialsManager.credentials(parameters: ["request": "first"]) { error = $0; newCredentials = $1 - expect(error).toNot(beNil()) - expect(newCredentials).to(beNil()) + credentialsManager.credentials(parameters: ["request": "first"]) { result in + expect(result).to(beFailure()) } } DispatchQueue.global(qos: .background).sync { - credentialsManager.credentials(parameters: ["request": "second"]) { error = $0; newCredentials = $1 - expect(error).to(beNil()) - expect(newCredentials).toNot(beNil()) + credentialsManager.credentials(parameters: ["request": "second"]) { result in + expect(result).to(haveCredentials()) done() } } diff --git a/Auth0Tests/ManagementSpec.swift b/Auth0Tests/ManagementSpec.swift index 539fa34c..fe48ca4b 100644 --- a/Auth0Tests/ManagementSpec.swift +++ b/Auth0Tests/ManagementSpec.swift @@ -15,18 +15,29 @@ class ManagementSpec: QuickSpec { it("should init with token & url") { let management = Management(token: Token, url: DomainURL) - expect(management).toNot(beNil()) + expect(management.token) == Token + expect(management.url) == DomainURL } it("should init with token, url & session") { - let management = Management(token: Token, url: DomainURL, session: URLSession.shared) - expect(management).toNot(beNil()) + let session = URLSession(configuration: URLSession.shared.configuration) + let management = Management(token: Token, url: DomainURL, session: session) + expect(management.session).to(be(session)) + } + + it("should init with token, url & telemetry") { + let telemetryInfo = "info" + var telemetry = Telemetry() + telemetry.info = telemetryInfo + let management = Management(token: Token, url: DomainURL, telemetry: telemetry) + expect(management.telemetry.info) == telemetryInfo } it("should have bearer token in authorization header") { let management = Management(token: Token, url: DomainURL) expect(management.defaultHeaders["Authorization"]) == "Bearer \(Token)" } + } describe("object response handling") { @@ -38,7 +49,7 @@ class ManagementSpec: QuickSpec { it("should yield success with payload") { let response = Response(data: data, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(haveObjectWithAttributes(["key"])) } @@ -50,7 +61,7 @@ class ManagementSpec: QuickSpec { let data = "NOT JSON".data(using: .utf8)! let http = HTTPURLResponse(url: DomainURL, statusCode: 200, httpVersion: nil, headerFields: nil) let response = Response(data: data, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(beFailure()) } @@ -59,7 +70,7 @@ class ManagementSpec: QuickSpec { let data = "[{\"key\": \"value\"}]".data(using: .utf8)! let http = HTTPURLResponse(url: DomainURL, statusCode: 200, httpVersion: nil, headerFields: nil) let response = Response(data: data, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(beFailure()) } @@ -68,7 +79,7 @@ class ManagementSpec: QuickSpec { let data = "[{\"key\": \"value\"}]".data(using: .utf8)! let http = HTTPURLResponse(url: DomainURL, statusCode: 400, httpVersion: nil, headerFields: nil) let response = Response(data: data, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(beFailure()) } @@ -78,7 +89,7 @@ class ManagementSpec: QuickSpec { let data = try? JSONSerialization.data(withJSONObject: error, options: []) let http = HTTPURLResponse(url: DomainURL, statusCode: 400, httpVersion: nil, headerFields: nil) let response = Response(data: data, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(haveManagementError("error", description: "description", code: "code", statusCode: 400)) } @@ -86,14 +97,14 @@ class ManagementSpec: QuickSpec { it("should yield generic failure when no payload") { let http = HTTPURLResponse(url: DomainURL, statusCode: 400, httpVersion: nil, headerFields: nil) let response = Response(data: nil, response: http, error: nil) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(beFailure()) } it("should yield generic failure") { let response = Response(data: nil, response: nil, error: NSError(domain: "com.auth0", code: -99999, userInfo: nil)) - var actual: Auth0Result? = nil + var actual: ManagementResult? = nil management.managementObject(response: response) { actual = $0 } expect(actual).toEventually(beFailure()) } diff --git a/Auth0Tests/Matchers.swift b/Auth0Tests/Matchers.swift index 4478cf86..e2176942 100644 --- a/Auth0Tests/Matchers.swift +++ b/Auth0Tests/Matchers.swift @@ -9,7 +9,7 @@ import OHHTTPStubsSwift func hasAllOf(_ parameters: [String: String]) -> HTTPStubsTestBlock { return { request in - guard let payload = request.a0_payload else { return false } + guard let payload = request.payload else { return false } return parameters.count == payload.count && parameters.reduce(true, { (initial, entry) -> Bool in return initial && payload[entry.0] as? String == entry.1 }) @@ -18,7 +18,7 @@ func hasAllOf(_ parameters: [String: String]) -> HTTPStubsTestBlock { func hasAtLeast(_ parameters: [String: String]) -> HTTPStubsTestBlock { return { request in - guard let payload = request.a0_payload else { return false } + guard let payload = request.payload else { return false } let entries = parameters.filter { (key, _) in payload.contains { (name, _) in key == name } } return entries.count == parameters.count && entries.reduce(true, { (initial, entry) -> Bool in return initial && payload[entry.0] as? String == entry.1 @@ -32,7 +32,7 @@ func hasUserMetadata(_ metadata: [String: String]) -> HTTPStubsTestBlock { func hasObjectAttribute(_ name: String, value: [String: String]) -> HTTPStubsTestBlock { return { request in - guard let payload = request.a0_payload, let actualValue = payload[name] as? [String: Any] else { return false } + guard let payload = request.payload, let actualValue = payload[name] as? [String: Any] else { return false } return value.count == actualValue.count && value.reduce(true, { (initial, entry) -> Bool in guard let value = actualValue[entry.0] as? String else { return false } return initial && value == entry.1 @@ -42,7 +42,7 @@ func hasObjectAttribute(_ name: String, value: [String: String]) -> HTTPStubsTes func hasNoneOf(_ names: [String]) -> HTTPStubsTestBlock { return { request in - guard let payload = request.a0_payload else { return false } + guard let payload = request.payload else { return false } return payload.filter { names.contains($0.0) }.isEmpty } } @@ -138,126 +138,167 @@ func containItem(withName name: String, value: String? = nil) -> Predicate<[URLQ } } -func haveAuthenticationError(code: String, description: String) -> Predicate> { - return Predicate>.define("an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .failure(let cause as AuthenticationError) = actual { - return PredicateResult(bool: code == cause.code && description == cause.localizedDescription, message: failureMessage) +func haveAuthenticationError(code: String, description: String) -> Predicate> { + return Predicate>.define("an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in + return try beFailure(expression, failureMessage) { (error: AuthenticationError) -> Bool in + return code == error.code && description == error.localizedDescription } - return PredicateResult(status: .doesNotMatch, message: failureMessage) } } -func haveManagementError(_ error: String, description: String, code: String, statusCode: Int) -> Predicate> { - return beFailure("server error response") { (cause: ManagementError) in - return error == (cause["error"]) - && code == (cause["code"]) - && description == (cause["description"]) - && statusCode == (cause["statusCode"]) +#if WEB_AUTH_PLATFORM +func haveAuthenticationError(code: String, description: String) -> Predicate> { + return Predicate>.define("an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in + return try beFailure(expression, failureMessage) { (error: WebAuthError) -> Bool in + guard let cause = error.cause as? AuthenticationError else { return false } + return code == cause.code && description == cause.localizedDescription + } } } +#endif -func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Predicate> { - return Predicate>.define("a successful authentication result") { expression, failureMessage -> PredicateResult in - if let accessToken = accessToken { - _ = failureMessage.appended(message: " ") - } - if let idToken = idToken { - _ = failureMessage.appended(message: " ") - } - if let actual = try expression.evaluate(), case .success(let credentials) = actual { - return PredicateResult(bool: (accessToken == nil || credentials.accessToken == accessToken) && (idToken == nil || credentials.idToken == idToken), message: failureMessage) +func haveManagementError(_ cause: String, description: String, code: String, statusCode: Int) -> Predicate> { + return Predicate>.define("an error response with code <\(code)> and description <\(description)") { expression, failureMessage -> PredicateResult in + return try beFailure(expression, failureMessage) { (error: ManagementError) -> Bool in + return cause == (error["error"]) + && code == (error["code"]) + && description == (error["description"]) + && statusCode == (error["statusCode"]) } - return PredicateResult(status: .doesNotMatch, message: failureMessage) } } -func haveCreatedUser(_ email: String, username: String? = nil) -> Predicate> { - return Predicate>.define("have created user with email <\(email)>") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .success(let created) = actual { - return PredicateResult(bool: created.email == email && (username == nil || created.username == username), message: failureMessage) - } - return PredicateResult(status: .doesNotMatch, message: failureMessage) +func haveCredentialsManagerError(_ cause: CredentialsManagerError) -> Predicate> { + return Predicate>.define("an error result") { expression, failureMessage -> PredicateResult in + return try beFailure(expression, failureMessage) { (error: CredentialsManagerError) -> Bool in error == cause } } } -func beSuccessful() -> Predicate> { - return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .success = actual { - return PredicateResult(status: .matches, message: failureMessage) +func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Predicate> { + return Predicate>.define("a successful authentication result") { expression, failureMessage -> PredicateResult in + return try haveCredentials(accessToken: accessToken, + idToken: idToken, + refreshToken: nil, + expression, + failureMessage) + } +} + +#if WEB_AUTH_PLATFORM +func haveCredentials(_ accessToken: String? = nil, _ idToken: String? = nil) -> Predicate> { + return Predicate>.define("a successful authentication result") { expression, failureMessage -> PredicateResult in + return try haveCredentials(accessToken: accessToken, + idToken: idToken, + refreshToken: nil, + expression, + failureMessage) + } +} +#endif + +func haveCredentials(_ accessToken: String, _ idToken: String? = nil, _ refreshToken: String? = nil) -> Predicate> { + return Predicate>.define("a successful authentication result") { expression, failureMessage -> PredicateResult in + return try haveCredentials(accessToken: accessToken, + idToken: idToken, + refreshToken: refreshToken, + expression, + failureMessage) + } +} + +func haveCredentials() -> Predicate> { + return Predicate>.define("a successful authentication result") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) + } +} + +func haveCreatedUser(_ email: String, username: String? = nil) -> Predicate> { + return Predicate>.define("have created user with email <\(email)>") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) { (created: DatabaseUser) -> Bool in + return created.email == email && (username == nil || created.username == username) } - return PredicateResult(status: .doesNotMatch, message: failureMessage) } } -func beFailure(_ cause: String? = nil) -> Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +func beSuccessful() -> Predicate> { + return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) + } +} + +func beSuccessful() -> Predicate> { + return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) + } +} + +func beSuccessful() -> Predicate> { + return Predicate>.define("be a successful result") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) + } +} + +func beFailure(_ cause: String? = nil) -> Predicate> { + return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in if let cause = cause { _ = failureMessage.appended(message: " with cause \(cause)") } else { _ = failureMessage.appended(message: " from authentication api") } - if let actual = try expression.evaluate(), case .failure = actual { - return PredicateResult(status: .matches, message: failureMessage) - } - return PredicateResult(status: .doesNotMatch, message: failureMessage) + return try beFailure(expression, failureMessage) } } -func beFailure(_ cause: String? = nil, predicate: @escaping (AuthenticationError) -> Bool) -> Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +func beFailure(_ cause: String? = nil) -> Predicate> { + return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in if let cause = cause { _ = failureMessage.appended(message: " with cause \(cause)") } else { - _ = failureMessage.appended(message: " from authentication api") - } - if let actual = try expression.evaluate(), case .failure(let cause as AuthenticationError) = actual { - return PredicateResult(bool: predicate(cause), message: failureMessage) + _ = failureMessage.appended(message: " from management api") } - return PredicateResult(status: .doesNotMatch, message: failureMessage) + return try beFailure(expression, failureMessage) } } -func beFailure(_ cause: String? = nil, predicate: @escaping (ManagementError) -> Bool) -> Predicate> { - return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in +#if WEB_AUTH_PLATFORM +func beFailure(_ cause: String? = nil) -> Predicate> { + return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in if let cause = cause { _ = failureMessage.appended(message: " with cause \(cause)") } else { - _ = failureMessage.appended(message: " from management api") - } - if let actual = try expression.evaluate(), case .failure(let cause as ManagementError) = actual { - return PredicateResult(bool: predicate(cause), message: failureMessage) + _ = failureMessage.appended(message: " from web auth") } - return PredicateResult(status: .doesNotMatch, message: failureMessage) + return try beFailure(expression, failureMessage) } } +#endif -func haveProfile(_ sub: String) -> Predicate> { - return Predicate>.define("have userInfo for sub: <\(sub)>") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .success(let userInfo) = actual { - return PredicateResult(bool: userInfo.sub == sub, message: failureMessage) - } - return PredicateResult(status: .doesNotMatch, message: failureMessage) +func beFailure() -> Predicate> { + return Predicate>.define("be a failure result") { expression, failureMessage -> PredicateResult in + _ = failureMessage.appended(message: " from credentials manager") + return try beFailure(expression, failureMessage) + } +} + +func haveProfile(_ sub: String) -> Predicate> { + return Predicate>.define("have userInfo for sub: <\(sub)>") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) { (userInfo: UserInfo) -> Bool in userInfo.sub == sub } } } -func haveObjectWithAttributes(_ attributes: [String]) -> Predicate> { - return Predicate>.define("have attribues \(attributes)") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .success(let value) = actual { - let outcome = Array(value.keys).reduce(true, { (initial, value) -> Bool in +func haveObjectWithAttributes(_ attributes: [String]) -> Predicate> { + return Predicate>.define("have attributes \(attributes)") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) { (value: [String: Any]) -> Bool in + return Array(value.keys).reduce(true, { (initial, value) -> Bool in return initial && attributes.contains(value) }) - return PredicateResult(bool: outcome, message: failureMessage) } - return PredicateResult(status: .doesNotMatch, message: failureMessage) } } -func haveJWKS() -> Predicate> { - return Predicate>.define("have a JWKS object with at least one key") { expression, failureMessage -> PredicateResult in - if let actual = try expression.evaluate(), case .success(let jwks) = actual { - return PredicateResult(bool: !jwks.keys.isEmpty, message: failureMessage) - } - return PredicateResult(status: .doesNotMatch, message: failureMessage) +func haveJWKS() -> Predicate> { + return Predicate>.define("have a JWKS object with at least one key") { expression, failureMessage -> PredicateResult in + return try beSuccessful(expression, failureMessage) { (jwks: JWKS) -> Bool in !jwks.keys.isEmpty } } } @@ -274,10 +315,53 @@ func beURLSafeBase64() -> Predicate { } } +// MARK: - Private Functions + +private func beSuccessful(_ expression: Expression>, + _ message: ExpectationMessage, + predicate: @escaping (T) -> Bool = { _ in true }) throws -> PredicateResult { + if let actual = try expression.evaluate(), case .success(let value) = actual { + return PredicateResult(bool: predicate(value), message: message) + } + return PredicateResult(status: .doesNotMatch, message: message) +} + +private func beFailure(_ expression: Expression>, + _ message: ExpectationMessage, + predicate: @escaping (E) -> Bool = { _ in true }) throws -> PredicateResult { + if let actual = try expression.evaluate(), case .failure(let error) = actual { + return PredicateResult(bool: predicate(error), message: message) + } + return PredicateResult(status: .doesNotMatch, message: message) +} + +private func haveCredentials(accessToken: String?, + idToken: String?, + refreshToken: String?, + _ expression: Expression>, + _ message: ExpectationMessage) throws -> PredicateResult { + if let accessToken = accessToken { + _ = message.appended(message: " ") + } + if let idToken = idToken { + _ = message.appended(message: " ") + } + if let refreshToken = refreshToken { + _ = message.appended(message: " ") + } + return try beSuccessful(expression, message) { (credentials: Credentials) -> Bool in + return (accessToken == nil || credentials.accessToken == accessToken) + && (idToken == nil || credentials.idToken == idToken) + && (refreshToken == nil || credentials.refreshToken == refreshToken) + } +} + +// MARK: - Extensions + extension URLRequest { - var a0_payload: [String: Any]? { - get { - return URLProtocol.property(forKey: parameterPropertyKey, in: self) as? [String: Any] - } + + var payload: [String: Any]? { + return URLProtocol.property(forKey: parameterPropertyKey, in: self) as? [String: Any] } + } diff --git a/Auth0Tests/WebAuthSpec.swift b/Auth0Tests/WebAuthSpec.swift index 6b6002d9..7e32ce33 100644 --- a/Auth0Tests/WebAuthSpec.swift +++ b/Auth0Tests/WebAuthSpec.swift @@ -66,6 +66,36 @@ class WebAuthSpec: QuickSpec { override func spec() { + describe("init") { + + it("should init with client id & url") { + let webAuth = Auth0WebAuth(clientId: ClientId, url: DomainURL) + expect(webAuth.clientId) == ClientId + expect(webAuth.url) == DomainURL + } + + it("should init with client id, url & session") { + let session = URLSession(configuration: URLSession.shared.configuration) + let webAuth = Auth0WebAuth(clientId: ClientId, url: DomainURL, session: session) + expect(webAuth.session).to(be(session)) + } + + it("should init with client id, url & storage") { + let storage = TransactionStore() + let webAuth = Auth0WebAuth(clientId: ClientId, url: DomainURL, storage: storage) + expect(webAuth.storage).to(be(storage)) + } + + it("should init with client id, url & telemetry") { + let telemetryInfo = "info" + var telemetry = Telemetry() + telemetry.info = telemetryInfo + let webAuth = Auth0WebAuth(clientId: ClientId, url: DomainURL, telemetry: telemetry) + expect(webAuth.telemetry.info) == telemetryInfo + } + + } + describe("authorize URL") { itBehavesLike(ValidAuthorizeURLExample) { diff --git a/Package.swift b/Package.swift index 09341bb1..5be220d4 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( name: "Auth0", dependencies: ["SimpleKeychain", "JWTDecode"], path: "Auth0", - exclude: ["Info.plist", "Info-tvOS.plist"], + exclude: ["Info.plist"], swiftSettings: swiftSettings), .testTarget( name: "Auth0Tests", diff --git a/README.md b/README.md index 4c5abaa8..3c9e0a5e 100644 --- a/README.md +++ b/README.md @@ -293,11 +293,12 @@ Credentials will automatically be renewed (if expired) using the refresh token. > This method is thread-safe. ```swift -credentialsManager.credentials { error, credentials in - guard error == nil, let credentials = credentials else { - return print("Failed with \(error)") - } - print("Obtained credentials: \(credentials)") +credentialsManager.credentials { result in + switch result { + case .success(let credentials): + print("Obtained credentials: \(credentials)") + case .failure(let error): + print("Failed with \(error)") } ``` @@ -332,7 +333,8 @@ credentialsManager.enableBiometrics(withTitle: "Touch to Login") If needed, you are able to specify specific `LAPolicy` to be used - i.e. you might want to support FaceID, but allow fallback to pin code. ```swift -credentialsManager.enableBiometrics(withTitle: "Touch or enter pincode to Login", evaluationPolicy: .deviceOwnerAuthentication) +credentialsManager.enableBiometrics(withTitle: "Touch or enter pincode to Login", + evaluationPolicy: .deviceOwnerAuthentication) ``` ### Native Social Login diff --git a/V2_MIGRATION_GUIDE.md b/V2_MIGRATION_GUIDE.md index 4ca4328e..9513ff1f 100644 --- a/V2_MIGRATION_GUIDE.md +++ b/V2_MIGRATION_GUIDE.md @@ -213,6 +213,7 @@ switch error { #### Properties added - `.malformedInvitationURL` +- `.noAuthorizationCode` - `.idTokenValidationFailed` - `.other` @@ -236,7 +237,7 @@ switch error { switch error { case .revokeFailed: handleError(error.cause) // handle underlying error // ... - default: // handle unkwown errors, e.g. errors added in future versions + default: // handle unknown errors, e.g. errors added in future versions } ``` @@ -318,6 +319,8 @@ The `multifactorChallenge(mfaToken:types:authenticatorId:)` method lost its `cha ### Credentials Manager +#### Initializer + `CredentialsManager` now takes a `CredentialsStorage` protocol as it's storage argument rather than an instance of `SimpleKeychain`. This means you can now provide your own storage layer to `CredentialsManager`. @@ -341,6 +344,31 @@ class CustomStore: CredentialsStorage { let credentialsManager = CredentialsManager(authentication: authentication, storage: CustomStore()); ``` +#### `credentials(withScope:minTTL:parameters:callback)` + +This method now yields a `Result`, which is aliased to `CredentialsManagerResult`. + +##### Before + +```swift +credentialsManager.credentials { error, credentials in + guard error == nil, let credentials = credentials else { + return handleError(error) + } + ... // credentials retrieved +``` + +##### After + +```swift +credentialsManager.credentials { result in + switch result { + case .success(let credentials): ... // credentials retrieved + case .failure(let error): handleError(error) + } +} +``` + ## Behavior changes ### `openid` scope enforced on Web Auth From 303f7791b1336f7c714b5559dd8bf64ace25c93e Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 30 Nov 2021 20:42:06 -0300 Subject: [PATCH 2/3] Fix merge change --- Auth0/Auth0WebAuth.swift | 2 +- Auth0Tests/WebAuthSpec.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Auth0/Auth0WebAuth.swift b/Auth0/Auth0WebAuth.swift index b83f3517..d40a6c65 100644 --- a/Auth0/Auth0WebAuth.swift +++ b/Auth0/Auth0WebAuth.swift @@ -48,7 +48,7 @@ final class Auth0WebAuth: WebAuth { self.session = session self.storage = storage self.telemetry = telemetry - self.issuer = "\(url.absoluteString)/" + self.issuer = url.absoluteString } func connection(_ connection: String) -> Self { diff --git a/Auth0Tests/WebAuthSpec.swift b/Auth0Tests/WebAuthSpec.swift index 4498fcd0..62ae1560 100644 --- a/Auth0Tests/WebAuthSpec.swift +++ b/Auth0Tests/WebAuthSpec.swift @@ -344,7 +344,7 @@ class WebAuthSpec: QuickSpec { context("issuer") { it("should use the default issuer value") { - expect(newWebAuth().issuer).to(equal("\(DomainURL.absoluteString)/")) + expect(newWebAuth().issuer).to(equal(DomainURL.absoluteString)) } it("should use a custom issuer value") { From b10e67dce0dfd334fafc4c3be2fa13d53f7208a9 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Tue, 30 Nov 2021 21:58:12 -0300 Subject: [PATCH 3/3] Update migration guide --- V2_MIGRATION_GUIDE.md | 48 ++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/V2_MIGRATION_GUIDE.md b/V2_MIGRATION_GUIDE.md index 7c2ea43c..75232d0b 100644 --- a/V2_MIGRATION_GUIDE.md +++ b/V2_MIGRATION_GUIDE.md @@ -139,15 +139,6 @@ The `init(string: String?, statusCode: Int)` initializer was removed. The `a0_url(_:)` method is no longer public. -## Errors Removed - -### `WebAuthError` - -The following cases were removed, as they are no longer in use: - -- `.noNonceProvided` -- `.invalidIdTokenNonce` - ## Types changed - `UserInfo` was changed from class to struct. @@ -205,20 +196,25 @@ switch error { #### Properties removed -- `.cannotDismissWebAuthController` -- `.missingResponseParam` -- `.missingAccessToken` - `infoKey` - `errorDomain` - `errorCode` - `errorUserInfo` -#### Properties renamed +#### Error cases removed + +- `.noNonceProvided` +- `.invalidIdTokenNonce` +- `.cannotDismissWebAuthController` +- `.missingResponseParam` +- `.missingAccessToken` + +#### Error cases renamed - `.unknownError` was renamed to `.unknown`. - `.noBundleIdentifierFound` was renamed to `.noBundleIdentifier`. -#### Properties added +#### Error cases added - `.malformedInvitationURL` - `.noAuthorizationCode` @@ -249,12 +245,12 @@ switch error { } ``` -#### Properties renamed +#### Error cases renamed - `.failedRefresh` was renamed to `.refreshFailed`. - `.touchFailed` was renamed to `.biometricsFailed`. -#### Properties added +#### Error cases added - `.largeMinTTL` @@ -282,6 +278,10 @@ These properties have been removed: ### Authentication client +#### Errors + +The Authentication API client methods will now only yield errors of type `AuthenticationError`. The underlying error (if any) is available via the `cause: Error?` property of the `AuthenticationError`. + #### Removed `parameters` parameter The following methods lost the `parameters` parameter: @@ -325,8 +325,24 @@ In the following methods the `scope` parameter became non-optional (with a defau The `multifactorChallenge(mfaToken:types:authenticatorId:)` method lost its `channel` parameter, which is no longer necessary. +### Management client + +#### Errors + +The Management API client methods will now only yield errors of type `ManagementError`. The underlying error (if any) is available via the `cause: Error?` property of the `ManagementError`. + +### Web Auth + +#### Errors + +The Web Auth methods will now only yield errors of type `WebAuthError`. The underlying error (if any) is available via the `cause: Error?` property of the `WebAuthError`. + ### Credentials Manager +#### Errors + +The Credentials Manager methods will now only yield errors of type `CredentialsManagerError`. The underlying error (if any) is available via the `cause: Error?` property of the `CredentialsManagerError`. + #### Initializer `CredentialsManager` now takes a `CredentialsStorage` protocol as it's storage argument rather than an instance of `SimpleKeychain`.