AuthErrorUtils.swift 20 KB

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