AuthErrorUtils.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. // MARK: - URL response error codes
  16. /// Error code that indicates that the client ID provided was invalid.
  17. private let kURLResponseErrorCodeInvalidClientID = "auth/invalid-oauth-client-id"
  18. /// Error code that indicates that a network request within the SFSafariViewController or WKWebView
  19. /// failed.
  20. private let kURLResponseErrorCodeNetworkRequestFailed = "auth/network-request-failed"
  21. /// Error code that indicates that an internal error occurred within the
  22. /// SFSafariViewController or WKWebView failed.
  23. private let kURLResponseErrorCodeInternalError = "auth/internal-error"
  24. private let kFIRAuthErrorMessageMalformedJWT =
  25. "Failed to parse JWT. Check the userInfo dictionary for the full token."
  26. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  27. class AuthErrorUtils {
  28. static let internalErrorDomain = "FIRAuthInternalErrorDomain"
  29. static let userInfoDeserializedResponseKey = "FIRAuthErrorUserInfoDeserializedResponseKey"
  30. static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey"
  31. /// This marker indicates that the server error message contains a detail error message which
  32. /// should be used instead of the hardcoded client error message.
  33. private static let kServerErrorDetailMarker = " : "
  34. static func error(code: SharedErrorCode, userInfo: [String: Any]? = nil) -> Error {
  35. switch code {
  36. case let .public(publicCode):
  37. var errorUserInfo: [String: Any] = userInfo ?? [:]
  38. if errorUserInfo[NSLocalizedDescriptionKey] == nil {
  39. errorUserInfo[NSLocalizedDescriptionKey] = publicCode.errorDescription
  40. }
  41. if let localizedDescription = errorUserInfo[NSLocalizedDescriptionKey] as? String,
  42. localizedDescription == "" {
  43. errorUserInfo[NSLocalizedDescriptionKey] = publicCode.errorDescription
  44. }
  45. errorUserInfo[AuthErrors.userInfoNameKey] = publicCode.errorCodeString
  46. return NSError(
  47. domain: AuthErrors.domain,
  48. code: publicCode.rawValue,
  49. userInfo: errorUserInfo
  50. )
  51. case let .internal(internalCode):
  52. // This is an internal error. Wrap it in an internal error.
  53. let error = NSError(
  54. domain: internalErrorDomain,
  55. code: internalCode.rawValue,
  56. userInfo: userInfo
  57. )
  58. return self.error(code: .public(.internalError), underlyingError: error)
  59. }
  60. }
  61. static func error(code: SharedErrorCode, underlyingError: Error?) -> Error {
  62. var errorUserInfo: [String: Any]?
  63. if let underlyingError = underlyingError {
  64. errorUserInfo = [NSUnderlyingErrorKey: underlyingError]
  65. }
  66. return error(code: code, userInfo: errorUserInfo)
  67. }
  68. static func error(code: AuthErrorCode, underlyingError: Error?) -> Error {
  69. error(code: SharedErrorCode.public(code), underlyingError: underlyingError)
  70. }
  71. static func error(code: AuthErrorCode, userInfo: [String: Any]? = nil) -> Error {
  72. error(code: SharedErrorCode.public(code), userInfo: userInfo)
  73. }
  74. static func error(code: AuthErrorCode, message: String?) -> Error {
  75. let userInfo: [String: Any]?
  76. if let message {
  77. userInfo = [NSLocalizedDescriptionKey: message]
  78. } else {
  79. userInfo = nil
  80. }
  81. return error(code: SharedErrorCode.public(code), userInfo: userInfo)
  82. }
  83. static func userDisabledError(message: String?) -> Error {
  84. error(code: .userDisabled, message: message)
  85. }
  86. static func wrongPasswordError(message: String?) -> Error {
  87. error(code: .wrongPassword, message: message)
  88. }
  89. static func tooManyRequestsError(message: String?) -> Error {
  90. error(code: .tooManyRequests, message: message)
  91. }
  92. static func invalidCustomTokenError(message: String?) -> Error {
  93. error(code: .invalidCustomToken, message: message)
  94. }
  95. static func customTokenMismatchError(message: String?) -> Error {
  96. error(code: .customTokenMismatch, message: message)
  97. }
  98. static func invalidCredentialError(message: String?) -> Error {
  99. error(code: .invalidCredential, message: message)
  100. }
  101. static func requiresRecentLoginError(message: String?) -> Error {
  102. error(code: .requiresRecentLogin, message: message)
  103. }
  104. static func invalidUserTokenError(message: String?) -> Error {
  105. error(code: .invalidUserToken, message: message)
  106. }
  107. static func invalidEmailError(message: String?) -> Error {
  108. error(code: .invalidEmail, message: message)
  109. }
  110. static func providerAlreadyLinkedError() -> Error {
  111. error(code: .providerAlreadyLinked)
  112. }
  113. static func noSuchProviderError() -> Error {
  114. error(code: .noSuchProvider)
  115. }
  116. static func userTokenExpiredError(message: String?) -> Error {
  117. error(code: .userTokenExpired, message: message)
  118. }
  119. static func userNotFoundError(message: String?) -> Error {
  120. error(code: .userNotFound, message: message)
  121. }
  122. static func invalidAPIKeyError() -> Error {
  123. error(code: .invalidAPIKey)
  124. }
  125. static func userMismatchError() -> Error {
  126. error(code: .userMismatch)
  127. }
  128. static func operationNotAllowedError(message: String?) -> Error {
  129. error(code: .operationNotAllowed, message: message)
  130. }
  131. static func weakPasswordError(serverResponseReason reason: String?) -> Error {
  132. let userInfo: [String: Any]?
  133. if let reason, !reason.isEmpty {
  134. userInfo = [
  135. NSLocalizedFailureReasonErrorKey: reason,
  136. ]
  137. } else {
  138. userInfo = nil
  139. }
  140. return error(code: .weakPassword, userInfo: userInfo)
  141. }
  142. static func appNotAuthorizedError() -> Error {
  143. error(code: .appNotAuthorized)
  144. }
  145. static func expiredActionCodeError(message: String?) -> Error {
  146. error(code: .expiredActionCode, message: message)
  147. }
  148. static func invalidActionCodeError(message: String?) -> Error {
  149. error(code: .invalidActionCode, message: message)
  150. }
  151. static func invalidMessagePayloadError(message: String?) -> Error {
  152. error(code: .invalidMessagePayload, message: message)
  153. }
  154. static func invalidSenderError(message: String?) -> Error {
  155. error(code: .invalidSender, message: message)
  156. }
  157. static func invalidRecipientEmailError(message: String?) -> Error {
  158. error(code: .invalidRecipientEmail, message: message)
  159. }
  160. static func missingIosBundleIDError(message: String?) -> Error {
  161. error(code: .missingIosBundleID, message: message)
  162. }
  163. static func missingAndroidPackageNameError(message: String?) -> Error {
  164. error(code: .missingAndroidPackageName, message: message)
  165. }
  166. static func invalidRecaptchaTokenError() -> Error {
  167. error(code: .invalidRecaptchaToken)
  168. }
  169. static func unauthorizedDomainError(message: String?) -> Error {
  170. error(code: .unauthorizedDomain, message: message)
  171. }
  172. static func invalidContinueURIError(message: String?) -> Error {
  173. error(code: .invalidContinueURI, message: message)
  174. }
  175. static func missingContinueURIError(message: String?) -> Error {
  176. error(code: .missingContinueURI, message: message)
  177. }
  178. static func missingEmailError(message: String?) -> Error {
  179. error(code: .missingEmail, message: message)
  180. }
  181. static func missingPhoneNumberError(message: String?) -> Error {
  182. error(code: .missingPhoneNumber, message: message)
  183. }
  184. static func invalidPhoneNumberError(message: String?) -> Error {
  185. error(code: .invalidPhoneNumber, message: message)
  186. }
  187. static func missingVerificationCodeError(message: String?) -> Error {
  188. error(code: .missingVerificationCode, message: message)
  189. }
  190. static func invalidVerificationCodeError(message: String?) -> Error {
  191. error(code: .invalidVerificationCode, message: message)
  192. }
  193. static func missingVerificationIDError(message: String?) -> Error {
  194. error(code: .missingVerificationID, message: message)
  195. }
  196. static func invalidVerificationIDError(message: String?) -> Error {
  197. error(code: .invalidVerificationID, message: message)
  198. }
  199. static func sessionExpiredError(message: String?) -> Error {
  200. error(code: .sessionExpired, message: message)
  201. }
  202. static func missingAppCredential(message: String?) -> Error {
  203. error(code: .missingAppCredential, message: message)
  204. }
  205. static func invalidAppCredential(message: String?) -> Error {
  206. error(code: .invalidAppCredential, message: message)
  207. }
  208. static func quotaExceededError(message: String?) -> Error {
  209. error(code: .quotaExceeded, message: message)
  210. }
  211. static func missingAppTokenError(underlyingError: Error?) -> Error {
  212. error(code: .missingAppToken, underlyingError: underlyingError)
  213. }
  214. static func localPlayerNotAuthenticatedError() -> Error {
  215. error(code: .localPlayerNotAuthenticated)
  216. }
  217. static func gameKitNotLinkedError() -> Error {
  218. error(code: .gameKitNotLinked)
  219. }
  220. static func RPCRequestEncodingError(underlyingError: Error) -> Error {
  221. error(code: .internal(.RPCRequestEncodingError), underlyingError: underlyingError)
  222. }
  223. static func JSONSerializationErrorForUnencodableType() -> Error {
  224. error(code: .internal(.JSONSerializationError))
  225. }
  226. static func JSONSerializationError(underlyingError: Error) -> Error {
  227. error(code: .internal(.JSONSerializationError), underlyingError: underlyingError)
  228. }
  229. static func networkError(underlyingError: Error) -> Error {
  230. error(code: .networkError, underlyingError: underlyingError)
  231. }
  232. static func emailAlreadyInUseError(email: String?) -> Error {
  233. var userInfo: [String: Any]?
  234. if let email, !email.isEmpty {
  235. userInfo = [AuthErrors.userInfoEmailKey: email]
  236. }
  237. return error(code: .emailAlreadyInUse, userInfo: userInfo)
  238. }
  239. static func credentialAlreadyInUseError(message: String?,
  240. credential: AuthCredential?,
  241. email: String?) -> Error {
  242. var userInfo: [String: Any] = [:]
  243. if let credential {
  244. userInfo[AuthErrors.userInfoUpdatedCredentialKey] = credential
  245. }
  246. if let email, !email.isEmpty {
  247. userInfo[AuthErrors.userInfoEmailKey] = email
  248. }
  249. if !userInfo.isEmpty {
  250. return error(code: .credentialAlreadyInUse, userInfo: userInfo)
  251. }
  252. return error(code: .credentialAlreadyInUse, message: message)
  253. }
  254. static func webContextAlreadyPresentedError(message: String?) -> Error {
  255. error(code: .webContextAlreadyPresented, message: message)
  256. }
  257. static func webContextCancelledError(message: String?) -> Error {
  258. error(code: .webContextCancelled, message: message)
  259. }
  260. static func appVerificationUserInteractionFailure(reason: String?) -> Error {
  261. let userInfo: [String: Any]?
  262. if let reason, !reason.isEmpty {
  263. userInfo = [NSLocalizedFailureReasonErrorKey: reason]
  264. } else {
  265. userInfo = nil
  266. }
  267. return error(code: .appVerificationUserInteractionFailure, userInfo: userInfo)
  268. }
  269. static func webSignInUserInteractionFailure(reason: String?) -> Error {
  270. let userInfo: [String: Any]?
  271. if let reason, !reason.isEmpty {
  272. userInfo = [NSLocalizedFailureReasonErrorKey: reason]
  273. } else {
  274. userInfo = nil
  275. }
  276. return error(code: .webSignInUserInteractionFailure, userInfo: userInfo)
  277. }
  278. static func urlResponseError(code: String, message: String?) -> Error {
  279. let errorCode: AuthErrorCode
  280. switch code {
  281. case kURLResponseErrorCodeInvalidClientID:
  282. errorCode = .invalidClientID
  283. case kURLResponseErrorCodeNetworkRequestFailed:
  284. errorCode = .webNetworkRequestFailed
  285. case kURLResponseErrorCodeInternalError:
  286. errorCode = .webInternalError
  287. default:
  288. return AuthErrorUtils.webSignInUserInteractionFailure(reason: "[\(code)] - \(message ?? "")")
  289. }
  290. return error(code: errorCode, message: message)
  291. }
  292. static func nullUserError(message: String?) -> Error {
  293. error(code: .nullUser, message: message)
  294. }
  295. static func invalidProviderIDError(message: String?) -> Error {
  296. error(code: .invalidProviderID, message: message)
  297. }
  298. static func invalidDynamicLinkDomainError(message: String?) -> Error {
  299. error(code: .invalidDynamicLinkDomain, message: message)
  300. }
  301. static func invalidHostingLinkDomainError(message: String?) -> Error {
  302. error(code: .invalidHostingLinkDomain, message: message)
  303. }
  304. static func missingOrInvalidNonceError(message: String?) -> Error {
  305. error(code: .missingOrInvalidNonce, message: message)
  306. }
  307. static func keychainError(function: String, status: OSStatus) -> Error {
  308. let message = SecCopyErrorMessageString(status, nil) as String? ?? ""
  309. let reason = "\(function) (\(status)) \(message)"
  310. return error(code: .keychainError, userInfo: [NSLocalizedFailureReasonErrorKey: reason])
  311. }
  312. static func tenantIDMismatchError() -> Error {
  313. error(code: .tenantIDMismatch)
  314. }
  315. static func unsupportedTenantOperationError() -> Error {
  316. error(code: .unsupportedTenantOperation)
  317. }
  318. static func notificationNotForwardedError() -> Error {
  319. error(code: .notificationNotForwarded)
  320. }
  321. static func appNotVerifiedError(message: String?) -> Error {
  322. error(code: .appNotVerified, message: message)
  323. }
  324. static func missingClientIdentifierError(message: String?) -> Error {
  325. error(code: .missingClientIdentifier, message: message)
  326. }
  327. static func missingClientType(message: String?) -> Error {
  328. error(code: .missingClientType, message: message)
  329. }
  330. static func captchaCheckFailedError(message: String?) -> Error {
  331. error(code: .captchaCheckFailed, message: message)
  332. }
  333. static func unexpectedResponse(data: Data?, underlyingError: Error?) -> Error {
  334. var userInfo: [String: Any] = [:]
  335. if let data {
  336. userInfo[userInfoDataKey] = data
  337. }
  338. if let underlyingError {
  339. userInfo[NSUnderlyingErrorKey] = underlyingError
  340. }
  341. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  342. }
  343. static func unexpectedErrorResponse(data: Data?,
  344. underlyingError: Error?) -> Error {
  345. var userInfo: [String: Any] = [:]
  346. if let data {
  347. userInfo[userInfoDataKey] = data
  348. }
  349. if let underlyingError {
  350. userInfo[NSUnderlyingErrorKey] = underlyingError
  351. }
  352. return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
  353. }
  354. static func unexpectedErrorResponse(deserializedResponse: Any?) -> Error {
  355. var userInfo: [String: Any]?
  356. if let deserializedResponse {
  357. userInfo = [userInfoDeserializedResponseKey: deserializedResponse]
  358. }
  359. return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
  360. }
  361. static func unexpectedResponse(deserializedResponse: Any?) -> Error {
  362. var userInfo: [String: Any]?
  363. if let deserializedResponse {
  364. userInfo = [userInfoDeserializedResponseKey: deserializedResponse]
  365. }
  366. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  367. }
  368. static func unexpectedResponse(deserializedResponse: Any?,
  369. underlyingError: Error?) -> Error {
  370. var userInfo: [String: Any] = [:]
  371. if let deserializedResponse {
  372. userInfo[userInfoDeserializedResponseKey] = deserializedResponse
  373. }
  374. if let underlyingError {
  375. userInfo[NSUnderlyingErrorKey] = underlyingError
  376. }
  377. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  378. }
  379. static func unexpectedErrorResponse(deserializedResponse: Any?,
  380. underlyingError: Error?) -> Error {
  381. var userInfo: [String: Any] = [:]
  382. if let deserializedResponse {
  383. userInfo[userInfoDeserializedResponseKey] = deserializedResponse
  384. }
  385. if let underlyingError {
  386. userInfo[NSUnderlyingErrorKey] = underlyingError
  387. }
  388. return error(
  389. code: .internal(.unexpectedErrorResponse),
  390. userInfo: userInfo.isEmpty ? nil : userInfo
  391. )
  392. }
  393. static func malformedJWTError(token: String, underlyingError: Error?) -> Error {
  394. var userInfo: [String: Any] = [
  395. NSLocalizedDescriptionKey: kFIRAuthErrorMessageMalformedJWT,
  396. userInfoDataKey: token,
  397. ]
  398. if let underlyingError {
  399. userInfo[NSUnderlyingErrorKey] = underlyingError
  400. }
  401. return error(code: .malformedJWT, userInfo: userInfo)
  402. }
  403. static func RPCResponseDecodingError(deserializedResponse: Any?,
  404. underlyingError: Error?) -> Error {
  405. var userInfo: [String: Any] = [:]
  406. if let deserializedResponse {
  407. userInfo[userInfoDeserializedResponseKey] = deserializedResponse
  408. }
  409. if let underlyingError {
  410. userInfo[NSUnderlyingErrorKey] = underlyingError
  411. }
  412. return error(code: .internal(.RPCResponseDecodingError), userInfo: userInfo)
  413. }
  414. static func accountExistsWithDifferentCredentialError(email: String?,
  415. updatedCredential: AuthCredential?)
  416. -> Error {
  417. var userInfo: [String: Any] = [:]
  418. if let email {
  419. userInfo[AuthErrors.userInfoEmailKey] = email
  420. }
  421. if let updatedCredential {
  422. userInfo[AuthErrors.userInfoUpdatedCredentialKey] = updatedCredential
  423. }
  424. return error(code: .accountExistsWithDifferentCredential, userInfo: userInfo)
  425. }
  426. private static func extractJSONObjectFromString(from string: String) -> [String: Any]? {
  427. // 1. Find the start of the JSON object.
  428. guard let start = string.firstIndex(of: "{") else {
  429. return nil // No JSON object found
  430. }
  431. // 2. Find the end of the JSON object.
  432. // Start from the first curly brace `{`
  433. var curlyLevel = 0
  434. var endIndex: String.Index?
  435. for index in string.indices.suffix(from: start) {
  436. let char = string[index]
  437. if char == "{" {
  438. curlyLevel += 1
  439. } else if char == "}" {
  440. curlyLevel -= 1
  441. if curlyLevel == 0 {
  442. endIndex = index
  443. break
  444. }
  445. }
  446. }
  447. guard let end = endIndex else {
  448. return nil // Unbalanced curly braces
  449. }
  450. // 3. Extract the JSON string.
  451. let jsonString = String(string[start ... end])
  452. // 4. Convert JSON String to JSON Object
  453. guard let jsonData = jsonString.data(using: .utf8) else {
  454. return nil // Could not convert String to Data
  455. }
  456. do {
  457. if let jsonObject = try JSONSerialization
  458. .jsonObject(with: jsonData, options: []) as? [String: Any] {
  459. return jsonObject
  460. } else {
  461. return nil // JSON Object is not a dictionary
  462. }
  463. } catch {
  464. return nil // Failed to deserialize JSON
  465. }
  466. }
  467. static func blockingCloudFunctionServerResponse(message: String?) -> Error {
  468. guard let message else {
  469. return error(code: .blockingCloudFunctionError, message: message)
  470. }
  471. guard let jsonDict = extractJSONObjectFromString(from: message) else {
  472. return error(code: .blockingCloudFunctionError, message: message)
  473. }
  474. let errorDict = jsonDict["error"] as? [String: Any] ?? [:]
  475. let errorMessage = errorDict["message"] as? String
  476. return error(code: .blockingCloudFunctionError, message: errorMessage)
  477. }
  478. #if os(iOS)
  479. static func secondFactorRequiredError(pendingCredential: String?,
  480. hints: [MultiFactorInfo],
  481. auth: Auth)
  482. -> Error {
  483. var userInfo: [String: Any] = [:]
  484. if let pendingCredential = pendingCredential {
  485. let resolver = MultiFactorResolver(with: pendingCredential, hints: hints, auth: auth)
  486. userInfo[AuthErrors.userInfoMultiFactorResolverKey] = resolver
  487. }
  488. return error(code: .secondFactorRequired, userInfo: userInfo)
  489. }
  490. #endif // os(iOS)
  491. static func recaptchaSDKNotLinkedError() -> Error {
  492. // TODO(ObjC): point the link to GCIP doc once available.
  493. let message = "The reCAPTCHA SDK is not linked to your app. See " +
  494. "https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
  495. return error(code: .recaptchaSDKNotLinked, message: message)
  496. }
  497. static func recaptchaSiteKeyMissing() -> Error {
  498. // TODO(ObjC): point the link to GCIP doc once available.
  499. let message = "The site key for the reCAPTCHA SDK was not found. See " +
  500. "https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
  501. return error(code: .recaptchaSiteKeyMissing, message: message)
  502. }
  503. static func recaptchaActionCreationFailed() -> Error {
  504. // TODO(ObjC): point the link to GCIP doc once available.
  505. let message = "The reCAPTCHA SDK action class creation failed. See " +
  506. "https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps"
  507. return error(code: .recaptchaActionCreationFailed, message: message)
  508. }
  509. }