mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
Co-authored-by: gorthitk <tgorthi@google.com> Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com>
610 lines
20 KiB
Swift
610 lines
20 KiB
Swift
// Copyright 2023 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import Foundation
|
|
|
|
// MARK: - URL response error codes
|
|
|
|
/// Error code that indicates that the client ID provided was invalid.
|
|
private let kURLResponseErrorCodeInvalidClientID = "auth/invalid-oauth-client-id"
|
|
|
|
/// Error code that indicates that a network request within the SFSafariViewController or WKWebView
|
|
/// failed.
|
|
private let kURLResponseErrorCodeNetworkRequestFailed = "auth/network-request-failed"
|
|
|
|
/// Error code that indicates that an internal error occurred within the
|
|
/// SFSafariViewController or WKWebView failed.
|
|
private let kURLResponseErrorCodeInternalError = "auth/internal-error"
|
|
|
|
private let kFIRAuthErrorMessageMalformedJWT =
|
|
"Failed to parse JWT. Check the userInfo dictionary for the full token."
|
|
|
|
class AuthErrorUtils {
|
|
static let internalErrorDomain = "FIRAuthInternalErrorDomain"
|
|
static let userInfoDeserializedResponseKey = "FIRAuthErrorUserInfoDeserializedResponseKey"
|
|
static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey"
|
|
|
|
/// This marker indicates that the server error message contains a detail error message which
|
|
/// should be used instead of the hardcoded client error message.
|
|
private static let kServerErrorDetailMarker = " : "
|
|
|
|
static func error(code: SharedErrorCode, userInfo: [String: Any]? = nil) -> Error {
|
|
switch code {
|
|
case let .public(publicCode):
|
|
var errorUserInfo: [String: Any] = userInfo ?? [:]
|
|
if errorUserInfo[NSLocalizedDescriptionKey] == nil {
|
|
errorUserInfo[NSLocalizedDescriptionKey] = publicCode.errorDescription
|
|
}
|
|
if let localizedDescription = errorUserInfo[NSLocalizedDescriptionKey] as? String,
|
|
localizedDescription == "" {
|
|
errorUserInfo[NSLocalizedDescriptionKey] = publicCode.errorDescription
|
|
}
|
|
errorUserInfo[AuthErrors.userInfoNameKey] = publicCode.errorCodeString
|
|
return NSError(
|
|
domain: AuthErrors.domain,
|
|
code: publicCode.rawValue,
|
|
userInfo: errorUserInfo
|
|
)
|
|
case let .internal(internalCode):
|
|
// This is an internal error. Wrap it in an internal error.
|
|
let error = NSError(
|
|
domain: internalErrorDomain,
|
|
code: internalCode.rawValue,
|
|
userInfo: userInfo
|
|
)
|
|
|
|
return self.error(code: .public(.internalError), underlyingError: error)
|
|
}
|
|
}
|
|
|
|
static func error(code: SharedErrorCode, underlyingError: Error?) -> Error {
|
|
var errorUserInfo: [String: Any]?
|
|
if let underlyingError = underlyingError {
|
|
errorUserInfo = [NSUnderlyingErrorKey: underlyingError]
|
|
}
|
|
return error(code: code, userInfo: errorUserInfo)
|
|
}
|
|
|
|
static func error(code: AuthErrorCode, underlyingError: Error?) -> Error {
|
|
error(code: SharedErrorCode.public(code), underlyingError: underlyingError)
|
|
}
|
|
|
|
static func error(code: AuthErrorCode, userInfo: [String: Any]? = nil) -> Error {
|
|
error(code: SharedErrorCode.public(code), userInfo: userInfo)
|
|
}
|
|
|
|
static func error(code: AuthErrorCode, message: String?) -> Error {
|
|
let userInfo: [String: Any]?
|
|
if let message {
|
|
userInfo = [NSLocalizedDescriptionKey: message]
|
|
} else {
|
|
userInfo = nil
|
|
}
|
|
return error(code: SharedErrorCode.public(code), userInfo: userInfo)
|
|
}
|
|
|
|
static func userDisabledError(message: String?) -> Error {
|
|
error(code: .userDisabled, message: message)
|
|
}
|
|
|
|
static func wrongPasswordError(message: String?) -> Error {
|
|
error(code: .wrongPassword, message: message)
|
|
}
|
|
|
|
static func tooManyRequestsError(message: String?) -> Error {
|
|
error(code: .tooManyRequests, message: message)
|
|
}
|
|
|
|
static func invalidCustomTokenError(message: String?) -> Error {
|
|
error(code: .invalidCustomToken, message: message)
|
|
}
|
|
|
|
static func customTokenMismatchError(message: String?) -> Error {
|
|
error(code: .customTokenMismatch, message: message)
|
|
}
|
|
|
|
static func invalidCredentialError(message: String?) -> Error {
|
|
error(code: .invalidCredential, message: message)
|
|
}
|
|
|
|
static func requiresRecentLoginError(message: String?) -> Error {
|
|
error(code: .requiresRecentLogin, message: message)
|
|
}
|
|
|
|
static func invalidUserTokenError(message: String?) -> Error {
|
|
error(code: .invalidUserToken, message: message)
|
|
}
|
|
|
|
static func invalidEmailError(message: String?) -> Error {
|
|
error(code: .invalidEmail, message: message)
|
|
}
|
|
|
|
static func providerAlreadyLinkedError() -> Error {
|
|
error(code: .providerAlreadyLinked)
|
|
}
|
|
|
|
static func noSuchProviderError() -> Error {
|
|
error(code: .noSuchProvider)
|
|
}
|
|
|
|
static func userTokenExpiredError(message: String?) -> Error {
|
|
error(code: .userTokenExpired, message: message)
|
|
}
|
|
|
|
static func userNotFoundError(message: String?) -> Error {
|
|
error(code: .userNotFound, message: message)
|
|
}
|
|
|
|
static func invalidAPIKeyError() -> Error {
|
|
error(code: .invalidAPIKey)
|
|
}
|
|
|
|
static func userMismatchError() -> Error {
|
|
error(code: .userMismatch)
|
|
}
|
|
|
|
static func operationNotAllowedError(message: String?) -> Error {
|
|
error(code: .operationNotAllowed, message: message)
|
|
}
|
|
|
|
static func weakPasswordError(serverResponseReason reason: String?) -> Error {
|
|
let userInfo: [String: Any]?
|
|
if let reason, !reason.isEmpty {
|
|
userInfo = [
|
|
NSLocalizedFailureReasonErrorKey: reason,
|
|
]
|
|
} else {
|
|
userInfo = nil
|
|
}
|
|
return error(code: .weakPassword, userInfo: userInfo)
|
|
}
|
|
|
|
static func appNotAuthorizedError() -> Error {
|
|
error(code: .appNotAuthorized)
|
|
}
|
|
|
|
static func expiredActionCodeError(message: String?) -> Error {
|
|
error(code: .expiredActionCode, message: message)
|
|
}
|
|
|
|
static func invalidActionCodeError(message: String?) -> Error {
|
|
error(code: .invalidActionCode, message: message)
|
|
}
|
|
|
|
static func invalidMessagePayloadError(message: String?) -> Error {
|
|
error(code: .invalidMessagePayload, message: message)
|
|
}
|
|
|
|
static func invalidSenderError(message: String?) -> Error {
|
|
error(code: .invalidSender, message: message)
|
|
}
|
|
|
|
static func invalidRecipientEmailError(message: String?) -> Error {
|
|
error(code: .invalidRecipientEmail, message: message)
|
|
}
|
|
|
|
static func missingIosBundleIDError(message: String?) -> Error {
|
|
error(code: .missingIosBundleID, message: message)
|
|
}
|
|
|
|
static func missingAndroidPackageNameError(message: String?) -> Error {
|
|
error(code: .missingAndroidPackageName, message: message)
|
|
}
|
|
|
|
static func invalidRecaptchaTokenError() -> Error {
|
|
error(code: .invalidRecaptchaToken)
|
|
}
|
|
|
|
static func unauthorizedDomainError(message: String?) -> Error {
|
|
error(code: .unauthorizedDomain, message: message)
|
|
}
|
|
|
|
static func invalidContinueURIError(message: String?) -> Error {
|
|
error(code: .invalidContinueURI, message: message)
|
|
}
|
|
|
|
static func missingContinueURIError(message: String?) -> Error {
|
|
error(code: .missingContinueURI, message: message)
|
|
}
|
|
|
|
static func missingEmailError(message: String?) -> Error {
|
|
error(code: .missingEmail, message: message)
|
|
}
|
|
|
|
static func missingPhoneNumberError(message: String?) -> Error {
|
|
error(code: .missingPhoneNumber, message: message)
|
|
}
|
|
|
|
static func invalidPhoneNumberError(message: String?) -> Error {
|
|
error(code: .invalidPhoneNumber, message: message)
|
|
}
|
|
|
|
static func missingVerificationCodeError(message: String?) -> Error {
|
|
error(code: .missingVerificationCode, message: message)
|
|
}
|
|
|
|
static func invalidVerificationCodeError(message: String?) -> Error {
|
|
error(code: .invalidVerificationCode, message: message)
|
|
}
|
|
|
|
static func missingVerificationIDError(message: String?) -> Error {
|
|
error(code: .missingVerificationID, message: message)
|
|
}
|
|
|
|
static func invalidVerificationIDError(message: String?) -> Error {
|
|
error(code: .invalidVerificationID, message: message)
|
|
}
|
|
|
|
static func sessionExpiredError(message: String?) -> Error {
|
|
error(code: .sessionExpired, message: message)
|
|
}
|
|
|
|
static func missingAppCredential(message: String?) -> Error {
|
|
error(code: .missingAppCredential, message: message)
|
|
}
|
|
|
|
static func invalidAppCredential(message: String?) -> Error {
|
|
error(code: .invalidAppCredential, message: message)
|
|
}
|
|
|
|
static func quotaExceededError(message: String?) -> Error {
|
|
error(code: .quotaExceeded, message: message)
|
|
}
|
|
|
|
static func missingAppTokenError(underlyingError: Error?) -> Error {
|
|
error(code: .missingAppToken, underlyingError: underlyingError)
|
|
}
|
|
|
|
static func localPlayerNotAuthenticatedError() -> Error {
|
|
error(code: .localPlayerNotAuthenticated)
|
|
}
|
|
|
|
static func gameKitNotLinkedError() -> Error {
|
|
error(code: .gameKitNotLinked)
|
|
}
|
|
|
|
static func RPCRequestEncodingError(underlyingError: Error) -> Error {
|
|
error(code: .internal(.RPCRequestEncodingError), underlyingError: underlyingError)
|
|
}
|
|
|
|
static func JSONSerializationErrorForUnencodableType() -> Error {
|
|
error(code: .internal(.JSONSerializationError))
|
|
}
|
|
|
|
static func JSONSerializationError(underlyingError: Error) -> Error {
|
|
error(code: .internal(.JSONSerializationError), underlyingError: underlyingError)
|
|
}
|
|
|
|
static func networkError(underlyingError: Error) -> Error {
|
|
error(code: .networkError, underlyingError: underlyingError)
|
|
}
|
|
|
|
static func emailAlreadyInUseError(email: String?) -> Error {
|
|
var userInfo: [String: Any]?
|
|
if let email, !email.isEmpty {
|
|
userInfo = [AuthErrors.userInfoEmailKey: email]
|
|
}
|
|
return error(code: .emailAlreadyInUse, userInfo: userInfo)
|
|
}
|
|
|
|
static func credentialAlreadyInUseError(message: String?,
|
|
credential: AuthCredential?,
|
|
email: String?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let credential {
|
|
userInfo[AuthErrors.userInfoUpdatedCredentialKey] = credential
|
|
}
|
|
if let email, !email.isEmpty {
|
|
userInfo[AuthErrors.userInfoEmailKey] = email
|
|
}
|
|
if !userInfo.isEmpty {
|
|
return error(code: .credentialAlreadyInUse, userInfo: userInfo)
|
|
}
|
|
return error(code: .credentialAlreadyInUse, message: message)
|
|
}
|
|
|
|
static func webContextAlreadyPresentedError(message: String?) -> Error {
|
|
error(code: .webContextAlreadyPresented, message: message)
|
|
}
|
|
|
|
static func webContextCancelledError(message: String?) -> Error {
|
|
error(code: .webContextCancelled, message: message)
|
|
}
|
|
|
|
static func appVerificationUserInteractionFailure(reason: String?) -> Error {
|
|
let userInfo: [String: Any]?
|
|
if let reason, !reason.isEmpty {
|
|
userInfo = [NSLocalizedFailureReasonErrorKey: reason]
|
|
} else {
|
|
userInfo = nil
|
|
}
|
|
return error(code: .appVerificationUserInteractionFailure, userInfo: userInfo)
|
|
}
|
|
|
|
static func webSignInUserInteractionFailure(reason: String?) -> Error {
|
|
let userInfo: [String: Any]?
|
|
if let reason, !reason.isEmpty {
|
|
userInfo = [NSLocalizedFailureReasonErrorKey: reason]
|
|
} else {
|
|
userInfo = nil
|
|
}
|
|
return error(code: .webSignInUserInteractionFailure, userInfo: userInfo)
|
|
}
|
|
|
|
static func urlResponseError(code: String, message: String?) -> Error {
|
|
let errorCode: AuthErrorCode
|
|
switch code {
|
|
case kURLResponseErrorCodeInvalidClientID:
|
|
errorCode = .invalidClientID
|
|
case kURLResponseErrorCodeNetworkRequestFailed:
|
|
errorCode = .webNetworkRequestFailed
|
|
case kURLResponseErrorCodeInternalError:
|
|
errorCode = .webInternalError
|
|
default:
|
|
return AuthErrorUtils.webSignInUserInteractionFailure(reason: "[\(code)] - \(message ?? "")")
|
|
}
|
|
return error(code: errorCode, message: message)
|
|
}
|
|
|
|
static func nullUserError(message: String?) -> Error {
|
|
error(code: .nullUser, message: message)
|
|
}
|
|
|
|
static func invalidProviderIDError(message: String?) -> Error {
|
|
error(code: .invalidProviderID, message: message)
|
|
}
|
|
|
|
static func invalidHostingLinkDomainError(message: String?) -> Error {
|
|
error(code: .invalidHostingLinkDomain, message: message)
|
|
}
|
|
|
|
static func missingOrInvalidNonceError(message: String?) -> Error {
|
|
error(code: .missingOrInvalidNonce, message: message)
|
|
}
|
|
|
|
static func keychainError(function: String, status: OSStatus) -> Error {
|
|
let message = SecCopyErrorMessageString(status, nil) as String? ?? ""
|
|
let reason = "\(function) (\(status)) \(message)"
|
|
return error(code: .keychainError, userInfo: [NSLocalizedFailureReasonErrorKey: reason])
|
|
}
|
|
|
|
static func tenantIDMismatchError() -> Error {
|
|
error(code: .tenantIDMismatch)
|
|
}
|
|
|
|
static func unsupportedTenantOperationError() -> Error {
|
|
error(code: .unsupportedTenantOperation)
|
|
}
|
|
|
|
static func notificationNotForwardedError() -> Error {
|
|
error(code: .notificationNotForwarded)
|
|
}
|
|
|
|
static func appNotVerifiedError(message: String?) -> Error {
|
|
error(code: .appNotVerified, message: message)
|
|
}
|
|
|
|
static func missingClientIdentifierError(message: String?) -> Error {
|
|
error(code: .missingClientIdentifier, message: message)
|
|
}
|
|
|
|
static func missingClientType(message: String?) -> Error {
|
|
error(code: .missingClientType, message: message)
|
|
}
|
|
|
|
static func captchaCheckFailedError(message: String?) -> Error {
|
|
error(code: .captchaCheckFailed, message: message)
|
|
}
|
|
|
|
static func unexpectedResponse(data: Data?, underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let data {
|
|
userInfo[userInfoDataKey] = data
|
|
}
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
|
|
}
|
|
|
|
static func unexpectedErrorResponse(data: Data?,
|
|
underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let data {
|
|
userInfo[userInfoDataKey] = data
|
|
}
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
|
|
}
|
|
|
|
static func unexpectedErrorResponse(deserializedResponse: Any?) -> Error {
|
|
var userInfo: [String: Any]?
|
|
if let deserializedResponse {
|
|
userInfo = [userInfoDeserializedResponseKey: deserializedResponse]
|
|
}
|
|
return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
|
|
}
|
|
|
|
static func unexpectedResponse(deserializedResponse: Any?) -> Error {
|
|
var userInfo: [String: Any]?
|
|
if let deserializedResponse {
|
|
userInfo = [userInfoDeserializedResponseKey: deserializedResponse]
|
|
}
|
|
return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
|
|
}
|
|
|
|
static func unexpectedResponse(deserializedResponse: Any?,
|
|
underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let deserializedResponse {
|
|
userInfo[userInfoDeserializedResponseKey] = deserializedResponse
|
|
}
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
|
|
}
|
|
|
|
static func unexpectedErrorResponse(deserializedResponse: Any?,
|
|
underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let deserializedResponse {
|
|
userInfo[userInfoDeserializedResponseKey] = deserializedResponse
|
|
}
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(
|
|
code: .internal(.unexpectedErrorResponse),
|
|
userInfo: userInfo.isEmpty ? nil : userInfo
|
|
)
|
|
}
|
|
|
|
static func malformedJWTError(token: String, underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [
|
|
NSLocalizedDescriptionKey: kFIRAuthErrorMessageMalformedJWT,
|
|
userInfoDataKey: token,
|
|
]
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(code: .malformedJWT, userInfo: userInfo)
|
|
}
|
|
|
|
static func RPCResponseDecodingError(deserializedResponse: Any?,
|
|
underlyingError: Error?) -> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let deserializedResponse {
|
|
userInfo[userInfoDeserializedResponseKey] = deserializedResponse
|
|
}
|
|
if let underlyingError {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError
|
|
}
|
|
return error(code: .internal(.RPCResponseDecodingError), userInfo: userInfo)
|
|
}
|
|
|
|
static func accountExistsWithDifferentCredentialError(email: String?,
|
|
updatedCredential: AuthCredential?)
|
|
-> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let email {
|
|
userInfo[AuthErrors.userInfoEmailKey] = email
|
|
}
|
|
if let updatedCredential {
|
|
userInfo[AuthErrors.userInfoUpdatedCredentialKey] = updatedCredential
|
|
}
|
|
return error(code: .accountExistsWithDifferentCredential, userInfo: userInfo)
|
|
}
|
|
|
|
private static func extractJSONObjectFromString(from string: String) -> [String: Any]? {
|
|
// 1. Find the start of the JSON object.
|
|
guard let start = string.firstIndex(of: "{") else {
|
|
return nil // No JSON object found
|
|
}
|
|
// 2. Find the end of the JSON object.
|
|
// Start from the first curly brace `{`
|
|
var curlyLevel = 0
|
|
var endIndex: String.Index?
|
|
|
|
for index in string.indices.suffix(from: start) {
|
|
let char = string[index]
|
|
if char == "{" {
|
|
curlyLevel += 1
|
|
} else if char == "}" {
|
|
curlyLevel -= 1
|
|
if curlyLevel == 0 {
|
|
endIndex = index
|
|
break
|
|
}
|
|
}
|
|
}
|
|
guard let end = endIndex else {
|
|
return nil // Unbalanced curly braces
|
|
}
|
|
|
|
// 3. Extract the JSON string.
|
|
let jsonString = String(string[start ... end])
|
|
|
|
// 4. Convert JSON String to JSON Object
|
|
guard let jsonData = jsonString.data(using: .utf8) else {
|
|
return nil // Could not convert String to Data
|
|
}
|
|
|
|
do {
|
|
if let jsonObject = try JSONSerialization
|
|
.jsonObject(with: jsonData, options: []) as? [String: Any] {
|
|
return jsonObject
|
|
} else {
|
|
return nil // JSON Object is not a dictionary
|
|
}
|
|
} catch {
|
|
return nil // Failed to deserialize JSON
|
|
}
|
|
}
|
|
|
|
static func blockingCloudFunctionServerResponse(message: String?) -> Error {
|
|
guard let message else {
|
|
return error(code: .blockingCloudFunctionError, message: message)
|
|
}
|
|
guard let jsonDict = extractJSONObjectFromString(from: message) else {
|
|
return error(code: .blockingCloudFunctionError, message: message)
|
|
}
|
|
let errorDict = jsonDict["error"] as? [String: Any] ?? [:]
|
|
let errorMessage = errorDict["message"] as? String
|
|
return error(code: .blockingCloudFunctionError, message: errorMessage)
|
|
}
|
|
|
|
#if os(iOS) || os(macOS)
|
|
static func secondFactorRequiredError(pendingCredential: String?,
|
|
hints: [MultiFactorInfo],
|
|
auth: Auth)
|
|
-> Error {
|
|
var userInfo: [String: Any] = [:]
|
|
if let pendingCredential = pendingCredential {
|
|
let resolver = MultiFactorResolver(with: pendingCredential, hints: hints, auth: auth)
|
|
userInfo[AuthErrors.userInfoMultiFactorResolverKey] = resolver
|
|
}
|
|
|
|
return error(code: .secondFactorRequired, userInfo: userInfo)
|
|
}
|
|
#endif // os(iOS) || os(macOS)
|
|
|
|
static func recaptchaSDKNotLinkedError() -> Error {
|
|
// TODO(ObjC): point the link to GCIP doc once available.
|
|
let message = "The reCAPTCHA SDK is not linked to your app. See " +
|
|
"https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
|
|
return error(code: .recaptchaSDKNotLinked, message: message)
|
|
}
|
|
|
|
static func recaptchaSiteKeyMissing() -> Error {
|
|
// TODO(ObjC): point the link to GCIP doc once available.
|
|
let message = "The site key for the reCAPTCHA SDK was not found. See " +
|
|
"https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
|
|
return error(code: .recaptchaSiteKeyMissing, message: message)
|
|
}
|
|
|
|
static func recaptchaActionCreationFailed() -> Error {
|
|
// TODO(ObjC): point the link to GCIP doc once available.
|
|
let message = "The reCAPTCHA SDK action class creation failed. See " +
|
|
"https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
|
|
return error(code: .recaptchaActionCreationFailed, message: message)
|
|
}
|
|
|
|
static func passwordDoesNotMeetRequirementsError(message: String?) -> Error {
|
|
return error(code: .passwordDoesNotMeetRequirements, message: message)
|
|
}
|
|
}
|