AuthErrorUtils.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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. #if COCOAPODS
  16. @_implementationOnly import FirebaseAuth_Private
  17. #endif // COCOAPODS
  18. private let FIRAuthErrorDomain = "FIRAuthErrorDomain"
  19. private let FIRAuthInternalErrorDomain = "FIRAuthInternalErrorDomain"
  20. private let FIRAuthErrorUserInfoDeserializedResponseKey =
  21. "FIRAuthErrorUserInfoDeserializedResponseKey"
  22. private let FIRAuthErrorUserInfoDataKey = "FIRAuthErrorUserInfoDataKey"
  23. private let FIRAuthErrorUserInfoEmailKey = "FIRAuthErrorUserInfoEmailKey"
  24. private let FIRAuthErrorUserInfoUpdatedCredentialKey =
  25. "FIRAuthErrorUserInfoUpdatedCredentialKey"
  26. private let FIRAuthErrorUserInfoNameKey = "FIRAuthErrorUserInfoNameKey"
  27. private let FIRAuthErrorUserInfoMultiFactorResolverKey =
  28. "FIRAuthErrorUserInfoMultiFactorResolverKey"
  29. /** @var kServerErrorDetailMarker
  30. @brief This marker indicates that the server error message contains a detail error message which
  31. should be used instead of the hardcoded client error message.
  32. */
  33. private let kServerErrorDetailMarker = " : "
  34. // MARK: - URL response error codes
  35. /** @var kURLResponseErrorCodeInvalidClientID
  36. @brief Error code that indicates that the client ID provided was invalid.
  37. */
  38. private let kURLResponseErrorCodeInvalidClientID = "auth/invalid-oauth-client-id"
  39. /** @var kURLResponseErrorCodeNetworkRequestFailed
  40. @brief Error code that indicates that a network request within the SFSafariViewController or
  41. WKWebView failed.
  42. */
  43. private let kURLResponseErrorCodeNetworkRequestFailed = "auth/network-request-failed"
  44. /** @var kURLResponseErrorCodeInternalError
  45. @brief Error code that indicates that an internal error occurred within the
  46. SFSafariViewController or WKWebView failed.
  47. */
  48. private let kURLResponseErrorCodeInternalError = "auth/internal-error"
  49. private let kFIRAuthErrorMessageMalformedJWT =
  50. "Failed to parse JWT. Check the userInfo dictionary for the full token."
  51. @objc(FIRAuthErrorUtils) public class AuthErrorUtils: NSObject {
  52. static func error(code: SharedErrorCode, userInfo: [String: Any]? = nil) -> Error {
  53. switch code {
  54. case let .public(publicCode):
  55. var errorUserInfo: [String: Any] = userInfo ?? [:]
  56. if errorUserInfo[NSLocalizedDescriptionKey] == nil {
  57. errorUserInfo[NSLocalizedDescriptionKey] = publicCode.errorDescription
  58. }
  59. errorUserInfo[FIRAuthErrorUserInfoNameKey] = publicCode.errorCodeString
  60. return NSError(
  61. domain: FIRAuthErrorDomain,
  62. code: publicCode.rawValue,
  63. userInfo: errorUserInfo
  64. )
  65. case let .internal(internalCode):
  66. // This is an internal error. Wrap it in an internal error.
  67. let error = NSError(
  68. domain: FIRAuthInternalErrorDomain,
  69. code: internalCode.rawValue,
  70. userInfo: userInfo
  71. )
  72. return self.error(code: .public(.internalError), underlyingError: error)
  73. }
  74. }
  75. static func error(code: SharedErrorCode, underlyingError: Error?) -> Error {
  76. var errorUserInfo: [String: Any]?
  77. if let underlyingError = underlyingError {
  78. errorUserInfo = [NSUnderlyingErrorKey: underlyingError]
  79. }
  80. return error(code: code, userInfo: errorUserInfo)
  81. }
  82. static func error(code: AuthErrorCode, underlyingError: Error?) -> Error {
  83. error(code: SharedErrorCode.public(code), underlyingError: underlyingError)
  84. }
  85. @objc public static func error(code: AuthErrorCode, userInfo: [String: Any]? = nil) -> Error {
  86. error(code: SharedErrorCode.public(code), userInfo: userInfo)
  87. }
  88. @objc public static func error(code: AuthErrorCode, message: String?) -> Error {
  89. let userInfo: [String: Any]?
  90. if let message, !message.isEmpty {
  91. userInfo = [NSLocalizedDescriptionKey: message]
  92. } else {
  93. userInfo = nil
  94. }
  95. return error(code: SharedErrorCode.public(code), userInfo: userInfo)
  96. }
  97. @objc public static func userDisabledErrorWith(message: String?) -> Error {
  98. error(code: .userDisabled, message: message)
  99. }
  100. @objc public static func wrongPasswordError(message: String?) -> Error {
  101. error(code: .wrongPassword, message: message)
  102. }
  103. @objc public static func tooManyRequestsError(message: String?) -> Error {
  104. error(code: .tooManyRequests, message: message)
  105. }
  106. @objc public static func invalidCustomTokenError(message: String?) -> Error {
  107. error(code: .invalidCustomToken, message: message)
  108. }
  109. @objc public static func customTokenMistmatchError(message: String?) -> Error {
  110. error(code: .customTokenMismatch, message: message)
  111. }
  112. @objc public static func invalidCredentialError(message: String?) -> Error {
  113. error(code: .invalidCredential, message: message)
  114. }
  115. @objc public static func requiresRecentLoginError(message: String?) -> Error {
  116. error(code: .requiresRecentLogin, message: message)
  117. }
  118. @objc public static func invalidUserTokenError(message: String?) -> Error {
  119. error(code: .invalidUserToken, message: message)
  120. }
  121. @objc public static func invalidEmailError(message: String?) -> Error {
  122. error(code: .invalidEmail, message: message)
  123. }
  124. @objc public static func providerAlreadyLinkedError() -> Error {
  125. error(code: .providerAlreadyLinked)
  126. }
  127. @objc public static func noSuchProviderError() -> Error {
  128. error(code: .noSuchProvider)
  129. }
  130. @objc public static func userTokenExpiredError(message: String?) -> Error {
  131. error(code: .userTokenExpired, message: message)
  132. }
  133. @objc public static func userNotFoundError(message: String?) -> Error {
  134. error(code: .userNotFound, message: message)
  135. }
  136. @objc public static func invalidAPIKeyError() -> Error {
  137. error(code: .invalidAPIKey)
  138. }
  139. @objc public static func userMismatchError() -> Error {
  140. error(code: .userMismatch)
  141. }
  142. @objc public static func operationNotAllowedError(message: String?) -> Error {
  143. error(code: .operationNotAllowed, message: message)
  144. }
  145. @objc public static func weakPasswordError(serverResponseReason reason: String?) -> Error {
  146. let userInfo: [String: Any]?
  147. if let reason, !reason.isEmpty {
  148. userInfo = [
  149. NSLocalizedFailureReasonErrorKey: reason,
  150. ]
  151. } else {
  152. userInfo = nil
  153. }
  154. return error(code: .weakPassword, userInfo: userInfo)
  155. }
  156. @objc public static func appNotAuthorizedError() -> Error {
  157. error(code: .appNotAuthorized)
  158. }
  159. @objc public static func expiredActionCodeError(message: String?) -> Error {
  160. error(code: .expiredActionCode, message: message)
  161. }
  162. @objc public static func invalidActionCodeError(message: String?) -> Error {
  163. error(code: .invalidActionCode, message: message)
  164. }
  165. @objc public static func invalidMessagePayloadError(message: String?) -> Error {
  166. error(code: .invalidMessagePayload, message: message)
  167. }
  168. @objc public static func invalidSenderError(message: String?) -> Error {
  169. error(code: .invalidSender, message: message)
  170. }
  171. @objc public static func invalidRecipientEmailError(message: String?) -> Error {
  172. error(code: .invalidRecipientEmail, message: message)
  173. }
  174. @objc public static func missingIosBundleIDError(message: String?) -> Error {
  175. error(code: .missingIosBundleID, message: message)
  176. }
  177. @objc public static func missingAndroidPackageNameError(message: String?) -> Error {
  178. error(code: .missingAndroidPackageName, message: message)
  179. }
  180. @objc public static func unauthorizedDomainError(message: String?) -> Error {
  181. error(code: .unauthorizedDomain, message: message)
  182. }
  183. @objc public static func invalidContinueURIError(message: String?) -> Error {
  184. error(code: .invalidContinueURI, message: message)
  185. }
  186. @objc public static func missingContinueURIError(message: String?) -> Error {
  187. error(code: .missingContinueURI, message: message)
  188. }
  189. @objc public static func missingEmailError(message: String?) -> Error {
  190. error(code: .missingEmail, message: message)
  191. }
  192. @objc public static func missingPhoneNumberError(message: String?) -> Error {
  193. error(code: .missingPhoneNumber, message: message)
  194. }
  195. @objc public static func invalidPhoneNumberError(message: String?) -> Error {
  196. error(code: .invalidPhoneNumber, message: message)
  197. }
  198. @objc public static func missingVerificationCodeError(message: String?) -> Error {
  199. error(code: .missingVerificationCode, message: message)
  200. }
  201. @objc public static func invalidVerificationCodeError(message: String?) -> Error {
  202. error(code: .invalidVerificationCode, message: message)
  203. }
  204. @objc public static func missingVerificationIDError(message: String?) -> Error {
  205. error(code: .missingVerificationID, message: message)
  206. }
  207. @objc public static func invalidVerificationIDError(message: String?) -> Error {
  208. error(code: .invalidVerificationID, message: message)
  209. }
  210. @objc public static func sessionExpiredError(message: String?) -> Error {
  211. error(code: .sessionExpired, message: message)
  212. }
  213. @objc public static func missingAppCredential(message: String?) -> Error {
  214. error(code: .missingAppCredential, message: message)
  215. }
  216. @objc public static func invalidAppCredential(message: String?) -> Error {
  217. error(code: .invalidAppCredential, message: message)
  218. }
  219. @objc public static func quotaExceededError(message: String?) -> Error {
  220. error(code: .quotaExceeded, message: message)
  221. }
  222. @objc public static func missingAppTokenError(underlyingError: Error?) -> Error {
  223. error(code: .missingAppToken, underlyingError: underlyingError)
  224. }
  225. @objc public static func localPlayerNotAuthenticatedError() -> Error {
  226. error(code: .localPlayerNotAuthenticated)
  227. }
  228. @objc public static func gameKitNotLinkedError() -> Error {
  229. error(code: .gameKitNotLinked)
  230. }
  231. @objc public static func RPCRequestEncodingError(underlyingError: Error) -> Error {
  232. error(code: .internal(.RPCRequestEncodingError), underlyingError: underlyingError)
  233. }
  234. @objc public static func JSONSerializationErrorForUnencodableType() -> Error {
  235. error(code: .internal(.JSONSerializationError))
  236. }
  237. @objc public static func JSONSerializationError(underlyingError: Error) -> Error {
  238. error(code: .internal(.JSONSerializationError), underlyingError: underlyingError)
  239. }
  240. @objc public static func networkError(underlyingError: Error) -> Error {
  241. error(code: .networkError, underlyingError: underlyingError)
  242. }
  243. @objc public static func emailAlreadyInUseError(email: String?) -> Error {
  244. var userInfo: [String: Any]?
  245. if let email, !email.isEmpty {
  246. userInfo = [FIRAuthErrorUserInfoEmailKey: email]
  247. }
  248. return error(code: .emailAlreadyInUse, userInfo: userInfo)
  249. }
  250. @objc public static func credentialAlreadyInUseError(message: String?,
  251. credential: AuthCredential?,
  252. email: String?) -> Error {
  253. var userInfo: [String: Any] = [:]
  254. if let credential {
  255. userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = credential
  256. }
  257. if let email, !email.isEmpty {
  258. userInfo[FIRAuthErrorUserInfoEmailKey] = email
  259. }
  260. if !userInfo.isEmpty {
  261. return error(code: .credentialAlreadyInUse, userInfo: userInfo)
  262. }
  263. return error(code: .credentialAlreadyInUse, message: message)
  264. }
  265. @objc public static func webContextAlreadyPresentedError(message: String?) -> Error {
  266. error(code: .webContextAlreadyPresented, message: message)
  267. }
  268. @objc public static func webContextCancelledError(message: String?) -> Error {
  269. error(code: .webContextCancelled, message: message)
  270. }
  271. @objc public static func appVerificationUserInteractionFailure(reason: String?) -> Error {
  272. let userInfo: [String: Any]?
  273. if let reason, !reason.isEmpty {
  274. userInfo = [NSLocalizedFailureReasonErrorKey: reason]
  275. } else {
  276. userInfo = nil
  277. }
  278. return error(code: .appVerificationUserInteractionFailure, userInfo: userInfo)
  279. }
  280. @objc public static func webSignInUserInteractionFailure(reason: String?) -> Error {
  281. let userInfo: [String: Any]?
  282. if let reason, !reason.isEmpty {
  283. userInfo = [NSLocalizedFailureReasonErrorKey: reason]
  284. } else {
  285. userInfo = nil
  286. }
  287. return error(code: .webSignInUserInteractionFailure, userInfo: userInfo)
  288. }
  289. @objc public static func URLResponseError(code: String, message: String?) -> Error? {
  290. let errorCode: AuthErrorCode
  291. switch code {
  292. case kURLResponseErrorCodeInvalidClientID:
  293. errorCode = .invalidClientID
  294. case kURLResponseErrorCodeNetworkRequestFailed:
  295. errorCode = .webNetworkRequestFailed
  296. case kURLResponseErrorCodeInternalError:
  297. errorCode = .webInternalError
  298. default:
  299. return nil
  300. }
  301. return error(code: errorCode, message: message)
  302. }
  303. @objc public static func nullUserError(message: String?) -> Error {
  304. error(code: .nullUser, message: message)
  305. }
  306. @objc public static func invalidProviderIDError(message: String?) -> Error {
  307. error(code: .invalidProviderID, message: message)
  308. }
  309. @objc public static func invalidDynamicLinkDomainError(message: String?) -> Error {
  310. error(code: .invalidDynamicLinkDomain, message: message)
  311. }
  312. @objc public static func missingOrInvalidNonceError(message: String?) -> Error {
  313. error(code: .missingOrInvalidNonce, message: message)
  314. }
  315. @objc public static func keychainError(function: String, status: OSStatus) -> Error {
  316. let reason = "\(function) (\(status))"
  317. return error(code: .keychainError, userInfo: [NSLocalizedFailureReasonErrorKey: reason])
  318. }
  319. @objc public static func tenantIDMismatchError() -> Error {
  320. error(code: .tenantIDMismatch)
  321. }
  322. @objc public static func unsupportedTenantOperationError() -> Error {
  323. error(code: .unsupportedTenantOperation)
  324. }
  325. @objc public static func notificationNotForwardedError() -> Error {
  326. error(code: .notificationNotForwarded)
  327. }
  328. @objc public static func appNotVerifiedError(message: String?) -> Error {
  329. error(code: .appNotVerified, message: message)
  330. }
  331. @objc public static func missingClientIdentifierError(message: String?) -> Error {
  332. error(code: .missingClientIdentifier, message: message)
  333. }
  334. @objc public static func captchaCheckFailedError(message: String?) -> Error {
  335. error(code: .captchaCheckFailed, message: message)
  336. }
  337. @objc public static func unexpectedResponse(data: Data?, underlyingError: Error?) -> Error {
  338. var userInfo: [String: Any] = [:]
  339. if let data {
  340. userInfo[FIRAuthErrorUserInfoDataKey] = data
  341. }
  342. if let underlyingError {
  343. userInfo[NSUnderlyingErrorKey] = underlyingError
  344. }
  345. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  346. }
  347. @objc public static func unexpectedErrorResponse(data: Data?,
  348. underlyingError: Error?) -> Error {
  349. var userInfo: [String: Any] = [:]
  350. if let data {
  351. userInfo[FIRAuthErrorUserInfoDataKey] = data
  352. }
  353. if let underlyingError {
  354. userInfo[NSUnderlyingErrorKey] = underlyingError
  355. }
  356. return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
  357. }
  358. @objc public static func unexpectedErrorResponse(deserializedResponse: Any?) -> Error {
  359. var userInfo: [String: Any]?
  360. if let deserializedResponse {
  361. userInfo = [FIRAuthErrorUserInfoDeserializedResponseKey: deserializedResponse]
  362. }
  363. return error(code: .internal(.unexpectedErrorResponse), userInfo: userInfo)
  364. }
  365. @objc public static func unexpectedResponse(deserializedResponse: Any?) -> Error {
  366. var userInfo: [String: Any]?
  367. if let deserializedResponse {
  368. userInfo = [FIRAuthErrorUserInfoDeserializedResponseKey: deserializedResponse]
  369. }
  370. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  371. }
  372. @objc public static func unexpectedResponse(deserializedResponse: Any?,
  373. underlyingError: Error?) -> Error {
  374. var userInfo: [String: Any] = [:]
  375. if let deserializedResponse {
  376. userInfo[FIRAuthErrorUserInfoDeserializedResponseKey] = deserializedResponse
  377. }
  378. if let underlyingError {
  379. userInfo[NSUnderlyingErrorKey] = underlyingError
  380. }
  381. return error(code: .internal(.unexpectedResponse), userInfo: userInfo)
  382. }
  383. @objc public static func unexpectedErrorResponse(deserializedResponse: Any?,
  384. underlyingError: Error?) -> Error {
  385. var userInfo: [String: Any] = [:]
  386. if let deserializedResponse {
  387. userInfo[FIRAuthErrorUserInfoDeserializedResponseKey] = deserializedResponse
  388. }
  389. if let underlyingError {
  390. userInfo[NSUnderlyingErrorKey] = underlyingError
  391. }
  392. return error(
  393. code: .internal(.unexpectedErrorResponse),
  394. userInfo: userInfo.isEmpty ? nil : userInfo
  395. )
  396. }
  397. @objc public static func malformedJWTError(token: String, underlyingError: Error?) -> Error {
  398. var userInfo: [String: Any] = [
  399. NSLocalizedDescriptionKey: kFIRAuthErrorMessageMalformedJWT,
  400. FIRAuthErrorUserInfoDataKey: token,
  401. ]
  402. if let underlyingError {
  403. userInfo[NSUnderlyingErrorKey] = underlyingError
  404. }
  405. return error(code: .malformedJWT, userInfo: userInfo)
  406. }
  407. @objc public static func RPCResponseDecodingError(deserializedResponse: Any?,
  408. underlyingError: Error?) -> Error {
  409. var userInfo: [String: Any] = [:]
  410. if let deserializedResponse {
  411. userInfo[FIRAuthErrorUserInfoDeserializedResponseKey] = deserializedResponse
  412. }
  413. if let underlyingError {
  414. userInfo[NSUnderlyingErrorKey] = underlyingError
  415. }
  416. return error(code: .internal(.RPCResponseDecodingError), userInfo: userInfo)
  417. }
  418. @objc public static func accountExistsWithDifferentCredentialError(email: String?,
  419. updatedCredential: AuthCredential?)
  420. -> Error {
  421. var userInfo: [String: Any] = [:]
  422. if let email {
  423. userInfo[FIRAuthErrorUserInfoEmailKey] = email
  424. }
  425. if let updatedCredential {
  426. userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey] = updatedCredential
  427. }
  428. return error(code: .accountExistsWithDifferentCredential, userInfo: userInfo)
  429. }
  430. @objc public static func blockingCloudFunctionServerResponse(message: String?) -> Error {
  431. guard let message else {
  432. return error(code: .blockingCloudFunctionError, message: message)
  433. }
  434. var jsonString = message.replacingOccurrences(
  435. of: "HTTP Cloud Function returned an error:",
  436. with: ""
  437. )
  438. jsonString = jsonString.trimmingCharacters(in: .whitespaces)
  439. let jsonData = jsonString.data(using: .utf8) ?? Data()
  440. do {
  441. let jsonDict = try JSONSerialization
  442. .jsonObject(with: jsonData, options: []) as? [String: Any] ?? [:]
  443. let errorDict = jsonDict["error"] as? [String: Any] ?? [:]
  444. let errorMessage = errorDict["message"] as? String
  445. return error(code: .blockingCloudFunctionError, message: errorMessage)
  446. } catch {
  447. return JSONSerializationError(underlyingError: error)
  448. }
  449. }
  450. #if os(iOS)
  451. // TODO(ncooke3): Address the optionality of these arguments.
  452. @objc public static func secondFactorRequiredError(pendingCredential: String?,
  453. hints: [MultiFactorInfo]?)
  454. -> Error {
  455. var userInfo: [String: Any] = [:]
  456. if let pendingCredential = pendingCredential, let hints = hints {
  457. let resolver = MultiFactorResolver(
  458. mfaPendingCredential: pendingCredential,
  459. hints: hints
  460. )
  461. userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey] = resolver
  462. }
  463. return error(code: .secondFactorRequired, userInfo: userInfo)
  464. }
  465. #endif // os(iOS)
  466. }
  467. @objc public protocol MultiFactorResolverWrapper: NSObjectProtocol {}