diff --git a/.jazzy.yaml b/.jazzy.yaml new file mode 100644 index 000000000..6a02acc2f --- /dev/null +++ b/.jazzy.yaml @@ -0,0 +1,9 @@ +author: Auth0 +author_url: https://auth0.com +github_url: https://github.com/auth0/Auth0.swift +podspec: Auth0.podspec +output: Docs +clean: true +theme: fullwidth +sdk: iphoneos +undocumented_text: "" diff --git a/.shiprc b/.shiprc index a9c40495a..c7871c53d 100644 --- a/.shiprc +++ b/.shiprc @@ -4,6 +4,6 @@ "Auth0.podspec": [], "README.md": ["~> {MAJOR}.{MINOR}"] }, - "postbump": "bundle update", + "postbump": "bundle update && bundle exec jazzy", "prefixVersion": false } diff --git a/Auth0/Auth0.swift b/Auth0/Auth0.swift index ce5f9f96d..b6b6335e9 100644 --- a/Auth0/Auth0.swift +++ b/Auth0/Auth0.swift @@ -23,7 +23,7 @@ public typealias WebAuthResult = Result public typealias CredentialsManagerResult = Result /** - Default scope value used across Auth0.swift. + Default scope value used across Auth0.swift. Equals to `openid profile email`. */ public let defaultScope = "openid profile email" @@ -36,7 +36,7 @@ public let defaultScope = "openid profile email" - Parameters: - clientId: ClientId of your Auth0 application. - - domain: Domain of your Auth0 account. e.g.: 'samples.auth0.com'. + - domain: Domain of your Auth0 account, e.g. 'samples.auth0.com'. - session: Instance of `URLSession` used for networking. By default it will use the shared `URLSession`. - Returns: Auth0 Authentication API. */ @@ -134,7 +134,7 @@ public func users(token: String, session: URLSession = .shared, bundle: Bundle = - Parameters: - token: Token of Management API v2 with the correct allowed scopes to perform the desired action. - - domain: Domain of your Auth0 account. e.g.: 'samples.auth0.com'. + - domain: Domain of your Auth0 account, e.g. 'samples.auth0.com'. - session: Instance of `URLSession` used for networking. By default it will use the shared `URLSession`. - Returns: Auth0 Management API v2. */ @@ -185,7 +185,7 @@ public func webAuth(session: URLSession = .shared, bundle: Bundle = Bundle.main) - Parameters: - clientId: ClientId of your Auth0 application. - - domain: Domain of your Auth0 account. e.g.: 'samples.auth0.com'. + - domain: Domain of your Auth0 account, e.g. 'samples.auth0.com'. - session: Instance of `URLSession` used for networking. By default it will use the shared `URLSession`. - Returns: Auth0 WebAuth component. */ diff --git a/Auth0/Auth0Error.swift b/Auth0/Auth0Error.swift index 942d4016a..09ee0cc3d 100644 --- a/Auth0/Auth0Error.swift +++ b/Auth0/Auth0Error.swift @@ -4,10 +4,14 @@ let unknownError = "a0.sdk.internal_error.unknown" let nonJSONError = "a0.sdk.internal_error.plain" let emptyBodyError = "a0.sdk.internal_error.empty" +/** + Generic representation of Auth0 errors. ``AuthenticationError``, ``ManagementError``, ``WebAuthError``, and + ``CredentialsManagerError`` conform to this protocol. + */ public protocol Auth0Error: LocalizedError, CustomDebugStringConvertible { /** - The underlying `Error`, if any. Defaults to `nil`. + The underlying `Error`, if any. */ var cause: Error? { get } @@ -16,7 +20,7 @@ public protocol Auth0Error: LocalizedError, CustomDebugStringConvertible { public extension Auth0Error { /** - The underlying `Error`, if any. Defaults to `nil`. + Defaults to `nil`. */ var cause: Error? { return nil } @@ -37,9 +41,7 @@ public extension Auth0Error { } /** - Generic representation of Auth0 API errors. - - - Note: It's recommended to use either `AuthenticationError` or `ManagementError` for better error handling. + Generic representation of Auth0 API errors. ``AuthenticationError`` and ``ManagementError`` conform to this protocol. */ public protocol Auth0APIError: Auth0Error { diff --git a/Auth0/Authentication.swift b/Auth0/Authentication.swift index 598e6f96b..547f4e77e 100644 --- a/Auth0/Authentication.swift +++ b/Auth0/Authentication.swift @@ -2,23 +2,18 @@ import Foundation -/** - A created database user (just email, username and email verified flag). - */ +/// A created database user (just email, username and email verified flag). public typealias DatabaseUser = (email: String, username: String?, verified: Bool) -/** - Types of passwordless authentication. - - - code: Simple OTP code sent by email or sms. - - webLink: Regular Web HTTP link (Web only, uses redirect). - - iOSLink: Universal Link. - - androidLink: Android App Link. - */ +/// Types of passwordless authentication. public enum PasswordlessType: String { + /// Simple OTP code sent by email or sms. case code = "code" + /// Regular Web HTTP link (web only, uses redirect). case webLink = "link" + /// Universal Link. case iOSLink = "link_ios" + /// Android App Link. case androidLink = "link_android" } @@ -28,7 +23,10 @@ public enum PasswordlessType: String { - See: [Auth0 Auth API docs](https://auth0.com/docs/api/authentication) */ public protocol Authentication: Trackable, Loggable { + + /// The Auth0 client ID. var clientId: String { get } + /// The Auth0 domain URL. var url: URL { get } /** @@ -60,7 +58,7 @@ public protocol Authentication: Trackable, Loggable { .start { print($0) } ``` - When result is `.success`, its associated value will be a `Credentials` object. + When result is `.success`, its associated value will be a ``Credentials`` object. - Parameters: - email: Email the user used to start the passwordless login flow. @@ -101,7 +99,7 @@ public protocol Authentication: Trackable, Loggable { .start { print($0) } ``` - When result is `.success`, its associated value will be a `Credentials` object. + When result is `.success`, its associated value will be a ``Credentials`` object. - Parameters: - phoneNumber: Phone number the user used to start the passwordless login flow. @@ -357,6 +355,7 @@ public protocol Authentication: Trackable, Loggable { */ func loginDefaultDirectory(withUsername username: String, password: String, audience: String?, scope: String) -> Request + // swiftlint:disable:next function_parameter_count /** Creates a user in a Database connection. @@ -409,7 +408,6 @@ public protocol Authentication: Trackable, Loggable { - rootAttributes: Root attributes that will be added to the newly created user. See https://auth0.com/docs/api/authentication#signup for supported attributes. Will not overwrite existing parameters. - Returns: Request that will yield a created database user (just email, username and email verified flag). */ - // swiftlint:disable:next function_parameter_count func createUser(email: String, username: String?, password: String, connection: String, userMetadata: [String: Any]?, rootAttributes: [String: Any]?) -> Request /** @@ -608,461 +606,54 @@ public protocol Authentication: Trackable, Loggable { public extension Authentication { - /** - Logs in a user using an email and an OTP code received via email (last part of the passwordless login flow). - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(email: "support@auth0.com", code: "123456") - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can also specify audience and scope: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(email: "support@auth0.com", - code: "123456", - audience: "https://myapi.com/api", - scope: "openid profile email offline_access") - .start { print($0) } - ``` - - When result is `.success`, its associated value will be a `Credentials` object. - - - Parameters: - - email: Email the user used to start the passwordless login flow. - - code: One time password (OTP) code the user received via email. - - audience: API Identifier that the client is requesting access to. Default is `nil`. - - scope: Scope value requested when authenticating the user. Default is `openid profile email`. - - Returns: Authentication request that will yield Auth0 user's credentials. - - Requires: Passwordless OTP Grant `http://auth0.com/oauth/grant-type/passwordless/otp`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. - */ func login(email username: String, code otp: String, audience: String? = nil, scope: String = defaultScope) -> Request { return self.login(email: username, code: otp, audience: audience, scope: scope) } - /** - Logs in a user using a phone number and an OTP code received via sms (last part of the passwordless login flow). - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(phoneNumber: "+4599134762367", code: "123456") - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can also specify audience and scope: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(phoneNumber: "+4599134762367", - code: "123456", - audience: "https://myapi.com/api", - scope: "openid profile email offline_access") - .start { print($0) } - ``` - - When result is `.success`, its associated value will be a `Credentials` object. - - - Parameters: - - phoneNumber: Phone number the user used to start the passwordless login flow. - - code: One time password (OTP) code the user received via sms. - - audience: API Identifier that the client is requesting access to. Default is `nil`. - - scope: Scope value requested when authenticating the user. Default is `openid profile email`. - - Returns: Authentication request that will yield Auth0 user's credentials. - - Requires: Passwordless OTP Grant `http://auth0.com/oauth/grant-type/passwordless/otp`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. - */ func login(phoneNumber username: String, code otp: String, audience: String? = nil, scope: String = defaultScope) -> Request { return self.login(phoneNumber: username, code: otp, audience: audience, scope: scope) } - /** - Login using username and password in a realm. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(usernameOrEmail: "support@auth0.com", - password: "a secret password", - realm: "mydatabase") - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can also specify audience and scope: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(usernameOrEmail: "support@auth0.com", - password: "a secret password", - realm: "mydatabase", - audience: "https://myapi.com/api", - scope: "openid profile email offline_access") - .start { print($0) } - ``` - - - Parameters: - - username: Username or email used of the user to authenticate. - - password: Password of the user. - - realm: Domain of the realm or connection name. - - audience: API Identifier that the client is requesting access to. - - scope: Scope value requested when authenticating the user. - - Important: This only works if you have the OAuth 2.0 API Authorization flag on. - - Returns: Authentication request that will yield Auth0 user's credentials. - - Requires: Grant `http://auth0.com/oauth/grant-type/password-realm`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. - */ func login(usernameOrEmail username: String, password: String, realm: String, audience: String? = nil, scope: String = defaultScope) -> Request { return self.login(usernameOrEmail: username, password: password, realm: realm, audience: audience, scope: scope) } - /// Verifies multi-factor authentication (MFA) using an out-of-band (OOB) challenge (either Push notification, SMS, or Voice). - /// - /// ``` - /// Auth0 - /// .authentication(clientId: clientId, domain: "samples.auth0.com") - /// .login(withOOBCode: "123456", mfaToken: "mfa token") - /// .start { result in - /// switch result { - /// case .success(let credentials): - /// print("Obtained credentials: \(credentials)") - /// case .failure(let error): - /// print("Failed with \(error)") - /// } - /// } - /// ``` - /// - /// - Parameters: - /// - oobCode: The oob code received from the challenge request. - /// - mfaToken: Token returned when authentication fails due to MFA requirement. - /// - bindingCode: A code used to bind the side channel (used to deliver the challenge) with the main channel you are using to authenticate. This is usually an OTP-like code delivered as part of the challenge message. - /// - Returns: Authentication request that will yield Auth0 user's credentials. - /// - Requires: Grant `http://auth0.com/oauth/grant-type/mfa-oob`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. func login(withOOBCode oobCode: String, mfaToken: String, bindingCode: String? = nil) -> Request { return self.login(withOOBCode: oobCode, mfaToken: mfaToken, bindingCode: bindingCode) } - /// Request a challenge for multi-factor authentication (MFA) based on the challenge types supported by the application and user. - /// The `type` is how the user will get the challenge and prove possession. Supported challenge types include: - /// * `otp`: for one-time password (OTP) - /// * `oob`: for SMS/Voice messages or out-of-band (OOB) - /// - /// - Parameters: - /// - mfaToken: Token returned when authentication fails due to MFA requirement. - /// - types: A list of the challenges types accepted by your application. Accepted challenge types are `oob` or `otp`. Excluding this parameter means that your client application accepts all supported challenge types. - /// - authenticatorId: The ID of the authenticator to challenge. You can get the ID by querying the list of available authenticators for the user. - /// - Returns: A request that will yield a multi-factor challenge. func multifactorChallenge(mfaToken: String, types: [String]? = nil, authenticatorId: String? = nil) -> Request { return self.multifactorChallenge(mfaToken: mfaToken, types: types, authenticatorId: authenticatorId) } - /** - Authenticate a user with their Sign In With Apple authorization code. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(appleAuthorizationCode: authCode) - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - If you need to specify a scope: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(appleAuthorizationCode: authCode, - fullName: credentials.fullName, - scope: "openid profile email offline_access", - audience: "https://myapi.com/api") - .start { print($0) } - ``` - - - Parameters: - - authCode: Authorization Code retrieved from Apple Authorization. - - fullName: The full name property returned with the Apple ID Credentials. - - profile: Additional user profile data returned with the Apple ID Credentials. - - scope: Requested scope value when authenticating the user. By default is `openid profile email`. - - audience: API Identifier that the client is requesting access to. - - Returns: A request that will yield Auth0 user's credentials. - */ func login(appleAuthorizationCode authorizationCode: String, fullName: PersonNameComponents? = nil, profile: [String: Any]? = nil, audience: String? = nil, scope: String = defaultScope) -> Request { return self.login(appleAuthorizationCode: authorizationCode, fullName: fullName, profile: profile, audience: audience, scope: scope) } - /** - Authenticate a user with their Facebook session info access token and profile data. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(facebookSessionAccessToken: sessionAccessToken, profile: profile) - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - If you need to specify a scope or audience: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .login(facebookSessionAccessToken: sessionAccessToken, - scope: "openid profile email offline_access", - audience: "https://myapi.com/api") - .start { print($0) } - ``` - - - Parameters: - - sessionAccessToken: Session info access token retrieved from Facebook. - - profile: The user profile returned by Facebook. - - scope: Requested scope value when authenticating the user. By default is `openid profile email`. - - audience: API Identifier that the client is requesting access to. - - Returns: A request that will yield Auth0 user's credentials. - */ func login(facebookSessionAccessToken sessionAccessToken: String, profile: [String: Any], audience: String? = nil, scope: String = defaultScope) -> Request { return self.login(facebookSessionAccessToken: sessionAccessToken, profile: profile, audience: audience, scope: scope) } - /** - Login using username and password in the default directory. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .loginDefaultDirectory(withUsername: "support@auth0.com", - password: "a secret password") - .start { result in - switch result { - case .success(let credentials): - print("Obtained credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can also specify audience and scope: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .loginDefaultDirectory(withUsername: "support@auth0.com", - password: "a secret password", - audience: "https://myapi.com/api", - scope: "openid profile email offline_access") - .start { print($0) } - ``` - - - Parameters: - - username: Username or email used of the user to authenticate. - - password: Password of the user. - - audience: API Identifier that the client is requesting access to. - - scope: Scope value requested when authenticating the user. - - Important: This only works if you have the OAuth 2.0 API Authorization flag on. - - Returns: Authentication request that will yield Auth0 user's credentials. - */ func loginDefaultDirectory(withUsername username: String, password: String, audience: String? = nil, scope: String = defaultScope) -> Request { return self.loginDefaultDirectory(withUsername: username, password: password, audience: audience, scope: scope) } - /** - Creates a user in a Database connection. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .createUser(email: "support@auth0.com", - password: "a secret password", - connection: "Username-Password-Authentication") - .start { result in - switch result { - case .success(let user): - print("User signed up: \(user)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can also add additional metadata when creating the user: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .createUser(email: "support@auth0.com", - password: "a secret password", - connection: "Username-Password-Authentication", - userMetadata: ["first_name": "support"]) - .start { print($0) } - ``` - - If the database connection requires a username: - - ``` - Auth0 - .authentication(clientId, domain: "samples.auth0.com") - .createUser(email: "support@auth0.com", - username: "support", - password: "a secret password", - connection: "Username-Password-Authentication") - .start { print($0) } - ``` - - - Parameters: - - email: Email of the user to create. - - username: Username of the user if the connection requires username. By default is `nil`. - - password: Password for the new user. - - connection: Name where the user will be created (Database connection). - - userMetadata: Additional userMetadata parameters that will be added to the newly created user. - - rootAttributes: Root attributes that will be added to the newly created user. See https://auth0.com/docs/api/authentication#signup for supported attributes. Will not overwrite existing parameters. - - Returns: Request that will yield a created database user (just email, username and email verified flag). - */ func createUser(email: String, username: String? = nil, password: String, connection: String, userMetadata: [String: Any]? = nil, rootAttributes: [String: Any]? = nil) -> Request { return self.createUser(email: email, username: username, password: password, connection: connection, userMetadata: userMetadata, rootAttributes: rootAttributes) } - /** - Creates a user in a Database connection. - - - Parameters: - - email: Email of the user to create. - - username: Username of the user if the connection requires username. By default is `nil`. - - password: Password for the new user. - - connection: Name where the user will be created (Database connection). - - userMetadata: Additional userMetadata parameters that will be added to the newly created user. - - Returns: Request that will yield a created database user (just email, username and email verified flag). - */ func createUser(email: String, username: String? = nil, password: String, connection: String, userMetadata: [String: Any]? = nil) -> Request { return self.createUser(email: email, username: username, password: password, connection: connection, userMetadata: userMetadata, rootAttributes: nil) } - /** - Starts passwordless authentication by sending an email with a OTP code. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .startPasswordless(email: "support@auth0.com") - .start { print($0) } - ``` - - If you have configured iOS Universal Links: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .startPasswordless(email: "support@auth0.com", type: .iOSLink) - .start { print($0) } - ``` - - - Parameters: - - email: Email where to send the code or link. - - type: Type of passwordless authentication. By default is `code`. - - connection: Name of the passwordless connection. By default is 'email'. - - Returns: A request. - - Requires: Passwordless OTP Grant `http://auth0.com/oauth/grant-type/passwordless/otp`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. - */ func startPasswordless(email: String, type: PasswordlessType = .code, connection: String = "email", parameters: [String: Any] = [:]) -> Request { return self.startPasswordless(email: email, type: type, connection: connection, parameters: parameters) } - /** - Starts passwordless authentication by sending an sms with an OTP code. - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .startPasswordless(phoneNumber: "+12025550135") - .start { print($0) } - ``` - - If you have configured iOS Universal Links: - - ``` - Auth0 - .authentication(clientId: clientId, domain: "samples.auth0.com") - .startPasswordless(phoneNumber: "+12025550135", type: .iOSLink) - .start { print($0) } - ``` - - - Parameters: - - phoneNumber: Phone number where to send the sms with code or link. - - type: Type of passwordless authentication. By default is `code`. - - connection: Name of the passwordless connection. By default is 'sms'. - - Returns: A request. - - Requires: Passwordless OTP Grant `http://auth0.com/oauth/grant-type/passwordless/otp`. Check [our documentation](https://auth0.com/docs/configure/applications/application-grant-types) for more info and how to enable it. - */ func startPasswordless(phoneNumber: String, type: PasswordlessType = .code, connection: String = "sms") -> Request { return self.startPasswordless(phoneNumber: phoneNumber, type: type, connection: connection) } - /** - Renew the user's credentials with a `refresh_token` grant for `/oauth/token`. - - ``` - Auth0 - .renew(withRefreshToken: refreshToken, scope: "openid profile email offline_access read:users") - .start { result in - switch result { - case .success(let credentials): - print("Obtained new credentials: \(credentials)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - To ask the same scopes requested when the refresh token was issued: - - ``` - Auth0 - .renew(withRefreshToken: refreshToken) - .start { print($0) } - ``` - - - Parameters: - - refreshToken: The client's refresh token. - - scope: Scopes to request for the new tokens. By default is `nil`, which will ask for the same ones requested during Auth. - - Important: This method only works for a refresh token obtained after auth with OAuth 2.0 API Authorization. - - Returns: A request that will yield Auth0 user's credentials. - */ func renew(withRefreshToken refreshToken: String, scope: String? = nil) -> Request { return self.renew(withRefreshToken: refreshToken, scope: scope) } diff --git a/Auth0/AuthenticationError.swift b/Auth0/AuthenticationError.swift index 346ec689d..82b54a98e 100644 --- a/Auth0/AuthenticationError.swift +++ b/Auth0/AuthenticationError.swift @@ -5,8 +5,20 @@ import Foundation */ public struct AuthenticationError: Auth0APIError { + /** + Additional information about the error. + */ public let info: [String: Any] + /** + Creates an error from a JSON response. + + - Parameters: + - info: JSON response from Auth0. + - statusCode: HTTP Status Code of the Response. + + - Returns: A newly created error. + */ public init(info: [String: Any], statusCode: Int) { var values = info values["statusCode"] = statusCode @@ -14,17 +26,31 @@ public struct AuthenticationError: Auth0APIError { self.statusCode = statusCode } + /** + HTTP Status Code of the response. + */ public let statusCode: Int + /** + The underlying `Error`, if any. Defaults to `nil`. + */ public var cause: Error? { return self.info["cause"] as? Error } + /** + The code of the error as a String. + */ public var code: String { let code = self.info["error"] ?? self.info["code"] return code as? String ?? unknownError } + /** + Description of the error. + + - Important: You should avoid displaying the error description to the user, it's meant for debugging only. + */ public var debugDescription: String { let description = self.info["description"] ?? self.info["error_description"] if let string = description as? String { @@ -36,6 +62,8 @@ public struct AuthenticationError: Auth0APIError { return "Failed with unknown error \(self.info)" } + // MARK: - Error Types + /// When MFA code is required to authenticate. public var isMultifactorRequired: Bool { return self.code == "a0.mfa_required" || self.code == "mfa_required" @@ -56,12 +84,12 @@ public struct AuthenticationError: Auth0APIError { return self.code == "expired_token" && self.localizedDescription == "mfa_token is expired" || self.code == "invalid_grant" && self.localizedDescription == "Malformed mfa_token" } - /// When password used for SignUp does not match connection's strength requirements. More info will be available in `info`. + /// When password used for sign up does not match connection's strength requirements. More info will be available in `info`. public var isPasswordNotStrongEnough: Bool { return self.code == "invalid_password" && self.info["name"] as? String == "PasswordStrengthError" } - /// When password used for SignUp was already used before (Reported when password history feature is enabled). More info will be available in `info`. + /// When password used for sign up was already used before (reported when password history feature is enabled). More info will be available in `info`. public var isPasswordAlreadyUsed: Bool { return self.code == "invalid_password" && self.info["name"] as? String == "PasswordHistoryError" } @@ -96,8 +124,11 @@ public struct AuthenticationError: Auth0APIError { } +// MARK: - Equatable + extension AuthenticationError: Equatable { + /// Conformance to `Equatable`. public static func == (lhs: AuthenticationError, rhs: AuthenticationError) -> Bool { return lhs.code == rhs.code && lhs.statusCode == rhs.statusCode diff --git a/Auth0/Challenge.swift b/Auth0/Challenge.swift index f40a43771..42be05b5e 100644 --- a/Auth0/Challenge.swift +++ b/Auth0/Challenge.swift @@ -1,6 +1,11 @@ +/// Multi-factor challenge. public struct Challenge: Codable { + /// How the user will get the challenge and prove possession. public let challengeType: String + /// Out-of-Band (OOB) code. public let oobCode: String? + /// When the challenge response includes a `prompt` binding method, your app needs to prompt the user for the + /// `binding_code` and send it as part of the request. public let bindingMethod: String? public enum CodingKeys: String, CodingKey { diff --git a/Auth0/Credentials.swift b/Auth0/Credentials.swift index 8b5229a88..2c1bf6857 100644 --- a/Auth0/Credentials.swift +++ b/Auth0/Credentials.swift @@ -16,13 +16,13 @@ private struct StructCredentials { @objc(A0Credentials) public final class Credentials: NSObject { - /// Token used that allows calling to the requested APIs (audience sent on Auth). + /// Token used that allows calling to the requested APIs (the `audience` value` sent on authentication). public let accessToken: String /// Type of the access token. public let tokenType: String /// When the access token expires. public let expiresIn: Date - /// If the API allows you to request new access tokens and the scope `offline_access` was included on Auth. + /// If the API allows you to request new access tokens and the scope `offline_access` was included on authentication. public let refreshToken: String? /// Token that details the user identity after authentication. public let idToken: String @@ -31,6 +31,7 @@ public final class Credentials: NSObject { /// MFA recovery code that the application must display to the end-user to be stored securely for future use. public let recoveryCode: String? + /// Custom description that redacts the tokens with ``. public override var description: String { let redacted = "" let values = StructCredentials(accessToken: redacted, @@ -43,6 +44,7 @@ public final class Credentials: NSObject { return String(describing: values).replacingOccurrences(of: "StructCredentials", with: "Credentials") } + /// Default initializer. public init(accessToken: String = "", tokenType: String = "", idToken: String = "", @@ -75,6 +77,7 @@ extension Credentials: Codable { case recoveryCode = "recovery_code" } + /// `Decodable` initializer. public convenience init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let accessToken = try values.decodeIfPresent(String.self, forKey: .accessToken) @@ -108,6 +111,7 @@ extension Credentials: Codable { extension Credentials: NSSecureCoding { + /// `NSSecureCoding` decoding initializer. public convenience init?(coder aDecoder: NSCoder) { let accessToken = aDecoder.decodeObject(of: NSString.self, forKey: "accessToken") let tokenType = aDecoder.decodeObject(of: NSString.self, forKey: "tokenType") @@ -126,6 +130,7 @@ extension Credentials: NSSecureCoding { recoveryCode: recoveryCode as String?) } + /// `NSSecureCoding` encoding method. public func encode(with aCoder: NSCoder) { aCoder.encode(self.accessToken as NSString, forKey: "accessToken") aCoder.encode(self.tokenType as NSString, forKey: "tokenType") @@ -136,6 +141,7 @@ extension Credentials: NSSecureCoding { aCoder.encode(self.recoveryCode as NSString?, forKey: "recoveryCode") } + /// Property that enables secure coding. Equals to `true`. public static var supportsSecureCoding: Bool { return true } } diff --git a/Auth0/CredentialsManager.swift b/Auth0/CredentialsManager.swift index 0dbc88caf..65762e9c4 100644 --- a/Auth0/CredentialsManager.swift +++ b/Auth0/CredentialsManager.swift @@ -20,7 +20,7 @@ public struct CredentialsManager { private var bioAuth: BioAuthentication? #endif - /// Creates a new CredentialsManager instance. + /// Create a new CredentialsManager instance. /// /// - Parameters: /// - authentication: Auth0 authentication instance. @@ -108,7 +108,7 @@ public struct CredentialsManager { } } - /// Checks if a non-expired set of credentials are stored. + /// Check if a non-expired set of credentials are stored. /// /// - Parameter minTTL: Minimum lifetime in seconds the access token must have left. /// - Returns: If there are valid and non-expired credentials stored. @@ -118,6 +118,7 @@ public struct CredentialsManager { return !self.hasExpired(credentials) && !self.willExpire(credentials, within: minTTL) } + #if WEB_AUTH_PLATFORM /// Retrieve credentials from keychain and yield new credentials using `refreshToken` if `accessToken` has expired. /// Otherwise, the retrieved credentials will be returned in the success case as they have not yet expired. Renewed credentials /// will be stored in the keychain. @@ -141,7 +142,6 @@ public struct CredentialsManager { /// - callback: Callback with the user's credentials or the error. /// - 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/security/tokens/refresh-tokens) - #if WEB_AUTH_PLATFORM public func credentials(withScope scope: String? = nil, minTTL: Int = 0, parameters: [String: Any] = [:], headers: [String: String] = [:], callback: @escaping (CredentialsManagerResult) -> Void) { if let bioAuth = self.bioAuth { guard bioAuth.available else { @@ -251,7 +251,7 @@ public struct CredentialsManager { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) public extension CredentialsManager { - /// Calls the revoke token endpoint to revoke the refresh token and, if successful, the credentials are cleared. Otherwise, + /// Call the revoke token endpoint to revoke the refresh token and, if successful, the credentials are cleared. Otherwise, /// the credentials are not cleared and the subscription completes with an error. /// /// If no refresh token is available the endpoint is not called, the credentials are cleared, and the subscription completes @@ -311,13 +311,13 @@ public extension CredentialsManager { #if compiler(>=5.5) && canImport(_Concurrency) public extension CredentialsManager { - /// Calls the revoke token endpoint to revoke the refresh token and, if successful, the credentials are cleared. Otherwise, + /// Call the revoke token endpoint to revoke the refresh token and, if successful, the credentials are cleared. Otherwise, /// the credentials are not cleared and an error is thrown. /// /// If no refresh token is available the endpoint is not called, the credentials are cleared, and no error is thrown. /// /// - Parameter headers: Additional headers to add to a possible token revocation. The headers will be set via Request.headers. - /// - Throws: An error of type `CredentialsManagerError`. + /// - Throws: An error of type ``CredentialsManagerError``. #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) func revoke(headers: [String: String] = [:]) async throws { @@ -348,7 +348,7 @@ public extension CredentialsManager { /// - parameters: Additional parameters to add to a possible token refresh. The parameters will be set via Request.parameters. /// - headers: Additional headers to add to a possible token refresh. The headers will be set via Request.headers. /// - Returns: The user's credentials. - /// - Throws: An error of type `CredentialsManagerError`. + /// - Throws: An error of type ``CredentialsManagerError``. /// - 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/security/tokens/refresh-tokens) #if compiler(>=5.5.2) diff --git a/Auth0/CredentialsManagerError.swift b/Auth0/CredentialsManagerError.swift index 24355f786..c83115829 100644 --- a/Auth0/CredentialsManagerError.swift +++ b/Auth0/CredentialsManagerError.swift @@ -21,8 +21,16 @@ public struct CredentialsManagerError: Auth0Error { self.cause = cause } + /** + The underlying `Error`, if any. Defaults to `nil`. + */ public let cause: Error? + /** + Description of the error. + + - Important: You should avoid displaying the error description to the user, it's meant for debugging only. + */ public var debugDescription: String { switch self.code { case .noCredentials: return "No valid credentials found." @@ -34,17 +42,30 @@ public struct CredentialsManagerError: Auth0Error { } } + // MARK: - Error Cases + + /// No credentials were found in the store. This error does not include a ``cause``. public static let noCredentials: CredentialsManagerError = .init(code: .noCredentials) + /// The ``Credentials`` instance stored does not contain a Refresh Token. This error does not include a ``cause``. public static let noRefreshToken: CredentialsManagerError = .init(code: .noRefreshToken) + /// The credentials renewal failed. The underlying ``AuthenticationError`` can be accessed via the `cause: Error?` property. public static let refreshFailed: CredentialsManagerError = .init(code: .refreshFailed) + /// The Biometric authentication failed. The underlying `LAError` can be accessed via the ``cause`` property. public static let biometricsFailed: CredentialsManagerError = .init(code: .biometricsFailed) + /// The revocation of the Refresh Token failed. The underlying ``AuthenticationError`` can be accessed via the + /// ``cause`` property. public static let revokeFailed: CredentialsManagerError = .init(code: .revokeFailed) + /// The `minTTL` requested is greater than the lifetime of the renewed Access Token. Request a lower `minTTL` or + /// increase the 'Token Expiration' setting of your Auth0 API in the Dashboard. This error does not include a ``cause``. public static let largeMinTTL: CredentialsManagerError = .init(code: .largeMinTTL(minTTL: 0, lifetime: 0)) } +// MARK: - Equatable + extension CredentialsManagerError: Equatable { + /// Conformance to `Equatable`. public static func == (lhs: CredentialsManagerError, rhs: CredentialsManagerError) -> Bool { return lhs.code == rhs.code && lhs.localizedDescription == rhs.localizedDescription } diff --git a/Auth0/CredentialsStorage.swift b/Auth0/CredentialsStorage.swift index 7b351ebcb..098b0f284 100644 --- a/Auth0/CredentialsStorage.swift +++ b/Auth0/CredentialsStorage.swift @@ -14,23 +14,31 @@ public protocol CredentialsStorage { /// - Parameters: /// - data: The data to be stored. /// - forKey: The key to store it to. - /// - Returns: If credentials were stored. + /// - Returns: If the data was stored. func setEntry(_ data: Data, forKey: String) -> Bool /// Delete a storage entry. /// /// - Parameter forKey: The key to delete from the store. - /// - Returns: If credentials were deleted. + /// - Returns: If the entry was deleted. func deleteEntry(forKey: String) -> Bool } extension A0SimpleKeychain: CredentialsStorage { + /// Retrieve a storage entry. + /// + /// - Parameter forKey: The key to get from the Keychain. + /// - Returns: The stored data. public func getEntry(forKey: String) -> Data? { return data(forKey: forKey) } + /// Delete a storage entry. + /// + /// - Parameter forKey: The key to delete from the Keychain. + /// - Returns: If the data was stored. public func setEntry(_ data: Data, forKey: String) -> Bool { return setData(data, forKey: forKey) } diff --git a/Auth0/JWKS.swift b/Auth0/JWKS.swift index 9d4871a50..4d750978d 100644 --- a/Auth0/JWKS.swift +++ b/Auth0/JWKS.swift @@ -1,27 +1,40 @@ import Foundation +/// The JSON Web Key Set (JWKS) of your Auth0 tenant. public struct JWKS: Codable { - let keys: [JWK] + /// The keys in the key set. + public let keys: [JWK] } public extension JWKS { + /// Get a key from the key set by its ID (`kid`). func key(id kid: String) -> JWK? { return keys.first { $0.keyId == kid } } } +/// Cryptographic public key of your Auth0 tenant. public struct JWK: Codable { - let keyType: String - let keyId: String? - let usage: String? - let algorithm: String? - let certUrl: String? - let certThumbprint: String? - let certChain: [String]? - let rsaModulus: String? - let rsaExponent: String? + /// The type of key. + public let keyType: String + /// The unique identifier of the key. + public let keyId: String? + /// How the key is meant to be used. + public let usage: String? + /// The algorithm of the key. + public let algorithm: String? + /// The URL of the x.509 cert. + public let certUrl: String? + /// The thumbprint of the x.509 cert (SHA-1 thumbprint). + public let certThumbprint: String? + /// The x.509 certificate chain. + public let certChain: [String]? + /// The modulus of the key. + public let rsaModulus: String? + /// The exponent of the key. + public let rsaExponent: String? enum CodingKeys: String, CodingKey { case keyType = "kty" diff --git a/Auth0/Loggable.swift b/Auth0/Loggable.swift index 2c4fab9e6..5e8392c5f 100644 --- a/Auth0/Loggable.swift +++ b/Auth0/Loggable.swift @@ -1,7 +1,9 @@ import Foundation +/// A type that can log statements for debugging purposes. public protocol Loggable { + /// Logger used to log statements. var logger: Logger? { get set } } @@ -37,4 +39,5 @@ public extension Loggable { } return loggable } + } diff --git a/Auth0/ManagementError.swift b/Auth0/ManagementError.swift index 6c69bcadd..b55961b44 100644 --- a/Auth0/ManagementError.swift +++ b/Auth0/ManagementError.swift @@ -5,8 +5,20 @@ import Foundation */ public struct ManagementError: Auth0APIError { + /** + Additional information about the error. + */ public let info: [String: Any] + /** + Creates an error from a JSON response. + + - Parameters: + - info: JSON response from Auth0. + - statusCode: HTTP Status Code of the Response. + + - Returns: A newly created error. + */ public init(info: [String: Any], statusCode: Int) { var values = info values["statusCode"] = statusCode @@ -14,16 +26,30 @@ public struct ManagementError: Auth0APIError { self.statusCode = statusCode } + /** + HTTP Status Code of the response. + */ public let statusCode: Int + /** + The underlying `Error`, if any. Defaults to `nil`. + */ public var cause: Error? { return self.info["cause"] as? Error } + /** + The code of the error as a String. + */ public var code: String { return self.info["code"] as? String ?? unknownError } + /** + Description of the error. + + - Important: You should avoid displaying the error description to the user, it's meant for debugging only. + */ public var debugDescription: String { if let string = self.info["description"] as? String { return string @@ -33,8 +59,11 @@ public struct ManagementError: Auth0APIError { } +// MARK: - Equatable + extension ManagementError: Equatable { + /// Conformance to `Equatable`. public static func == (lhs: ManagementError, rhs: ManagementError) -> Bool { return lhs.code == rhs.code && lhs.statusCode == rhs.statusCode diff --git a/Auth0/NSData+URLSafe.swift b/Auth0/NSData+URLSafe.swift index 5bce8c4ad..373df6d01 100644 --- a/Auth0/NSData+URLSafe.swift +++ b/Auth0/NSData+URLSafe.swift @@ -2,6 +2,7 @@ import Foundation public extension Data { + /// Encodes data to base64url. func a0_encodeBase64URLSafe() -> String? { return self .base64EncodedString(options: []) diff --git a/Auth0/Request.swift b/Auth0/Request.swift index 8dd61092a..a5ae8ca46 100644 --- a/Auth0/Request.swift +++ b/Auth0/Request.swift @@ -126,7 +126,7 @@ public extension Request { /** Starts the request to the server. - - Throws: An error that conforms to `Auth0APIError`; either an `AuthenticationError` or a `ManagementError`. + - Throws: An error that conforms to ``Auth0APIError``; either an ``AuthenticationError`` or a ``ManagementError``. */ #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) diff --git a/Auth0/String+URLSafe.swift b/Auth0/String+URLSafe.swift index bce9ad35c..58fafe123 100644 --- a/Auth0/String+URLSafe.swift +++ b/Auth0/String+URLSafe.swift @@ -2,6 +2,7 @@ import Foundation public extension String { + /// Decodes base64url-encoded data. func a0_decodeBase64URLSafe() -> Data? { let lengthMultiple = 4 let paddingLength = lengthMultiple - count % lengthMultiple diff --git a/Auth0/Telemetry.swift b/Auth0/Telemetry.swift index 1149e7960..f476f7f85 100644 --- a/Auth0/Telemetry.swift +++ b/Auth0/Telemetry.swift @@ -102,6 +102,7 @@ public struct Telemetry { public protocol Trackable { + /// The ``Telemetry`` instance. var telemetry: Telemetry { get set } } diff --git a/Auth0/UserInfo.swift b/Auth0/UserInfo.swift index cda1a1f09..7c1d138ef 100644 --- a/Auth0/UserInfo.swift +++ b/Auth0/UserInfo.swift @@ -4,6 +4,7 @@ import Foundation /// - Note: [Claims](https://auth0.com/docs/security/tokens/json-web-tokens/json-web-token-claims) public struct UserInfo: JSONObjectPayload { + /// The list of public claims. public static let publicClaims = [ "sub", "name", @@ -27,41 +28,66 @@ public struct UserInfo: JSONObjectPayload { "updated_at" ] + // MARK: - Claims + + /// The Auth0 user ID. public let sub: String + /// The name of the user. Requires the `profile` scope. public let name: String? + /// The fist name of the user. Requires the `profile` scope. public let givenName: String? + /// The last name of the user. Requires the `profile` scope. public let familyName: String? + /// The middle name of the user. Requires the `profile` scope. public let middleName: String? + /// The nickname of the user. Requires the `profile` scope. public let nickname: String? + /// The preferred username of the user. Requires the `profile` scope. public let preferredUsername: String? + /// The URL of the user's profile page. Requires the `profile` scope. public let profile: URL? + /// The URL of the user's picture. Requires the `profile` scope. public let picture: URL? + /// The URL of the user's website. Requires the `profile` scope. public let website: URL? + /// The email of the user. Requires the `email` scope. public let email: String? + /// If the user's email was verified. Requires the `email` scope. public let emailVerified: Bool? + /// The gender of the user. Requires the `profile` scope. public let gender: String? + /// The birthdate of the user. Requires the `profile` scope. public let birthdate: String? + /// The time zone of the user. Requires the `profile` scope. public let zoneinfo: TimeZone? + /// The locale of the user. Requires the `profile` scope. public let locale: Locale? + /// The phone number of the user. Requires the `phone_number` scope. public let phoneNumber: String? + /// If the user's phone number was verified. Requires the `phone_number` scope. public let phoneNumberVerified: Bool? + /// The address of the user. Requires the `address` scope. public let address: [String: String]? + /// The time the user's information was last updated. Requires the `profile` scope. public let updatedAt: Date? - + /// Any custom claims. public let customClaims: [String: Any]? } +// MARK: - Initializer + public extension UserInfo { // swiftlint:disable:next function_body_length + /// Creates a ``UserInfo`` from a JSON dictionary. init?(json: [String: Any]) { guard let sub = json["sub"] as? String else { return nil } diff --git a/Auth0/UserPatchAttributes.swift b/Auth0/UserPatchAttributes.swift index 34003a518..60b12d9d0 100644 --- a/Auth0/UserPatchAttributes.swift +++ b/Auth0/UserPatchAttributes.swift @@ -1,12 +1,12 @@ import Foundation -/// Attributes of the user allowed to update using `patch()` method of `Users`. +/// Attributes of the user allowed to update using `patch()` method of ``Users `. public class UserPatchAttributes { private(set) var dictionary: [String: Any] /** - Creates a new attributes. + Create a new attributes. - Parameter dictionary: Default attribute values. - Returns: New attributes. @@ -16,7 +16,7 @@ public class UserPatchAttributes { } /** - Mark/Unmark a user as blocked. + Mark/unmark a user as blocked. - Parameter blocked: If the user is blocked. - Returns: Itself. @@ -27,7 +27,7 @@ public class UserPatchAttributes { } /** - Changes the email of the user. + Change the email of the user. - Parameters: - email: New email for the user. @@ -47,7 +47,7 @@ public class UserPatchAttributes { } /** - Sets the verified status of the email. + Set the verified status of the email. - Parameters: - verified: If the email is verified or not. @@ -61,7 +61,7 @@ public class UserPatchAttributes { } /** - Changes the phone number of the user (SMS connection only). + Change the phone number of the user (SMS connection only). - Parameters: - phoneNumber: New phone number for the user. @@ -81,7 +81,7 @@ public class UserPatchAttributes { } /** - Sets the verified status of the phone number. + Set the verified status of the phone number. - Parameters: - verified: If the phone number is verified or not. @@ -95,7 +95,7 @@ public class UserPatchAttributes { } /** - Changes the user's password. + Change the user's password. - Parameters: - password: New password for the user. @@ -111,7 +111,7 @@ public class UserPatchAttributes { } /** - Changes the username. + Change the username. - Parameters: - username: New username. @@ -125,7 +125,7 @@ public class UserPatchAttributes { } /** - Updates user metadata. + Update user metadata. - Parameter metadata: New user metadata values. - Returns: Itself. @@ -136,7 +136,7 @@ public class UserPatchAttributes { } /** - Updates app metadata. + Update app metadata. - Parameter metadata: New app metadata values. - Returns: Itself. diff --git a/Auth0/Users.swift b/Auth0/Users.swift index c2bd87f90..58d5d945e 100644 --- a/Auth0/Users.swift +++ b/Auth0/Users.swift @@ -11,7 +11,9 @@ public typealias ManagementObject = [String: Any] */ public protocol Users: Trackable, Loggable { + /// The Management API token. var token: String { get } + /// The Auth0 domain URL. var url: URL { get } /** @@ -51,7 +53,7 @@ public protocol Users: Trackable, Loggable { ``` - Parameters: - - identifier: Id of the user. + - identifier: ID of the user. - fields: List of the user's field names that will be included/excluded in the response. By default all will be retrieved. - include: Flag that indicates that only the names in 'fields' should be included or not in the response. By default it will include them. - Returns: A request that will yield a user. @@ -88,7 +90,7 @@ public protocol Users: Trackable, Loggable { .appMetadata(["role": "admin"]) ``` - Then just pass the `UserPatchAttributes` to the patch method, like: + Then just pass the ``UserPatchAttributes`` to the patch method, like: ``` Auth0 @@ -98,7 +100,7 @@ public protocol Users: Trackable, Loggable { ``` - Parameters: - - identifier: Id of the user to update. + - identifier: ID of the user to update. - attributes: Root attributes to be updated. - Returns: A request. - See: ``UserPatchAttributes`` @@ -118,7 +120,7 @@ public protocol Users: Trackable, Loggable { ``` - Parameters: - - identifier: Id of the user. + - identifier: ID of the user. - userMetadata: Metadata to update. - Returns: A request to patch `user_metadata`. - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id). @@ -138,12 +140,12 @@ public protocol Users: Trackable, Loggable { ``` - Parameters: - - identifier: Id of the primary user who will be linked against a secondary one. + - identifier: ID of the primary user who will be linked against a secondary one. - withOtherUserToken: Token of the secondary user to link to. - Returns: A request to link two users. - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/post_identities). - See: [Link Accounts Guide](https://auth0.com/docs/users/user-account-linking). - - Important: The token must have the following scope `update:current_user_identities`. + - Important: The token must have the scope `update:current_user_identities`. */ func link(_ identifier: String, withOtherUserToken token: String) -> Request<[ManagementObject], ManagementError> @@ -158,14 +160,14 @@ public protocol Users: Trackable, Loggable { ``` - Parameters: - - identifier: Id of the primary user who will be linked against a secondary one. - - withUser: Id of the secondary user who will be linked. - - provider: Name of the provider of the secondary user. e.g. 'auth0' for Database connections. - - connectionId: Id of the connection of the secondary user. + - identifier: ID of the primary user who will be linked against a secondary one. + - withUser: ID of the secondary user who will be linked. + - provider: Name of the provider of the secondary user, e.g. 'auth0' for Database connections. + - connectionId: ID of the connection of the secondary user. - Returns: A request to link two users. - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/post_identities). - See: [Link Accounts Guide](https://auth0.com/docs/users/user-account-linking). - - Important: The token must have the following scope `update:users`. + - Important: The token must have the scope `update:users`. */ func link(_ identifier: String, withUser userId: String, provider: String, connectionId: String?) -> Request<[ManagementObject], ManagementError> @@ -182,11 +184,11 @@ public protocol Users: Trackable, Loggable { - Parameters: - identityId: Identifier of the identity to remove. - provider: Name of the provider of the identity. - - fromUserId: Id of the user who owns the identity. + - fromUserId: ID of the user who owns the identity. - Returns: A request to remove an identity. - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/delete_provider_by_user_id). - See: [Link Accounts Guide](https://auth0.com/docs/users/user-account-linking). - - Important: The token must have the following scope `update:users`. + - Important: The token must have the scope `update:users`. */ func unlink(identityId: String, provider: String, fromUserId identifier: String) -> Request<[ManagementObject], ManagementError> @@ -194,77 +196,14 @@ public protocol Users: Trackable, Loggable { public extension Users { - /** - Fetch a user using its identifier. - By default it gets all the user's attributes: - - ``` - Auth0 - .users(token: token, domain: "samples.auth0.com") - .get(userId) - .start { result in - switch result { - case .success(let user): - print("User signed up: \(user)") - case .failure(let error): - print("Failed with \(error)") - } - } - ``` - - You can select which attributes you want: - - ``` - Auth0 - .users(token: token, domain: "samples.auth0.com") - .get(userId, fields: ["email", "user_id"]) - .start { print($0) } - ``` - - You can even exclude some attributes: - - ``` - Auth0 - .users(token token, domain: "samples.auth0.com") - .get(userId, fields: ["identities", "app_metadata"], include: false) - .start { print($0) } - ``` - - - Parameters: - - identifier: Id of the user. - - fields: List of the user's field names that will be included/excluded in the response. By default all will be retrieved. - - include: Flag that indicates that only the names in 'fields' should be included or not in the response. By default it will include them. - - Returns: A request that will yield a user. - - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id). - - Important: The token must have the scope `read:users` scope. - */ func get(_ identifier: String, fields: [String] = [], include: Bool = true) -> Request { return self.get(identifier, fields: fields, include: include) } - /** - Links a user given its identifier with a secondary user identified by its id, provider and connection identifier. - - ``` - Auth0 - .users(token: token, domain: "samples.auth0.com") - .link(userId, userId: anotherUserId, provider: "auth0", connectionId: "AConnectionID") - .start { print($0) } - ``` - - - Parameters: - - identifier: Id of the primary user who will be linked against a secondary one. - - withUser: Id of the secondary user who will be linked. - - provider: Name of the provider of the secondary user. e.g. 'auth0' for Database connections. - - connectionId: Id of the connection of the secondary user. - - Returns: A request to link two users. - - Note: [Auth0 Management API docs](https://auth0.com/docs/api/management/v2#!/Users/post_identities). - - See: [Link Accounts Guide](https://auth0.com/docs/users/user-account-linking). - - Important: The token must have the following scope `update:users`. - */ func link(_ identifier: String, withUser userId: String, provider: String, connectionId: String? = nil) -> Request<[ManagementObject], ManagementError> { return self.link(identifier, withUser: userId, provider: provider, connectionId: connectionId) } + } extension Management: Users { diff --git a/Auth0/WebAuth.swift b/Auth0/WebAuth.swift index cf20abbb4..0523e322c 100644 --- a/Auth0/WebAuth.swift +++ b/Auth0/WebAuth.swift @@ -8,8 +8,12 @@ import Combine /// WebAuth Authentication using Auth0. public protocol WebAuth: Trackable, Loggable { + + /// The Auth0 client ID. var clientId: String { get } + /// The Auth0 domain URL. var url: URL { get } + /// The ``Telemetry`` instance. var telemetry: Telemetry { get set } /** @@ -22,7 +26,7 @@ public protocol WebAuth: Trackable, Loggable { func connection(_ connection: String) -> Self /** - Scopes that will be requested during auth. + Scopes that will be requested during authentication. - Parameter scope: A scope value like: `openid profile email offline_access`. - Returns: The same WebAuth instance to allow method chaining. @@ -30,7 +34,7 @@ public protocol WebAuth: Trackable, Loggable { func scope(_ scope: String) -> Self /** - Provider scopes for oauth2/social connections. e.g. Facebook, Google etc. + Provider scopes for OAuth2/social connections, e.g. Facebook, Google etc. - Parameter connectionScope: OAuth2/social comma separated scope list: `user_friends,email`. - Returns: The same WebAuth instance to allow method chaining. @@ -55,9 +59,9 @@ public protocol WebAuth: Trackable, Loggable { */ func parameters(_ parameters: [String: String]) -> Self - /// Specify a custom redirect url to be used. + /// Specify a custom redirect URL to be used. /// - /// - Parameter redirectURL: Custom redirect url. + /// - Parameter redirectURL: Custom redirect URL. /// - Returns: The same WebAuth instance to allow method chaining. func redirectURL(_ redirectURL: URL) -> Self @@ -82,7 +86,7 @@ public protocol WebAuth: Trackable, Loggable { func issuer(_ issuer: String) -> Self /// Add a leeway amount for ID Token validation. - /// This value represents the clock skew for the validation of date claims e.g. `exp`. + /// This value represents the clock skew for the validation of date claims, e.g. `exp`. /// /// - Parameter leeway: Number of milliseconds. Defaults to `60000` (1 minute). /// - Returns: The same WebAuth instance to allow method chaining. @@ -96,7 +100,7 @@ public protocol WebAuth: Trackable, Loggable { func maxAge(_ maxAge: Int) -> Self /// Disable Single Sign On (SSO) on iOS 13+ and macOS. - /// Has no effect on older versions of iOS. + /// Has no effect on iOS 12. /// /// - Returns: The same WebAuth instance to allow method chaining. func useEphemeralSession() -> Self @@ -107,9 +111,9 @@ public protocol WebAuth: Trackable, Loggable { /// - Returns: The same WebAuth instance to allow method chaining. func invitationURL(_ invitationURL: URL) -> Self - /// Specify an organization id to log in to. + /// Specify an organization ID to log in to. /// - /// - Parameter organization: An organization id. + /// - Parameter organization: An organization ID. /// - Returns: The same WebAuth instance to allow method chaining. func organization(_ organization: String) -> Self @@ -155,7 +159,7 @@ public protocol WebAuth: Trackable, Loggable { and it will throw a `WebAuthError.userCancelled` error. - Returns: The result of the WebAuth flow. - - Throws: An error of type `WebAuthError`. + - Throws: An error of type ``WebAuthError``. */ #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) @@ -210,7 +214,7 @@ public protocol WebAuth: Trackable, Loggable { } ``` - Remove Auth0 session and remove the IdP session: + Remove Auth0 session and remove the identity provider (IdP) session: ``` Auth0 @@ -219,7 +223,7 @@ public protocol WebAuth: Trackable, Loggable { ``` - Parameters: - - federated: `Bool` to remove the IdP session. Defaults to `false`. + - federated: `Bool` to remove the identity provider (IdP) session. Defaults to `false`. - callback: Callback called with bool outcome of the call. */ func clearSession(federated: Bool, callback: @escaping (WebAuthResult) -> Void) @@ -246,7 +250,7 @@ public protocol WebAuth: Trackable, Loggable { .store(in: &cancellables) ``` - Remove Auth0 session and remove the IdP session: + Remove Auth0 session and remove the identity provider (IdP) session: ``` Auth0 @@ -256,7 +260,7 @@ public protocol WebAuth: Trackable, Loggable { .store(in: &cancellables) ``` - - Parameter federated: `Bool` to remove the IdP session. Defaults to `false`. + - Parameter federated: `Bool` to remove the identity provider (IdP) session. Defaults to `false`. - Returns: A type-erased publisher. */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) @@ -281,7 +285,7 @@ public protocol WebAuth: Trackable, Loggable { } ``` - Remove Auth0 session and remove the IdP session: + Remove Auth0 session and remove the identity provider (IdP) session: ``` try await Auth0 @@ -289,7 +293,7 @@ public protocol WebAuth: Trackable, Loggable { .clearSession(federated: true) ``` - - Parameter federated: `Bool` to remove the IdP session. Defaults to `false`. + - Parameter federated: `Bool` to remove the identity provider (IdP) session. Defaults to `false`. - Returns: `Bool` outcome of the call. */ #if compiler(>=5.5.2) @@ -300,115 +304,21 @@ public protocol WebAuth: Trackable, Loggable { func clearSession(federated: Bool) async throws #endif #endif + } public extension WebAuth { - /** - Removes Auth0 session and optionally remove the Identity Provider session. - - See: [Auth0 Logout docs](https://auth0.com/docs/login/logout) - - You will need to ensure that the **Callback URL** has been added - to the **Allowed Logout URLs** section of your application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications/). - - ``` - Auth0 - .webAuth() - .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: - - ``` - Auth0 - .webAuth() - .clearSession(federated: true) { print($0) } - ``` - - - Parameters: - - 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 (WebAuthResult) -> Void) { self.clearSession(federated: federated, callback: callback) } - /** - Removes Auth0 session and optionally remove the Identity Provider session. - - See: [Auth0 Logout docs](https://auth0.com/docs/login/logout) - - You will need to ensure that the **Callback URL** has been added - to the **Allowed Logout URLs** section of your application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications/). - - ``` - Auth0 - .webAuth() - .clearSession() - .sink(receiveCompletion: { completion in - switch completion { - case .failure(let error): - print("Failed with \(error)") - case .finished: - print("Logged out") - } - }, receiveValue: { _ in }) - .store(in: &cancellables) - ``` - - Remove Auth0 session and remove the IdP session: - - ``` - Auth0 - .webAuth() - .clearSession(federated: true) - .sink(receiveValue: { print($0) }) - .store(in: &cancellables) - ``` - - - Parameter federated: `Bool` to remove the IdP session. Defaults to `false`. - - Returns: A type-erased publisher. - */ @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) func clearSession(federated: Bool = false) -> AnyPublisher { return self.clearSession(federated: federated) } #if compiler(>=5.5) && canImport(_Concurrency) - /** - Removes Auth0 session and optionally remove the Identity Provider session. - - See: [Auth0 Logout docs](https://auth0.com/docs/login/logout) - - You will need to ensure that the **Callback URL** has been added - to the **Allowed Logout URLs** section of your application in the [Auth0 Dashboard](https://manage.auth0.com/#/applications/). - - ``` - 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: - - ``` - try await Auth0 - .webAuth(clientId: clientId, domain: "samples.auth0.com") - .clearSession(federated: true) - ``` - - - Parameter federated: `Bool` to remove the IdP session. Defaults to `false`. - - Returns: `Bool` outcome of the call. - */ #if compiler(>=5.5.2) @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) func clearSession(federated: Bool = false) async throws { diff --git a/Auth0/WebAuthError.swift b/Auth0/WebAuthError.swift index ab14b6ff0..a31f05d3f 100644 --- a/Auth0/WebAuthError.swift +++ b/Auth0/WebAuthError.swift @@ -24,8 +24,16 @@ public struct WebAuthError: Auth0Error { self.cause = cause } + /** + The underlying `Error`, if any. Defaults to `nil`. + */ public let cause: Error? + /** + Description of the error. + + - Important: You should avoid displaying the error description to the user, it's meant for debugging only. + */ public var debugDescription: String { switch self.code { case .noBundleIdentifier: return "Unable to retrieve the bundle identifier." @@ -41,19 +49,37 @@ public struct WebAuthError: Auth0Error { } } + // MARK: - Error Cases + + /// The bundle identifier could not be retrieved from `Bundle.main.bundleIdentifier`, or it could not be used to + /// build a valid URL. This error does not include a ``cause``. public static let noBundleIdentifier: WebAuthError = .init(code: .noBundleIdentifier) + /// The invitation URL is missing the `organization` and/or the `invitation` query parameters. + /// This error does not include a ``cause``. public static let malformedInvitationURL: WebAuthError = .init(code: .malformedInvitationURL("")) + /// The user cancelled the Web Auth operation. This error does not include a ``cause``. public static let userCancelled: WebAuthError = .init(code: .userCancelled) + /// The correct method for Token Endpoint Authentication Method is not set (it should be 'None'). + /// You need to enable PKCE support in your Auth0 application's settings page, by setting the 'Application Type' to + /// 'Native' and the 'Token Endpoint Authentication Method' to 'None'. This error does not include a ``cause``. public static let pkceNotAllowed: WebAuthError = .init(code: .pkceNotAllowed) + /// The callback URL is missing the `code` query parameter. This error does not include a ``cause``. public static let noAuthorizationCode: WebAuthError = .init(code: .noAuthorizationCode([:])) + /// The ID Token validation performed after Web Auth login failed. + /// The underlying error can be accessed via the ``cause`` property. public static let idTokenValidationFailed: WebAuthError = .init(code: .idTokenValidationFailed) + /// Another `Error` occurred. That error can be accessed via the ``cause`` property. public static let other: WebAuthError = .init(code: .other) + /// An unknown error occurred, but an `Error` value is not available. This error does not include a ``cause``. public static let unknown: WebAuthError = .init(code: .unknown("")) } +// MARK: - Equatable + extension WebAuthError: Equatable { + /// Conformance to `Equatable`. public static func == (lhs: WebAuthError, rhs: WebAuthError) -> Bool { return lhs.code == rhs.code && lhs.localizedDescription == rhs.localizedDescription } diff --git a/Gemfile b/Gemfile index 32ad48b7f..8d8a21cd0 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'fastlane' gem 'cocoapods' +gem 'jazzy' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index f842a4ebe..2e14bffb8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -201,9 +201,20 @@ GEM httpclient (2.8.3) i18n (1.8.10) concurrent-ruby (~> 1.0) + jazzy (0.14.1) + cocoapods (~> 1.5) + mustache (~> 1.1) + open4 (~> 1.3) + redcarpet (~> 3.4) + rexml (~> 3.2) + rouge (>= 2.0.6, < 4.0) + sassc (~> 2.1) + sqlite3 (~> 1.3) + xcinvoke (~> 0.3.0) jmespath (1.4.0) json (2.6.1) jwt (2.3.0) + liferaft (0.0.6) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) @@ -211,15 +222,18 @@ GEM molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.0.0) + mustache (1.1.1) nanaimo (0.3.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) + open4 (1.3.4) optparse (0.1.1) os (1.1.1) plist (3.6.0) public_suffix (4.0.6) rake (13.0.6) + redcarpet (3.5.1) representable (3.1.1) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) @@ -230,6 +244,8 @@ GEM ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) + sassc (2.4.0) + ffi (~> 1.9) security (0.1.3) semantic (1.6.1) signet (0.16.0) @@ -240,6 +256,7 @@ GEM simctl (1.6.8) CFPropertyList naturally + sqlite3 (1.4.2) terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) @@ -259,6 +276,8 @@ GEM unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) + xcinvoke (0.3.0) + liferaft (~> 0.0.6) xcodeproj (1.21.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) @@ -279,6 +298,7 @@ DEPENDENCIES cocoapods fastlane fastlane-plugin-auth0_shipper + jazzy BUNDLED WITH 2.2.28